浏览代码

Core: refactor of transaction, buy account and list account operations

Herman Schoenfeld 6 年之前
父节点
当前提交
23797282cd

+ 137 - 39
src/core/UAccounts.pas

@@ -134,16 +134,22 @@ Type
   TAccountComp = Class
   TAccountComp = Class
   private
   private
   public
   public
-    Class Function IsValidAccountKey(const account: TAccountKey; var errors : String): Boolean;
-    Class Function IsValidAccountInfo(const accountInfo: TAccountInfo; var errors : String): Boolean;
+    Class Function IsValidAccountKey(const AAccountInfo: TAccountKey; var errors : String): Boolean;
+    Class function IsNullAccountKey(const AAccountInfo : TAccountKey) : Boolean;
+    Class function IsValidNewAccountKey(const AAccountInfo : TAccountInfo; const ANewKey : TAccountKey; AProtocolVersion : Integer; ACurrentBlock : Integer) : Boolean;
+    Class Function IsValidAccountInfo(const AAccountInfo: TAccountInfo; var errors : String): Boolean;
     Class Function IsValidAccountHashLockKey(const AAccount : TAccount; const AKey : TRawBytes) : Boolean;
     Class Function IsValidAccountHashLockKey(const AAccount : TAccount; const AKey : TRawBytes) : Boolean;
     Class Function IsValidHashLockKey(const AKey : TRawBytes; out AError : String) : Boolean;
     Class Function IsValidHashLockKey(const AKey : TRawBytes; out AError : String) : Boolean;
     Class Function CalculateHashLock(const AKey : TRawBytes) : T32Bytes;
     Class Function CalculateHashLock(const AKey : TRawBytes) : T32Bytes;
-    Class Function IsAccountForSale(const accountInfo: TAccountInfo) : Boolean;
-    Class Function IsAccountForSwap(const accountInfo: TAccountInfo) : Boolean;
-    Class Function IsAccountForSaleOrSwap(const accountInfo: TAccountInfo) : Boolean;
-    class function IsAccountForSaleAcceptingTransactions(const AAccountInfo: TAccountInfo): Boolean;
-    Class Function IsAccountForSaleOrSwapAcceptingTransactions(const account: TAccount; const APayload : TRawBytes) : Boolean;
+    Class Function IsAccountForSale(const AAccountInfo: TAccountInfo; ACurrentBlock : Integer) : Boolean;
+    Class function IsAccountForPrivateSale(const AAccountInfo: TAccountInfo; ACurrentBlock : Integer): Boolean;
+    Class function IsAccountForPublicSale(const AAccountInfo: TAccountInfo): Boolean;
+    Class Function IsAccountForSwap(const AAccountInfo: TAccountInfo; ACurrentBlock : Integer) : Boolean;
+    Class function IsAccountForCoinSwap(const AAccountInfo: TAccountInfo; ACurrentBlock : Integer) : Boolean;
+    Class function IsAccountForAccountSwap(const AAccountInfo: TAccountInfo; ACurrentBlock : Integer) : Boolean;
+    Class Function IsAccountForSaleOrSwap(const AAccountInfo: TAccountInfo; ACurrentBlock : Integer) : Boolean;
+    Class Function IsAccountForSaleOrSwapAcceptingTransactions(const AAccount: TAccount; ACurrentBlock : Integer; const APayload : TRawBytes) : Boolean;
+    Class Function IsOperationRecipientSignable(const ASender, ATarget : TAccount; AIncomingFunds : UInt64; ACurrentBlock : Integer ) : Boolean;
     Class Function GetECInfoTxt(Const EC_OpenSSL_NID: Word) : String;
     Class Function GetECInfoTxt(Const EC_OpenSSL_NID: Word) : String;
     Class Procedure ValidsEC_OpenSSL_NID(list : TList<Word>);
     Class Procedure ValidsEC_OpenSSL_NID(list : TList<Word>);
     Class Function AccountKey2RawString(const account: TAccountKey): TRawBytes; overload;
     Class Function AccountKey2RawString(const account: TAccountKey): TRawBytes; overload;
@@ -468,7 +474,7 @@ Type
     Function TransferAmount(previous : TAccountPreviousBlockInfo; const AOpID : TRawBytes; sender,signer,target : Cardinal; n_operation : Cardinal; amount, fee : UInt64; var errors : String) : Boolean;
     Function TransferAmount(previous : TAccountPreviousBlockInfo; const AOpID : TRawBytes; sender,signer,target : Cardinal; n_operation : Cardinal; amount, fee : UInt64; var errors : String) : Boolean;
     Function TransferAmounts(previous : TAccountPreviousBlockInfo; const AOpID : TRawBytes; const senders, n_operations : Array of Cardinal; const sender_amounts : Array of UInt64; const receivers : Array of Cardinal; const receivers_amounts : Array of UInt64; var errors : String) : Boolean;
     Function TransferAmounts(previous : TAccountPreviousBlockInfo; const AOpID : TRawBytes; const senders, n_operations : Array of Cardinal; const sender_amounts : Array of UInt64; const receivers : Array of Cardinal; const receivers_amounts : Array of UInt64; var errors : String) : Boolean;
     Function UpdateAccountInfo(previous : TAccountPreviousBlockInfo; const AOpID : TRawBytes; signer_account, signer_n_operation, target_account: Cardinal; const accountInfo: TAccountInfo; const newName, newData : TRawBytes; newType : Word; fee: UInt64; var errors : String) : Boolean;
     Function UpdateAccountInfo(previous : TAccountPreviousBlockInfo; const AOpID : TRawBytes; signer_account, signer_n_operation, target_account: Cardinal; const accountInfo: TAccountInfo; const newName, newData : TRawBytes; newType : Word; fee: UInt64; var errors : String) : Boolean;
-    Function BuyAccount(APrevious : TAccountPreviousBlockInfo; const AOpID : TRawBytes; ABuyer,AAccountToBuy,ASeller: Cardinal; ANOperation : Cardinal; AAmount, AAccountPrice, AFee : UInt64; const ANewAccountKey : TAccountKey; const AHashLockKey : TRawBytes; var AErrors : String) : Boolean;
+    Function BuyAccount(APrevious : TAccountPreviousBlockInfo; const AOpID : TRawBytes; ABuyer,AAccountToBuy,ASeller: Cardinal; ANOperation : Cardinal; AAmount, AAccountPrice, AFee : UInt64; const ANewAccountKey : TAccountKey; const AHashLockKey : TRawBytes; ARecipientSigned : Boolean; var AErrors : String) : Boolean;
     Function Commit(Const operationBlock : TOperationBlock; var errors : String) : Boolean;
     Function Commit(Const operationBlock : TOperationBlock; var errors : String) : Boolean;
     Function Account(account_number : Cardinal) : TAccount;
     Function Account(account_number : Cardinal) : TAccount;
     Procedure Rollback;
     Procedure Rollback;
@@ -1414,7 +1420,6 @@ begin
   end;
   end;
 end;
 end;
 
 
-
 class function TAccountComp.FormatMoney(Money: Int64): String;
 class function TAccountComp.FormatMoney(Money: Int64): String;
 begin
 begin
   Result := FormatFloat('#,###0.0000',(Money/10000));
   Result := FormatFloat('#,###0.0000',(Money/10000));
@@ -1471,47 +1476,80 @@ begin
   Result := TBaseType.To32Bytes( TCrypto.DoSha256(AKey) );
   Result := TBaseType.To32Bytes( TCrypto.DoSha256(AKey) );
 end;
 end;
 
 
-class function TAccountComp.IsAccountForSale(const accountInfo: TAccountInfo): Boolean;
+class function TAccountComp.IsAccountForSale(const AAccountInfo: TAccountInfo; ACurrentBlock : Integer): Boolean;
+begin
+  Result := IsAccountForPrivateSale(AAccountInfo, ACurrentBlock) OR IsAccountForPublicSale(AAccountInfo);
+end;
+
+class function TAccountComp.IsAccountForPrivateSale(const AAccountInfo: TAccountInfo; ACurrentBlock : Integer): Boolean;
+begin
+  Result := (AAccountInfo.state in [as_ForSale]) AND (NOT IsNullAccountKey(AAccountInfo.accountKey)) AND (AAccountInfo.locked_until_block >= ACurrentBlock);
+end;
+
+class function TAccountComp.IsAccountForPublicSale(const AAccountInfo: TAccountInfo): Boolean;
+begin
+  Result := (AAccountInfo.state in [as_ForSale]) AND IsNullAccountKey(AAccountInfo.accountKey);
+end;
+
+class function TAccountComp.IsAccountForSwap(const AAccountInfo: TAccountInfo; ACurrentBlock : Integer): Boolean;
 begin
 begin
-  Result := AccountInfo.state in [as_ForSale];
+  Result := IsAccountForAccountSwap(AAccountInfo, ACurrentBlock) OR IsAccountForCoinSwap(AAccountInfo, ACurrentBlock);
 end;
 end;
 
 
-class function TAccountComp.IsAccountForSwap(const accountInfo: TAccountInfo): Boolean;
+class function TAccountComp.IsAccountForAccountSwap(const AAccountInfo: TAccountInfo; ACurrentBlock : Integer) : Boolean;
 begin
 begin
-  Result := AccountInfo.state in [as_ForAtomicAccountSwap, as_ForAtomicCoinSwap];
+  Result := (AAccountInfo.state in [as_ForAtomicAccountSwap]) AND (AAccountInfo.locked_until_block >= ACurrentBlock);
 end;
 end;
 
 
-class function TAccountComp.IsAccountForSaleOrSwap(const accountInfo: TAccountInfo): Boolean;
+class function TAccountComp.IsAccountForCoinSwap(const AAccountInfo: TAccountInfo; ACurrentBlock : Integer) : Boolean;
 begin
 begin
-  Result := IsAccountForSale(accountInfo) OR IsAccountForSwap(accountInfo);
+  Result := (AAccountInfo.state in [ as_ForAtomicCoinSwap]) AND (AAccountInfo.locked_until_block >= ACurrentBlock);
 end;
 end;
 
 
-class function TAccountComp.IsAccountForSaleAcceptingTransactions(const AAccountInfo: TAccountInfo): Boolean;
-var LErrors : String;
+class function TAccountComp.IsAccountForSaleOrSwap(const AAccountInfo: TAccountInfo; ACurrentBlock : Integer): Boolean;
 begin
 begin
-  Result := IsAccountForSale(AAccountInfo) AND IsValidAccountKey(AAccountInfo.new_publicKey, LErrors);
+  Result := IsAccountForSale(AAccountInfo, ACurrentBlock) OR IsAccountForSwap(AAccountInfo, ACurrentBlock);
 end;
 end;
 
 
-class function TAccountComp.IsAccountForSaleOrSwapAcceptingTransactions(const account: TAccount; const APayload : TRawBytes): Boolean;
+class function TAccountComp.IsAccountForSaleOrSwapAcceptingTransactions(const AAccount: TAccount; ACurrentBlock : Integer; const APayload : TRawBytes): Boolean;
 var errors : String;
 var errors : String;
 begin
 begin
   Result := False;
   Result := False;
-  if Not IsAccountForSaleOrSwap(account.accountInfo) then
+  if Not IsAccountForSaleOrSwap(AAccount.accountInfo, ACurrentBlock) then
     exit;
     exit;
 
 
-  if (account.accountInfo.state in [as_ForSale, as_ForAtomicAccountSwap]) then
-    if NOT IsValidAccountKey(account.accountInfo.new_publicKey,errors) then
+  if (AAccount.accountInfo.state in [as_ForSale, as_ForAtomicAccountSwap]) then
+    if NOT IsValidAccountKey(AAccount.accountInfo.new_publicKey,errors) then
       exit;
       exit;
 
 
-   if (account.accountInfo.state in [as_ForAtomicAccountSwap, as_ForAtomicCoinSwap]) then
-     if NOT IsValidAccountHashLockKey(account, APayload) then
+   if (AAccount.accountInfo.state in [as_ForAtomicAccountSwap, as_ForAtomicCoinSwap]) then
+     if NOT IsValidAccountHashLockKey(AAccount, APayload) then
        exit;
        exit;
   Result := True;
   Result := True;
 end;
 end;
 
 
+Class Function TAccountComp.IsOperationRecipientSignable(const ASender, ATarget : TAccount; AIncomingFunds : UInt64; ACurrentBlock : Integer ) : Boolean;
+begin
+  // V5 - Allow recipient-signed operations under following conditions:
+  //  - Sender Account = Target Account
+  //  - Target Account is time-locked to new-owner-key R and time-lock is active
+  //  - (Target.Balance + Operation.Quantity) >= Target.SalePrice
+  //  - Signed by new-owner-key R
+  //
+  //  This allows following use-cases:
+  //  - Private account sale where buyer does not have existing account to initiate transaction
+  //  - Atomic account swap where counterparty does not have existing account to initiate transaction
+  Result := (ASender.account = ATarget.account) AND
+            TAccountComp.IsAccountLocked(ATarget.accountInfo, ACurrentBlock) AND
+           ((ATarget.balance + AIncomingFunds) >= (ATarget.accountInfo.price));
+
+ // Note: this does not validate recipient signature, only determines if
+ // it is recipient signable
+end;
+
 class function TAccountComp.IsAccountLocked(const AccountInfo: TAccountInfo; blocks_count: Cardinal): Boolean;
 class function TAccountComp.IsAccountLocked(const AccountInfo: TAccountInfo; blocks_count: Cardinal): Boolean;
 begin
 begin
-  Result := IsAccountForSaleOrSwap(accountInfo) And ((AccountInfo.locked_until_block)>=blocks_count);
+  Result := IsAccountForSaleOrSwap(accountInfo, blocks_count) And ((AccountInfo.locked_until_block)>=blocks_count);
 end;
 end;
 
 
 class procedure TAccountComp.SaveTOperationBlockToStream(const stream: TStream; const operationBlock: TOperationBlock);
 class procedure TAccountComp.SaveTOperationBlockToStream(const stream: TStream; const operationBlock: TOperationBlock);
@@ -1559,20 +1597,20 @@ begin
       Account.account_data.ToHexaString,Account.account_seal.ToHexaString ]);
       Account.account_data.ToHexaString,Account.account_seal.ToHexaString ]);
 end;
 end;
 
 
-class function TAccountComp.IsValidAccountInfo(const accountInfo: TAccountInfo; var errors: String): Boolean;
+class function TAccountComp.IsValidAccountInfo(const AAccountInfo: TAccountInfo; var errors: String): Boolean;
 Var s : String;
 Var s : String;
 begin
 begin
   errors := '';
   errors := '';
-  case accountInfo.state of
+  case AAccountInfo.state of
     as_Unknown: begin
     as_Unknown: begin
         errors := 'Account state is unknown';
         errors := 'Account state is unknown';
         Result := false;
         Result := false;
       end;
       end;
     as_Normal: begin
     as_Normal: begin
-        Result := IsValidAccountKey(accountInfo.accountKey,errors);
+        Result := IsValidAccountKey(AAccountInfo.accountKey,errors);
       end;
       end;
     as_ForSale: begin
     as_ForSale: begin
-        If Not IsValidAccountKey(accountInfo.accountKey,s) then errors := errors +' '+s;
+        If Not IsValidAccountKey(AAccountInfo.accountKey,s) then errors := errors +' '+s;
         Result := errors='';
         Result := errors='';
       end;
       end;
   else
   else
@@ -1580,19 +1618,57 @@ begin
   end;
   end;
 end;
 end;
 
 
-class function TAccountComp.IsValidAccountKey(const account: TAccountKey; var errors : String): Boolean;
+class function TAccountComp.IsValidAccountKey(const AAccountInfo: TAccountKey; var errors : String): Boolean;
 begin
 begin
   errors := '';
   errors := '';
-  case account.EC_OpenSSL_NID of
+  case AAccountInfo.EC_OpenSSL_NID of
     CT_NID_secp256k1,CT_NID_secp384r1,CT_NID_sect283k1,CT_NID_secp521r1 : begin
     CT_NID_secp256k1,CT_NID_secp384r1,CT_NID_sect283k1,CT_NID_secp521r1 : begin
-      Result := TECPrivateKey.IsValidPublicKey(account,errors);
+      Result := TECPrivateKey.IsValidPublicKey(AAccountInfo,errors);
     end;
     end;
   else
   else
-    errors := Format('Invalid AccountKey type:%d (Unknown type) - Length x:%d y:%d',[account.EC_OpenSSL_NID,length(account.x),length(account.y)]);
+    errors := Format('Invalid AccountKey type:%d (Unknown type) - Length x:%d y:%d',[AAccountInfo.EC_OpenSSL_NID,length(AAccountInfo.x),length(AAccountInfo.y)]);
     Result := False;
     Result := False;
   end;
   end;
 end;
 end;
 
 
+class function TAccountComp.IsNullAccountKey(const AAccountInfo : TAccountKey) : Boolean;
+begin
+  Result := AAccountInfo.EC_OpenSSL_NID = CT_TECDSA_Public_Nul.EC_OpenSSL_NID;
+end;
+
+class function TAccountComp.IsValidNewAccountKey(const AAccountInfo : TAccountInfo; const ANewKey : TAccountKey; AProtocolVersion : Integer; ACurrentBlock : Integer) : Boolean;
+begin
+  Result :=False;
+  if AProtocolVersion <= CT_PROTOCOL_5 then begin
+    // V2 - V4 Rules
+    // - Private Sale: non-null and must match stored new-key in account_info
+    // - Public Sale: non-null
+    // - Else: non-null  (used for change key)
+    if IsAccountForPrivateSale(AAccountInfo, ACurrentBlock) then
+       Result := (NOT IsNullAccountKey(ANewKey)) AND TAccountComp.EqualAccountKeys(ANewKey, AAccountInfo.new_publicKey)
+    else if IsAccountForPublicSale(AAccountInfo) then
+       Result := NOT IsNullAccountKey(ANewKey)
+    else
+       Result := NOT IsNullAccountKey(ANewKey);
+  end else begin
+    // V5 New Key Rules:
+    // - Private Sale, Atomic Account Swap: new key must match specified new-key in account_info
+    // - Public Sale: non null
+    // - Atomic Coin Swap: new key must equal existing account key, ignoring stored new-key if any
+    // - else: non-null
+    if TAccountComp.IsAccountForPrivateSale(AAccountInfo, ACurrentBlock) then
+      Result := TAccountComp.EqualAccountKeys(ANewKey, AAccountInfo.new_publicKey)
+    else if TAccountComp.IsAccountForPublicSale(AAccountInfo) then
+      Result := NOT IsNullAccountKey(ANewKey)
+    else if TAccountComp.IsAccountForAccountSwap(AAccountInfo, ACurrentBlock) then
+      Result := TAccountComp.EqualAccountKeys(ANewKey, AAccountInfo.new_publicKey)
+    else if TAccountComp.IsAccountForCoinSwap(AAccountInfo, ACurrentBlock) then
+      Result := TAccountComp.EqualAccountKeys(ANewKey, AAccountInfo.accountKey)
+    else
+      Result := NOT IsNullAccountKey(ANewKey);
+  end;
+end;
+
 class function TAccountComp.PrivateToAccountkey(key: TECPrivateKey): TAccountKey;
 class function TAccountComp.PrivateToAccountkey(key: TECPrivateKey): TAccountKey;
 begin
 begin
   Result := key.PublicKey;
   Result := key.PublicKey;
@@ -4040,7 +4116,7 @@ begin
   end;
   end;
 end;
 end;
 
 
-function TPCSafeBoxTransaction.BuyAccount(APrevious : TAccountPreviousBlockInfo; const AOpID : TRawBytes;  ABuyer, AAccountToBuy, ASeller: Cardinal; ANOperation: Cardinal; AAmount, AAccountPrice, AFee: UInt64;  const ANewAccountKey: TAccountKey; const AHashLockKey : TRawBytes; var AErrors: String): Boolean;
+function TPCSafeBoxTransaction.BuyAccount(APrevious : TAccountPreviousBlockInfo; const AOpID : TRawBytes;  ABuyer, AAccountToBuy, ASeller: Cardinal; ANOperation: Cardinal; AAmount, AAccountPrice, AFee: UInt64;  const ANewAccountKey: TAccountKey; const AHashLockKey : TRawBytes; ARecipientSigned : Boolean; var AErrors: String): Boolean;
 var
 var
   LPBuyerAccount, LPAccountToBuy, LPSellerAccount : PAccount;
   LPBuyerAccount, LPAccountToBuy, LPSellerAccount : PAccount;
   LPBuyerAccount_Sealed, LPAccountToBuy_Sealed, LPSellerAccount_Sealed : PSealedAccount;
   LPBuyerAccount_Sealed, LPAccountToBuy_Sealed, LPSellerAccount_Sealed : PSealedAccount;
@@ -4084,11 +4160,11 @@ begin
     AErrors := 'Max fee';
     AErrors := 'Max fee';
     Exit;
     Exit;
   end;
   end;
-  if (TAccountComp.IsAccountLocked(LPBuyerAccount^.accountInfo,Origin_BlocksCount)) then begin
+  if (TAccountComp.IsAccountLocked(LPBuyerAccount^.accountInfo,Origin_BlocksCount) AND (NOT ARecipientSigned)) then begin
     AErrors := 'Buyer account is locked until block '+Inttostr(LPBuyerAccount^.accountInfo.locked_until_block);
     AErrors := 'Buyer account is locked until block '+Inttostr(LPBuyerAccount^.accountInfo.locked_until_block);
     Exit;
     Exit;
   end;
   end;
-  If not (TAccountComp.IsAccountForSaleOrSwap(LPAccountToBuy^.accountInfo)) then begin
+  If not (TAccountComp.IsAccountForSaleOrSwap(LPAccountToBuy^.accountInfo, Origin_BlocksCount)) then begin
     AErrors := 'Account is not for sale or swap';
     AErrors := 'Account is not for sale or swap';
     Exit;
     Exit;
   end;
   end;
@@ -4105,7 +4181,7 @@ begin
       TAccountComp.FormatMoney(LPAccountToBuy^.balance)+' + amount '+TAccountComp.FormatMoney(AAmount);
       TAccountComp.FormatMoney(LPAccountToBuy^.balance)+' + amount '+TAccountComp.FormatMoney(AAmount);
     Exit;
     Exit;
   end;
   end;
-  if TAccountComp.IsAccountForSwap(LPAccountToBuy^.accountInfo) AND (NOT TAccountComp.IsValidAccountHashLockKey(LPAccountToBuy^, AHashLockKey)) then begin
+  if TAccountComp.IsAccountForSwap(LPAccountToBuy^.accountInfo, Origin_BlocksCount) AND (NOT TAccountComp.IsValidAccountHashLockKey(LPAccountToBuy^, AHashLockKey)) then begin
     AErrors := 'Account is not unlocked by supplied hash lock key';
     AErrors := 'Account is not unlocked by supplied hash lock key';
     Exit;
     Exit;
   end;
   end;
@@ -4135,10 +4211,32 @@ begin
 
 
   // Inc buyer n_operation
   // Inc buyer n_operation
   LPBuyerAccount^.n_operation := ANOperation;
   LPBuyerAccount^.n_operation := ANOperation;
-  // Set new balance values
+  // Set new balance values (with overflow checks)
+  if AAmount > (AAmount + AFee) then begin
+    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: 37E7343143614D4C8489FA9963CE8C3C';
+    exit;
+  end;
+  if LPBuyerAccount^.balance < (AAmount + AFee) then begin
+    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: F06169D9A209410AACB1AAD324B7A191';
+    exit;
+  end;
   LPBuyerAccount^.balance := LPBuyerAccount^.balance - (AAmount + AFee);
   LPBuyerAccount^.balance := LPBuyerAccount^.balance - (AAmount + AFee);
+
+  if LPAccountToBuy^.balance > (LPAccountToBuy^.balance + AAmount) then begin
+    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: 390BB1E7241B4A0BA5A2D934E67F39D3';
+    exit;
+  end;
+  if (LPAccountToBuy^.balance + AAmount) < (LPAccountToBuy^.accountInfo.price) then begin
+    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: 19D80241DA584D0B93ED30F27110B9A9';
+    exit;
+  end;
   LPAccountToBuy^.balance := LPAccountToBuy^.balance + AAmount - LPAccountToBuy^.accountInfo.price;
   LPAccountToBuy^.balance := LPAccountToBuy^.balance + AAmount - LPAccountToBuy^.accountInfo.price;
-  LPSellerAccount^.balance := LPSellerAccount^.balance + LPAccountToBuy^.accountInfo.price;
+
+  if LPSellerAccount^.balance > (LPSellerAccount^.balance + LPAccountToBuy^.accountInfo.price) then begin
+    AErrors := 'Critical overflow error detected, aborting SafeBox update. Ref: 972CA85C6E4E4081ABB767F6D8019421';
+    exit;
+  end;
+    LPSellerAccount^.balance := LPSellerAccount^.balance + LPAccountToBuy^.accountInfo.price;
 
 
   // After buy, account will be unlocked and set to normal state and new account public key changed
   // After buy, account will be unlocked and set to normal state and new account public key changed
   LPAccountToBuy^.accountInfo := CT_AccountInfo_NUL;
   LPAccountToBuy^.accountInfo := CT_AccountInfo_NUL;

+ 3 - 3
src/core/UBlockChain.pas

@@ -3304,20 +3304,20 @@ begin
       OperationResume.DestAccount:=TOpBuyAccount(Operation).Data.target;
       OperationResume.DestAccount:=TOpBuyAccount(Operation).Data.target;
       if TOpBuyAccount(Operation).Data.sender=Affected_account_number then begin
       if TOpBuyAccount(Operation).Data.sender=Affected_account_number then begin
         OperationResume.OpSubtype := CT_OpSubtype_BuyAccountBuyer;
         OperationResume.OpSubtype := CT_OpSubtype_BuyAccountBuyer;
-        OperationResume.OperationTxt := 'Buy account '+TAccountComp.AccountNumberToAccountTxtNumber(TOpBuyAccount(Operation).Data.target)+' for '+TAccountComp.FormatMoney(TOpBuyAccount(Operation).Data.AccountPrice)+' PASC' + ' with payload hex "' + TCrypto.ToHexaString(TOpBuyAccount(Operation).Data.Payload) +'"';
+        OperationResume.OperationTxt := 'Buy account '+TAccountComp.AccountNumberToAccountTxtNumber(TOpBuyAccount(Operation).Data.target)+' for '+TAccountComp.FormatMoney(TOpBuyAccount(Operation).Data.AccountPrice)+' PASC';
         OperationResume.Amount := Int64(TOpBuyAccount(Operation).Data.amount) * (-1);
         OperationResume.Amount := Int64(TOpBuyAccount(Operation).Data.amount) * (-1);
         Result := true;
         Result := true;
       end else if TOpBuyAccount(Operation).Data.target=Affected_account_number then begin
       end else if TOpBuyAccount(Operation).Data.target=Affected_account_number then begin
         OperationResume.OpSubtype := CT_OpSubtype_BuyAccountTarget;
         OperationResume.OpSubtype := CT_OpSubtype_BuyAccountTarget;
         OperationResume.OperationTxt := 'Purchased account '+TAccountComp.AccountNumberToAccountTxtNumber(TOpBuyAccount(Operation).Data.target)+' by '+
         OperationResume.OperationTxt := 'Purchased account '+TAccountComp.AccountNumberToAccountTxtNumber(TOpBuyAccount(Operation).Data.target)+' by '+
-          TAccountComp.AccountNumberToAccountTxtNumber(TOpBuyAccount(Operation).Data.sender)+' for '+TAccountComp.FormatMoney(TOpBuyAccount(Operation).Data.AccountPrice)+' PASC' + ' with payload hex "' + TCrypto.ToHexaString(TOpBuyAccount(Operation).Data.Payload) +'"';;
+          TAccountComp.AccountNumberToAccountTxtNumber(TOpBuyAccount(Operation).Data.sender)+' for '+TAccountComp.FormatMoney(TOpBuyAccount(Operation).Data.AccountPrice)+' PASC';
         OperationResume.Amount := Int64(TOpBuyAccount(Operation).Data.amount) - Int64(TOpBuyAccount(Operation).Data.AccountPrice);
         OperationResume.Amount := Int64(TOpBuyAccount(Operation).Data.amount) - Int64(TOpBuyAccount(Operation).Data.AccountPrice);
         OperationResume.Fee := 0;
         OperationResume.Fee := 0;
         Result := true;
         Result := true;
       end else if TOpBuyAccount(Operation).Data.SellerAccount=Affected_account_number then begin
       end else if TOpBuyAccount(Operation).Data.SellerAccount=Affected_account_number then begin
         OperationResume.OpSubtype := CT_OpSubtype_BuyAccountSeller;
         OperationResume.OpSubtype := CT_OpSubtype_BuyAccountSeller;
         OperationResume.OperationTxt := 'Sold account '+TAccountComp.AccountNumberToAccountTxtNumber(TOpBuyAccount(Operation).Data.target)+' by '+
         OperationResume.OperationTxt := 'Sold account '+TAccountComp.AccountNumberToAccountTxtNumber(TOpBuyAccount(Operation).Data.target)+' by '+
-          TAccountComp.AccountNumberToAccountTxtNumber(TOpBuyAccount(Operation).Data.sender)+' for '+TAccountComp.FormatMoney(TOpBuyAccount(Operation).Data.AccountPrice)+' PASC' + ' with payload hex "' + TCrypto.ToHexaString(TOpBuyAccount(Operation).Data.Payload) +'"';;
+          TAccountComp.AccountNumberToAccountTxtNumber(TOpBuyAccount(Operation).Data.sender)+' for '+TAccountComp.FormatMoney(TOpBuyAccount(Operation).Data.AccountPrice)+' PASC';
         OperationResume.Amount := TOpBuyAccount(Operation).Data.AccountPrice;
         OperationResume.Amount := TOpBuyAccount(Operation).Data.AccountPrice;
         OperationResume.Fee := 0;
         OperationResume.Fee := 0;
         Result := true;
         Result := true;

+ 64 - 46
src/core/UOpTransaction.pas

@@ -758,9 +758,13 @@ function TOpTransaction.DoOperation(APrevious : TAccountPreviousBlockInfo; ASafe
 Var s_new, t_new : Int64;
 Var s_new, t_new : Int64;
   LTotalAmount : Cardinal;
   LTotalAmount : Cardinal;
   LSender,LTarget,LSeller : TAccount;
   LSender,LTarget,LSeller : TAccount;
+  LRecipientSignable, LIsSwap : Boolean;
+  LCurrentBlock, LCurrentProtocol : Integer;
 begin
 begin
   Result := false;
   Result := false;
   AErrors := '';
   AErrors := '';
+  LCurrentBlock := ASafeBoxTransaction.FreezedSafeBox.BlocksCount;
+  LCurrentProtocol := ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol;
 
 
   {$region 'Common Validation'}
   {$region 'Common Validation'}
 
 
@@ -768,27 +772,14 @@ begin
     AErrors := Format('Invalid sender %d',[FData.sender]);
     AErrors := Format('Invalid sender %d',[FData.sender]);
     Exit;
     Exit;
   end;
   end;
-
-  if (FData.target>=ASafeBoxTransaction.FreezedSafeBox.AccountsCount) then begin
-    AErrors := Format('Invalid target %d',[FData.target]);
-    Exit;
-  end;
-  if (FData.sender=FData.target) then begin
-    AErrors := Format('Sender=Target %d',[FData.sender]);
-    Exit;
-  end;
-  if TAccountComp.IsAccountBlockedByProtocol(FData.sender,ASafeBoxTransaction.FreezedSafeBox.BlocksCount) then begin
+  if TAccountComp.IsAccountBlockedByProtocol(FData.sender,LCurrentBlock) then begin
     AErrors := Format('sender (%d) is blocked for protocol',[FData.sender]);
     AErrors := Format('sender (%d) is blocked for protocol',[FData.sender]);
     Exit;
     Exit;
   end;
   end;
-  if TAccountComp.IsAccountBlockedByProtocol(FData.target,ASafeBoxTransaction.FreezedSafeBox.BlocksCount) then begin
+  if TAccountComp.IsAccountBlockedByProtocol(FData.target,LCurrentBlock) then begin
     AErrors := Format('target (%d) is blocked for protocol',[FData.target]);
     AErrors := Format('target (%d) is blocked for protocol',[FData.target]);
     Exit;
     Exit;
   end;
   end;
-  if (FData.amount<=0) Or (FData.amount>CT_MaxTransactionAmount) then begin
-    AErrors := Format('Invalid amount %d (0 or max: %d)',[FData.amount,CT_MaxTransactionAmount]);
-    Exit;
-  end;
   if (FData.fee<0) Or (FData.fee>CT_MaxTransactionFee) then begin
   if (FData.fee<0) Or (FData.fee>CT_MaxTransactionFee) then begin
     AErrors := Format('Invalid fee %d (max %d)',[FData.fee,CT_MaxTransactionFee]);
     AErrors := Format('Invalid fee %d (max %d)',[FData.fee,CT_MaxTransactionFee]);
     Exit;
     Exit;
@@ -802,6 +793,30 @@ begin
 
 
   LSender := ASafeBoxTransaction.Account(FData.sender);
   LSender := ASafeBoxTransaction.Account(FData.sender);
   LTarget := ASafeBoxTransaction.Account(FData.target);
   LTarget := ASafeBoxTransaction.Account(FData.target);
+
+  if (FData.target>=ASafeBoxTransaction.FreezedSafeBox.AccountsCount) then begin
+    AErrors := Format('Invalid target %d',[FData.target]);
+    Exit;
+  end;
+
+  // V5 - Allow recipient-signed transactions. This is defined as
+  //  - Sender Account = Target Account
+  LRecipientSignable := TAccountComp.IsOperationRecipientSignable(LSender, LTarget, FData.Amount, LCurrentBlock);
+  LIsSwap := TAccountComp.IsAccountForCoinSwap(LTarget.accountInfo, LCurrentBlock);
+
+  if (FData.sender=FData.target) AND (NOT LRecipientSignable) then begin
+    AErrors := Format('Sender=Target and Target is not recipient-signable. Account: %d',[FData.sender]);
+    Exit;
+  end;
+  if (LRecipientSignable Or LIsSwap) then begin
+    IF ((FData.amount < 0) or (FData.amount>CT_MaxTransactionAmount)) then begin
+      AErrors := Format('Recipient-signed transaction had invalid amount %d. Must be within 0 or %d.)',[FData.amount,CT_MaxTransactionAmount]);
+      Exit;
+    end
+  end else if((FData.amount<=0) Or (FData.amount>CT_MaxTransactionAmount)) then begin
+    AErrors := Format('Invalid amount %d (1 or max: %d)',[FData.amount,CT_MaxTransactionAmount]);
+    Exit;
+  end;
   if ((LSender.n_operation+1)<>FData.n_operation) then begin
   if ((LSender.n_operation+1)<>FData.n_operation) then begin
     AErrors := Format('Invalid n_operation %d (expected %d)',[FData.n_operation,LSender.n_operation+1]);
     AErrors := Format('Invalid n_operation %d (expected %d)',[FData.n_operation,LSender.n_operation+1]);
     Exit;
     Exit;
@@ -816,12 +831,12 @@ begin
     Exit;
     Exit;
   end;
   end;
   // Is locked? Protocol 2 check
   // Is locked? Protocol 2 check
-  if (TAccountComp.IsAccountLocked(LSender.accountInfo,ASafeBoxTransaction.FreezedSafeBox.BlocksCount)) then begin
+  if (NOT LRecipientSignable) AND (TAccountComp.IsAccountLocked(LSender.accountInfo,LCurrentBlock)) then begin
     AErrors := 'Sender Account is currently locked';
     AErrors := 'Sender Account is currently locked';
     exit;
     exit;
   end;
   end;
   // Build 1.4
   // Build 1.4
-  If (FData.public_key.EC_OpenSSL_NID<>CT_TECDSA_Public_Nul.EC_OpenSSL_NID) And (Not TAccountComp.EqualAccountKeys(FData.public_key,LSender.accountInfo.accountkey)) then begin
+  If (NOT TAccountComp.IsNullAccountKey(FData.public_key)) And (NOT TAccountComp.EqualAccountKeys(FData.public_key,LSender.accountInfo.accountkey)) then begin
     AErrors := Format('Invalid sender public key for account %d. Distinct from SafeBox public key! %s <> %s',[
     AErrors := Format('Invalid sender public key for account %d. Distinct from SafeBox public key! %s <> %s',[
       FData.sender,
       FData.sender,
       TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(FData.public_key)),
       TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(FData.public_key)),
@@ -829,7 +844,10 @@ begin
     exit;
     exit;
   end;
   end;
   // Check signature
   // Check signature
-  If Not IsValidECDSASignature(LSender.accountInfo.accountkey, ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol, FData.sign) then begin
+  if LRecipientSignable AND (NOT IsValidECDSASignature(LSender.accountInfo.new_publicKey, LCurrentProtocol, FData.sign)) then begin
+    AErrors := 'Invalid recipient-signed ECDSA signature';
+    Exit;
+  end else If (NOT LRecipientSignable) AND (NOT IsValidECDSASignature(LSender.accountInfo.accountkey, ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol, FData.sign)) then begin
     AErrors := 'Invalid ECDSA signature';
     AErrors := 'Invalid ECDSA signature';
     Exit;
     Exit;
   end;
   end;
@@ -847,13 +865,13 @@ begin
       exit;
       exit;
     end;
     end;
 
 
-    if (TAccountComp.IsAccountForSwap(LTarget.accountInfo) AND (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_5)) then begin
+    if (TAccountComp.IsAccountForSwap(LTarget.accountInfo, LCurrentBlock) AND (LCurrentProtocol<CT_PROTOCOL_5)) then begin
       AErrors := 'Atomic swaps are not allowed until Protocol 5';
       AErrors := 'Atomic swaps are not allowed until Protocol 5';
       exit;
       exit;
     end;
     end;
 
 
     LSeller := ASafeBoxTransaction.Account(FData.SellerAccount);
     LSeller := ASafeBoxTransaction.Account(FData.SellerAccount);
-    if Not TAccountComp.IsAccountForSaleOrSwap(LTarget.accountInfo) then begin
+    if Not TAccountComp.IsAccountForSaleOrSwap(LTarget.accountInfo, LCurrentBlock) then begin
       AErrors := Format('%d is not for sale or swap',[LTarget.account]);
       AErrors := Format('%d is not for sale or swap',[LTarget.account]);
       exit;
       exit;
     end;
     end;
@@ -862,23 +880,24 @@ begin
       AErrors := Format('Seller account %d is not expected account %d',[FData.SellerAccount,LTarget.accountInfo.account_to_pay]);
       AErrors := Format('Seller account %d is not expected account %d',[FData.SellerAccount,LTarget.accountInfo.account_to_pay]);
       exit;
       exit;
     end;
     end;
-    if (LTarget.balance + FData.amount < LTarget.accountInfo.price) then begin
-      AErrors := Format('Account %d balance (%d) + amount (%d) < price (%d)',[LTarget.account,LTarget.balance,FData.amount,LTarget.accountInfo.price]);
+    LTotalAmount := LTarget.accountInfo.price;
+    if LRecipientSignable then
+      LTotalAmount := LTotalAmount + FData.fee;
+    
+    if (LTarget.balance + FData.amount) < LTotalAmount then begin
+      AErrors := Format('Account %d balance (%d) + amount (%d) < price (%d)',[LTarget.account,LTarget.balance,FData.amount,LTotalAmount]);
       exit;
       exit;
     end;
     end;
     if (FData.AccountPrice<>LTarget.accountInfo.price) then begin
     if (FData.AccountPrice<>LTarget.accountInfo.price) then begin
       AErrors := Format('Signed price (%d) is not the same of account price (%d)',[FData.AccountPrice,LTarget.accountInfo.price]);
       AErrors := Format('Signed price (%d) is not the same of account price (%d)',[FData.AccountPrice,LTarget.accountInfo.price]);
       exit;
       exit;
     end;
     end;
-
-    if (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol>=CT_PROTOCOL_5) then begin
-      if (TAccountComp.IsAccountForSaleAcceptingTransactions(LTarget.accountInfo)) and
-        (Not TAccountComp.EqualAccountKeys(FData.new_accountkey,LTarget.accountInfo.new_publicKey))  then begin
-          AErrors := Format('Provided new public key for %d is not the same than stored in a private sale: %s <> %s',[LTarget.account,
-            TAccountComp.AccountKey2RawString(LTarget.accountInfo.new_publicKey).ToHexaString,
-            TAccountComp.AccountKey2RawString(FData.new_accountkey).ToHexaString]);
-          exit;
-        end;
+    if NOT TAccountComp.IsValidNewAccountKey(LTarget.accountInfo, FData.new_accountkey, LCurrentProtocol, LCurrentBlock) then begin
+      AErrors := Format('Specified new public key for %d does not equal (or is not valid) the new public key stored in account: %s <> %s',
+      [LTarget.account,
+       TAccountComp.AccountKey2RawString(LTarget.accountInfo.new_publicKey).ToHexaString,
+        TAccountComp.AccountKey2RawString(FData.new_accountkey).ToHexaString]);
+      exit;
     end;
     end;
 
 
     If Not (TAccountComp.IsValidAccountKey(FData.new_accountkey,AErrors)) then exit; // BUG 20171511
     If Not (TAccountComp.IsValidAccountKey(FData.new_accountkey,AErrors)) then exit; // BUG 20171511
@@ -888,20 +907,20 @@ begin
               (FData.opTransactionStyle = transaction_with_auto_buy_account) OR
               (FData.opTransactionStyle = transaction_with_auto_buy_account) OR
               (
               (
                 (FData.opTransactionStyle = transaction) AND
                 (FData.opTransactionStyle = transaction) AND
-                (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol >= CT_PROTOCOL_2) AND
-                (TAccountComp.IsAccountForSaleOrSwapAcceptingTransactions(LTarget, FData.payload)) AND
+                (LCurrentProtocol >= CT_PROTOCOL_2) AND
+                (TAccountComp.IsAccountForSaleOrSwapAcceptingTransactions(LTarget, LCurrentBlock, FData.payload)) AND
                 ((LTarget.balance + FData.amount >= LTarget.accountInfo.price))
                 ((LTarget.balance + FData.amount >= LTarget.accountInfo.price))
               )  then begin
               )  then begin
     {$region 'Transaction Auto Buy Validation'}
     {$region 'Transaction Auto Buy Validation'}
-    if (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_2) then begin
+    if (LCurrentProtocol<CT_PROTOCOL_2) then begin
       AErrors := 'Tx-Buy account is not allowed on Protocol 1';
       AErrors := 'Tx-Buy account is not allowed on Protocol 1';
       exit;
       exit;
     end;
     end;
 
 
-    if (TAccountComp.IsAccountForSwap( LTarget.accountInfo ) AND (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_5)) then begin
+    if (TAccountComp.IsAccountForSwap( LTarget.accountInfo, LCurrentBlock ) AND (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_5)) then begin
       AErrors := 'Tx-Buy atomic swaps are not allowed until Protocol 5';
       AErrors := 'Tx-Buy atomic swaps are not allowed until Protocol 5';
       exit;
       exit;
-    end else If (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_5) then begin
+    end else If (LCurrentProtocol<CT_PROTOCOL_5) then begin
       // the below line was a bug fix that introduced a new bug, and is retained here for
       // the below line was a bug fix that introduced a new bug, and is retained here for
       // V2-V4 consistency
       // V2-V4 consistency
       //------
       //------
@@ -924,12 +943,6 @@ begin
 
 
   // final atomic coin swap checks for buy account flow
   // final atomic coin swap checks for buy account flow
   if (FData.opTransactionStyle in [buy_account, transaction_with_auto_buy_account]) AND (LTarget.accountInfo.state = as_ForAtomicCoinSwap) then begin
   if (FData.opTransactionStyle in [buy_account, transaction_with_auto_buy_account]) AND (LTarget.accountInfo.state = as_ForAtomicCoinSwap) then begin
-    // Ensure that the sender's key matches the counterparty account key
-    if NOT TAccountComp.EqualAccountKeys(LSender.accountInfo.accountKey, LSeller.accountInfo.accountKey) then begin
-      AErrors := 'Senders key did not match counterparty account''s key for this atomic swap ';
-      exit;
-    end;
-
     // Ensure the key for target doesn't change
     // Ensure the key for target doesn't change
     if NOT TAccountComp.EqualAccountKeys(LTarget.accountInfo.accountKey, FData.new_accountkey) then begin
     if NOT TAccountComp.EqualAccountKeys(LTarget.accountInfo.accountKey, FData.new_accountkey) then begin
       AErrors := 'Target key cannot be changed during atomic coin swap';
       AErrors := 'Target key cannot be changed during atomic coin swap';
@@ -962,6 +975,7 @@ begin
       FData.fee,
       FData.fee,
       FData.new_accountkey,
       FData.new_accountkey,
       FData.payload,
       FData.payload,
+      LRecipientSignable,
       AErrors
       AErrors
     );
     );
 
 
@@ -1776,8 +1790,7 @@ begin
   else Result := 0;
   else Result := 0;
 end;
 end;
 
 
-function TOpListAccount.DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : String) : Boolean;
-Var
+function TOpListAccount.DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : String) : Boolean;Var
   account_signer, account_target : TAccount;
   account_signer, account_target : TAccount;
   LIsDelist, LIsSale, LIsPrivateSale, LIsPublicSale, LIsSwap, LIsAccountSwap, LIsCoinSwap : boolean;
   LIsDelist, LIsSale, LIsPrivateSale, LIsPublicSale, LIsSwap, LIsAccountSwap, LIsCoinSwap : boolean;
 begin
 begin
@@ -1906,6 +1919,11 @@ begin
       errors := 'New public key for private sale is the same public key';
       errors := 'New public key for private sale is the same public key';
       Exit;
       Exit;
     end;
     end;
+  end else if LIsCoinSwap then begin
+    if (account_target.balance < (FData.account_price + FData.fee)) then begin
+      errors := 'Not enough funds for coin swap amount and fee';
+      exit;
+    end;
   end;
   end;
 
 
    // Build 1.4
    // Build 1.4
@@ -2286,9 +2304,9 @@ begin
   Result := CT_Op_ListAccountForSale;
   Result := CT_Op_ListAccountForSale;
 end;
 end;
 
 
-function GetOpSubType : Integer;
+function TOpListAccountForSaleOrSwap.GetOpSubType : Integer;
 begin
 begin
-  case FData.accountState of
+  case FData.account_state of
     as_ForSale:
     as_ForSale:
       if (FData.new_public_key.EC_OpenSSL_NID<>0) then Exit(CT_OpSubtype_ListAccountForPrivateSale) else Exit(CT_OpSubtype_ListAccountForPublicSale);
       if (FData.new_public_key.EC_OpenSSL_NID<>0) then Exit(CT_OpSubtype_ListAccountForPrivateSale) else Exit(CT_OpSubtype_ListAccountForPublicSale);
     as_ForAtomicAccountSwap: Exit(CT_OpSubtype_ListAccountForAccountSwap);
     as_ForAtomicAccountSwap: Exit(CT_OpSubtype_ListAccountForAccountSwap);

+ 1 - 1
src/core/URPC.pas

@@ -2147,7 +2147,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
           Exit;
           Exit;
         end;
         end;
         account_to_purchase := FNode.GetMempoolAccount(c_account);
         account_to_purchase := FNode.GetMempoolAccount(c_account);
-        if Not TAccountComp.IsAccountForSale(account_to_purchase.accountInfo) then begin
+        if Not TAccountComp.IsAccountForSale(account_to_purchase.accountInfo, FNode.Bank.BlocksCount) then begin
           ErrorNum := CT_RPC_ErrNum_InvalidAccount;
           ErrorNum := CT_RPC_ErrNum_InvalidAccount;
           ErrorDesc := 'Account is not for sale: '+params.AsString('account_to_purchase','');
           ErrorDesc := 'Account is not for sale: '+params.AsString('account_to_purchase','');
           Exit;
           Exit;

+ 13 - 2
src/core/UWallet.pas

@@ -79,7 +79,8 @@ Type
     Property WalletPassword : String read FWalletPassword write SetWalletPassword;
     Property WalletPassword : String read FWalletPassword write SetWalletPassword;
     Function AddPrivateKey(Const Name : String; ECPrivateKey : TECPrivateKey) : Integer; virtual;
     Function AddPrivateKey(Const Name : String; ECPrivateKey : TECPrivateKey) : Integer; virtual;
     Function AddPublicKey(Const Name : String; ECDSA_Public : TECDSA_Public) : Integer; virtual;
     Function AddPublicKey(Const Name : String; ECDSA_Public : TECDSA_Public) : Integer; virtual;
-    Function IndexOfAccountKey(AccountKey : TAccountKey) : Integer;
+    Function IndexOfAccountKey(const AccountKey : TAccountKey) : Integer;
+    function TryGetKey(const AAccountKey : TAccountKey; out AKey : TWalletKey) : Boolean;
     Procedure Delete(index : Integer); virtual;
     Procedure Delete(index : Integer); virtual;
     Procedure Clear; virtual;
     Procedure Clear; virtual;
     Function Count : Integer;
     Function Count : Integer;
@@ -352,11 +353,21 @@ begin
   Result := PWalletKey(FSearchableKeys[index])^;
   Result := PWalletKey(FSearchableKeys[index])^;
 end;
 end;
 
 
-function TWalletKeys.IndexOfAccountKey(AccountKey: TAccountKey): Integer;
+function TWalletKeys.IndexOfAccountKey(const AccountKey: TAccountKey): Integer;
 begin
 begin
   if Not find(AccountKey,Result) then Result := -1;
   if Not find(AccountKey,Result) then Result := -1;
 end;
 end;
 
 
+function TWalletKeys.TryGetKey(const AAccountKey : TAccountKey; out AKey : TWalletKey) : Boolean;
+var
+  LIndex : Integer;
+begin
+  if NOT find(AAccountKey,LIndex)
+    then Exit(False);
+  AKey := GetKey(LIndex);
+  Result := True;
+end;
+
 procedure TWalletKeys.LoadFromStream(Stream: TStream);
 procedure TWalletKeys.LoadFromStream(Stream: TStream);
 Var fileversion,i,l,j : Integer;
 Var fileversion,i,l,j : Integer;
   raw : TRawBytes;
   raw : TRawBytes;

+ 2 - 8
src/gui-classic/UFRMAccountSelect.dfm

@@ -39,7 +39,6 @@ object FRMAccountSelect: TFRMAccountSelect
       Width = 91
       Width = 91
       Height = 36
       Height = 36
       Caption = 'Search'
       Caption = 'Search'
-      DoubleBuffered = True
       Glyph.Data = {
       Glyph.Data = {
         F6060000424DF606000000000000360000002800000018000000180000000100
         F6060000424DF606000000000000360000002800000018000000180000000100
         180000000000C0060000120B0000120B00000000000000000000FF00FFFF00FF
         180000000000C0060000120B0000120B00000000000000000000FF00FFFF00FF
@@ -97,7 +96,6 @@ object FRMAccountSelect: TFRMAccountSelect
         EED8B7AE837EAE837EFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00
         EED8B7AE837EAE837EFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00
         FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFAE837EAE
         FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFAE837EAE
         837EAE837EAE837EAE837EFF00FFFF00FFFF00FFFF00FFFF00FF}
         837EAE837EAE837EAE837EFF00FFFF00FFFF00FFFF00FFFF00FF}
-      ParentDoubleBuffered = False
       TabOrder = 10
       TabOrder = 10
       OnClick = bbSearchClick
       OnClick = bbSearchClick
     end
     end
@@ -229,9 +227,8 @@ object FRMAccountSelect: TFRMAccountSelect
       Height = 30
       Height = 30
       Anchors = [akTop, akRight]
       Anchors = [akTop, akRight]
       Caption = 'Select'
       Caption = 'Select'
-      DoubleBuffered = True
       Kind = bkOK
       Kind = bkOK
-      ParentDoubleBuffered = False
+      NumGlyphs = 2
       TabOrder = 0
       TabOrder = 0
     end
     end
     object bbCancel: TBitBtn
     object bbCancel: TBitBtn
@@ -240,9 +237,8 @@ object FRMAccountSelect: TFRMAccountSelect
       Width = 95
       Width = 95
       Height = 30
       Height = 30
       Anchors = [akTop, akRight]
       Anchors = [akTop, akRight]
-      DoubleBuffered = True
       Kind = bkCancel
       Kind = bkCancel
-      ParentDoubleBuffered = False
+      NumGlyphs = 2
       TabOrder = 1
       TabOrder = 1
     end
     end
     object bbTypeStats: TBitBtn
     object bbTypeStats: TBitBtn
@@ -251,8 +247,6 @@ object FRMAccountSelect: TFRMAccountSelect
       Width = 95
       Width = 95
       Height = 30
       Height = 30
       Caption = 'Type stats'
       Caption = 'Type stats'
-      DoubleBuffered = True
-      ParentDoubleBuffered = False
       TabOrder = 2
       TabOrder = 2
       OnClick = bbTypeStatsClick
       OnClick = bbTypeStatsClick
     end
     end

+ 8 - 4
src/gui-classic/UFRMAccountSelect.pas

@@ -160,11 +160,13 @@ procedure TSearchThread.BCExecute;
     validAccKey : Boolean;
     validAccKey : Boolean;
     errors : String;
     errors : String;
     i : Integer;
     i : Integer;
+    LBlocksCount : Integer;
   begin
   begin
     SetLength(FAccounts,0);
     SetLength(FAccounts,0);
     c := 0;
     c := 0;
     maxC := FSearchValues.SafeBox.AccountsCount-1;
     maxC := FSearchValues.SafeBox.AccountsCount-1;
     validAccKey := TAccountComp.IsValidAccountKey(FSearchValues.inAccountKey,errors);
     validAccKey := TAccountComp.IsValidAccountKey(FSearchValues.inAccountKey,errors);
+    LBlocksCount := FSearchValues.SafeBox.BlocksCount;
     while (c<=maxC) And (Not Terminated) And (Not FDoStopSearch) do begin
     while (c<=maxC) And (Not Terminated) And (Not FDoStopSearch) do begin
       account := FSearchValues.SafeBox.Account(c);
       account := FSearchValues.SafeBox.Account(c);
       isValid := True;
       isValid := True;
@@ -174,14 +176,16 @@ procedure TSearchThread.BCExecute;
         isValid := FSearchValues.inWalletKeys.IndexOfAccountKey(account.accountInfo.accountKey)>=0;
         isValid := FSearchValues.inWalletKeys.IndexOfAccountKey(account.accountInfo.accountKey)>=0;
       end;
       end;
       If isValid And (FSearchValues.onlyForSale) then begin
       If isValid And (FSearchValues.onlyForSale) then begin
-        isValid := TAccountComp.IsAccountForSale(account.accountInfo);
+        isValid := TAccountComp.IsAccountForSale(account.accountInfo, LBlocksCount);
       end;
       end;
       If IsValid and (FSearchValues.onlyForPublicSale) then begin
       If IsValid and (FSearchValues.onlyForPublicSale) then begin
-        isValid := (TAccountComp.IsAccountForSale(account.accountInfo)) And (Not TAccountComp.IsAccountForSaleAcceptingTransactions(account.accountInfo));
+        isValid := TAccountComp.IsAccountForPublicSale(account.accountInfo);
       end;
       end;
       If IsValid and (FSearchValues.onlyForPrivateSaleToMe) then begin
       If IsValid and (FSearchValues.onlyForPrivateSaleToMe) then begin
-        isValid := (TAccountComp.IsAccountForSaleAcceptingTransactions(account.accountInfo)) And
-          (Assigned(FSearchValues.inWalletKeys)) And (FSearchValues.inWalletKeys.IndexOfAccountKey(account.accountInfo.new_publicKey)>=0);
+        isValid := ((TAccountComp.IsAccountForPrivateSale(account.accountInfo, LBlocksCount) OR
+                    TAccountComp.IsAccountForAccountSwap(account.accountInfo, LBlocksCount)) AND
+                    (Assigned(FSearchValues.inWalletKeys)) And (FSearchValues.inWalletKeys.IndexOfAccountKey(account.accountInfo.new_publicKey)>=0)) OR
+                    (True {TODO: TAccountComp.IsAccountForCoinSwap(account.accountInfo, LBlocksCount) AND account.accountInfo.account_to_pay in [MyListOfAccounts]});
       end;
       end;
       If IsValid then begin
       If IsValid then begin
         IsValid := (account.balance>=FSearchValues.minBal) And ((FSearchValues.maxBal<0) Or (account.balance<=FSearchValues.maxBal));
         IsValid := (account.balance>=FSearchValues.minBal) And ((FSearchValues.maxBal<0) Or (account.balance<=FSearchValues.maxBal));

+ 23 - 1
src/gui-classic/UFRMOperation.dfm

@@ -105,7 +105,6 @@ object FRMOperation: TFRMOperation
     TabOrder = 1
     TabOrder = 1
     object tsOperation: TTabSheet
     object tsOperation: TTabSheet
       TabVisible = False
       TabVisible = False
-      ExplicitHeight = 373
       object lblFee: TLabel
       object lblFee: TLabel
         Left = 15
         Left = 15
         Top = 220
         Top = 220
@@ -301,6 +300,10 @@ object FRMOperation: TFRMOperation
         OnChange = PageControlOpTypeChange
         OnChange = PageControlOpTypeChange
         object tsTransaction: TTabSheet
         object tsTransaction: TTabSheet
           Caption = 'Transaction'
           Caption = 'Transaction'
+          ExplicitLeft = 0
+          ExplicitTop = 0
+          ExplicitWidth = 0
+          ExplicitHeight = 0
           object lblDestAccount: TLabel
           object lblDestAccount: TLabel
             Left = 13
             Left = 13
             Top = 32
             Top = 32
@@ -387,6 +390,10 @@ object FRMOperation: TFRMOperation
         object tsChangePrivateKey: TTabSheet
         object tsChangePrivateKey: TTabSheet
           Caption = 'Change Key'
           Caption = 'Change Key'
           ImageIndex = 1
           ImageIndex = 1
+          ExplicitLeft = 0
+          ExplicitTop = 0
+          ExplicitWidth = 0
+          ExplicitHeight = 0
           object lblNewPrivateKey: TLabel
           object lblNewPrivateKey: TLabel
             Left = 57
             Left = 57
             Top = 40
             Top = 40
@@ -770,6 +777,10 @@ object FRMOperation: TFRMOperation
         object tsDelistAccount: TTabSheet
         object tsDelistAccount: TTabSheet
           Caption = 'Delist Account'
           Caption = 'Delist Account'
           ImageIndex = 3
           ImageIndex = 3
+          ExplicitLeft = 0
+          ExplicitTop = 0
+          ExplicitWidth = 0
+          ExplicitHeight = 0
           object lblDelistErrors: TLabel
           object lblDelistErrors: TLabel
             Left = 13
             Left = 13
             Top = 10
             Top = 10
@@ -790,6 +801,10 @@ object FRMOperation: TFRMOperation
         object tsBuyAccount: TTabSheet
         object tsBuyAccount: TTabSheet
           Caption = 'Buy Account'
           Caption = 'Buy Account'
           ImageIndex = 4
           ImageIndex = 4
+          ExplicitLeft = 0
+          ExplicitTop = 0
+          ExplicitWidth = 0
+          ExplicitHeight = 0
           object lblAccountToBuy: TLabel
           object lblAccountToBuy: TLabel
             Left = 13
             Left = 13
             Top = 32
             Top = 32
@@ -965,6 +980,10 @@ object FRMOperation: TFRMOperation
         end
         end
         object tsChangeInfo: TTabSheet
         object tsChangeInfo: TTabSheet
           Caption = 'Change Info'
           Caption = 'Change Info'
+          ExplicitLeft = 0
+          ExplicitTop = 0
+          ExplicitWidth = 0
+          ExplicitHeight = 0
           object lblChangeInfoErrors: TLabel
           object lblChangeInfoErrors: TLabel
             Left = 13
             Left = 13
             Top = 10
             Top = 10
@@ -1030,6 +1049,9 @@ object FRMOperation: TFRMOperation
     object tsGlobalError: TTabSheet
     object tsGlobalError: TTabSheet
       ImageIndex = 1
       ImageIndex = 1
       TabVisible = False
       TabVisible = False
+      ExplicitLeft = 0
+      ExplicitTop = 0
+      ExplicitWidth = 0
       ExplicitHeight = 373
       ExplicitHeight = 373
       object lblGlobalErrors: TLabel
       object lblGlobalErrors: TLabel
         Left = 40
         Left = 40

+ 32 - 28
src/gui-classic/UFRMOperation.pas

@@ -168,7 +168,7 @@ type
     Function UpdateOpChangeKey(Const TargetAccount : TAccount; var SignerAccount : TAccount; var NewPublicKey : TAccountKey; var errors : String) : Boolean;
     Function UpdateOpChangeKey(Const TargetAccount : TAccount; var SignerAccount : TAccount; var NewPublicKey : TAccountKey; var errors : String) : Boolean;
     Function UpdateOpListAccount(Const TargetAccount : TAccount; var SalePrice : Int64; var SellerAccount,SignerAccount : TAccount; var NewOwnerPublicKey : TAccountKey; var LockedUntilBlock : Cardinal; var HashLock : T32Bytes; var errors : String) : Boolean;
     Function UpdateOpListAccount(Const TargetAccount : TAccount; var SalePrice : Int64; var SellerAccount,SignerAccount : TAccount; var NewOwnerPublicKey : TAccountKey; var LockedUntilBlock : Cardinal; var HashLock : T32Bytes; var errors : String) : Boolean;
     Function UpdateOpDelist(Const TargetAccount : TAccount; var SignerAccount : TAccount; var errors : String) : Boolean;
     Function UpdateOpDelist(Const TargetAccount : TAccount; var SignerAccount : TAccount; var errors : String) : Boolean;
-    Function UpdateOpBuyAccount(Const SenderAccount : TAccount; var AccountToBuy : TAccount; var amount : Int64; var NewPublicKey : TAccountKey; var errors : String) : Boolean;
+    Function UpdateOpBuyAccount(Const SenderAccount : TAccount; var AccountToBuy : TAccount; var amount : Int64; var NewPublicKey : TAccountKey; var ARecipientSigned : Boolean; var errors : String) : Boolean;
     Function UpdateOpChangeInfo(Const TargetAccount : TAccount; var SignerAccount : TAccount; var changeName : Boolean; var newName : TRawBytes; var changeType : Boolean; var newType : Word; var errors : String) : Boolean;
     Function UpdateOpChangeInfo(Const TargetAccount : TAccount; var SignerAccount : TAccount; var changeName : Boolean; var newName : TRawBytes; var changeType : Boolean; var newType : Word; var errors : String) : Boolean;
     procedure SetDefaultFee(const Value: Int64);
     procedure SetDefaultFee(const Value: Int64);
     Procedure OnSenderAccountsChanged(Sender : TObject);
     Procedure OnSenderAccountsChanged(Sender : TObject);
@@ -203,7 +203,7 @@ procedure TFRMOperation.actExecuteExecute(Sender: TObject);
 Var errors : String;
 Var errors : String;
   P : PAccount;
   P : PAccount;
   i,iAcc : Integer;
   i,iAcc : Integer;
-  wk : TWalletKey;
+  LKey : TWalletKey;
   ops : TOperationsHashTree;
   ops : TOperationsHashTree;
   op : TPCOperation;
   op : TPCOperation;
   account,signerAccount,destAccount,accountToBuy : TAccount;
   account,signerAccount,destAccount,accountToBuy : TAccount;
@@ -215,7 +215,7 @@ Var errors : String;
   LHashLock : T32Bytes;
   LHashLock : T32Bytes;
   _newName : TRawBytes;
   _newName : TRawBytes;
   _newType : Word;
   _newType : Word;
-  _changeName, _changeType, _V2, _executeSigner  : Boolean;
+  _changeName, _changeType, _V2, _executeSigner, LRecipientSigned : Boolean;
   _senderAccounts : TCardinalsArray;
   _senderAccounts : TCardinalsArray;
 label loop_start;
 label loop_start;
 begin
 begin
@@ -241,12 +241,8 @@ loop_start:
       account := FNode.GetMempoolAccount(_senderAccounts[iAcc]);
       account := FNode.GetMempoolAccount(_senderAccounts[iAcc]);
       If Not UpdatePayload(account, errors) then
       If Not UpdatePayload(account, errors) then
         raise Exception.Create('Error encoding payload of sender account '+TAccountComp.AccountNumberToAccountTxtNumber(account.account)+': '+errors);
         raise Exception.Create('Error encoding payload of sender account '+TAccountComp.AccountNumberToAccountTxtNumber(account.account)+': '+errors);
-      i := WalletKeys.IndexOfAccountKey(account.accountInfo.accountKey);
-      if i<0 then begin
+      if NOT WalletKeys.TryGetKey(account.accountInfo.accountKey, LKey) then
         Raise Exception.Create('Sender account private key not found in Wallet');
         Raise Exception.Create('Sender account private key not found in Wallet');
-      end;
-
-      wk := WalletKeys.Key[i];
       dooperation := true;
       dooperation := true;
       // Default fee
       // Default fee
       if account.balance > uint64(DefaultFee) then _fee := DefaultFee else _fee := account.balance;
       if account.balance > uint64(DefaultFee) then _fee := DefaultFee else _fee := account.balance;
@@ -267,7 +263,7 @@ loop_start:
         end else begin
         end else begin
         end;
         end;
         if dooperation then begin
         if dooperation then begin
-          op := TOpTransaction.CreateTransaction(FNode.Bank.Safebox.CurrentProtocol,account.account,account.n_operation+1,destAccount.account,wk.PrivateKey,_amount,_fee,FEncodedPayload);
+          op := TOpTransaction.CreateTransaction(FNode.Bank.Safebox.CurrentProtocol,account.account,account.n_operation+1,destAccount.account,LKey.PrivateKey,_amount,_fee,FEncodedPayload);
           inc(_totalamount,_amount);
           inc(_totalamount,_amount);
           inc(_totalfee,_fee);
           inc(_totalfee,_fee);
         end;
         end;
@@ -287,11 +283,11 @@ loop_start:
           if uint64(_totalSignerFee) >= signerAccount.balance then _fee := 0
           if uint64(_totalSignerFee) >= signerAccount.balance then _fee := 0
           else if signerAccount.balance - uint64(_totalSignerFee) > uint64(DefaultFee) then _fee := DefaultFee
           else if signerAccount.balance - uint64(_totalSignerFee) > uint64(DefaultFee) then _fee := DefaultFee
           else _fee := signerAccount.balance - uint64(_totalSignerFee);
           else _fee := signerAccount.balance - uint64(_totalSignerFee);
-          op := TOpChangeKeySigned.Create(FNode.Bank.SafeBox.CurrentProtocol,signerAccount.account,signerAccount.n_operation+_signer_n_ops+1,account.account,wk.PrivateKey,_newOwnerPublicKey,_fee,FEncodedPayload);
+          op := TOpChangeKeySigned.Create(FNode.Bank.SafeBox.CurrentProtocol,signerAccount.account,signerAccount.n_operation+_signer_n_ops+1,account.account,LKey.PrivateKey,_newOwnerPublicKey,_fee,FEncodedPayload);
           inc(_signer_n_ops);
           inc(_signer_n_ops);
           inc(_totalSignerFee, _fee);
           inc(_totalSignerFee, _fee);
         end else begin
         end else begin
-          op := TOpChangeKey.Create(FNode.Bank.SafeBox.CurrentProtocol,account.account,account.n_operation+1,account.account,wk.PrivateKey,_newOwnerPublicKey,_fee,FEncodedPayload);
+          op := TOpChangeKey.Create(FNode.Bank.SafeBox.CurrentProtocol,account.account,account.n_operation+1,account.account,LKey.PrivateKey,_newOwnerPublicKey,_fee,FEncodedPayload);
         end;
         end;
         inc(_totalfee,_fee);
         inc(_totalfee,_fee);
         operationstxt := 'Change private key to '+TAccountComp.GetECInfoTxt(_newOwnerPublicKey.EC_OpenSSL_NID);
         operationstxt := 'Change private key to '+TAccountComp.GetECInfoTxt(_newOwnerPublicKey.EC_OpenSSL_NID);
@@ -303,13 +299,13 @@ loop_start:
         if signerAccount.balance>DefaultFee then _fee := DefaultFee
         if signerAccount.balance>DefaultFee then _fee := DefaultFee
         else _fee := signerAccount.balance;
         else _fee := signerAccount.balance;
         if (rbListAccountForPublicSale.Checked) then begin
         if (rbListAccountForPublicSale.Checked) then begin
-          op := TOpListAccountForSaleOrSwap.CreateListAccountForSaleOrSwap(FNode.Bank.SafeBox.CurrentProtocol, CT_OpSubtype_ListAccountForPublicSale, signerAccount.account,signerAccount.n_operation+1+iAcc, account.account,_salePrice,_fee,destAccount.account,CT_TECDSA_Public_Nul,0,wk.PrivateKey, CT_HashLock_NUL, FEncodedPayload);
+          op := TOpListAccountForSaleOrSwap.CreateListAccountForSaleOrSwap(FNode.Bank.SafeBox.CurrentProtocol, CT_OpSubtype_ListAccountForPublicSale, signerAccount.account,signerAccount.n_operation+1+iAcc, account.account,_salePrice,_fee,destAccount.account,CT_TECDSA_Public_Nul,0,LKey.PrivateKey, CT_HashLock_NUL, FEncodedPayload);
         end else if (rbListAccountForPrivateSale.Checked) then begin
         end else if (rbListAccountForPrivateSale.Checked) then begin
-          op := TOpListAccountForSaleOrSwap.CreateListAccountForSaleOrSwap(FNode.Bank.SafeBox.CurrentProtocol, CT_OpSubtype_ListAccountForPrivateSale, signerAccount.account,signerAccount.n_operation+1+iAcc, account.account,_salePrice,_fee,destAccount.account,_newOwnerPublicKey,_lockedUntil,wk.PrivateKey, CT_HashLock_NUL, FEncodedPayload);
+          op := TOpListAccountForSaleOrSwap.CreateListAccountForSaleOrSwap(FNode.Bank.SafeBox.CurrentProtocol, CT_OpSubtype_ListAccountForPrivateSale, signerAccount.account,signerAccount.n_operation+1+iAcc, account.account,_salePrice,_fee,destAccount.account,_newOwnerPublicKey,_lockedUntil,LKey.PrivateKey, CT_HashLock_NUL, FEncodedPayload);
         end  else if (rbListAccountForAccountSwap.Checked) then begin
         end  else if (rbListAccountForAccountSwap.Checked) then begin
-          op := TOpListAccountForSaleOrSwap.CreateListAccountForSaleOrSwap(FNode.Bank.SafeBox.CurrentProtocol, CT_OpSubtype_ListAccountForAccountSwap, signerAccount.account,signerAccount.n_operation+1+iAcc, account.account,_salePrice,_fee,destAccount.account,_newOwnerPublicKey,_lockedUntil,wk.PrivateKey, LHashLock, FEncodedPayload);
+          op := TOpListAccountForSaleOrSwap.CreateListAccountForSaleOrSwap(FNode.Bank.SafeBox.CurrentProtocol, CT_OpSubtype_ListAccountForAccountSwap, signerAccount.account,signerAccount.n_operation+1+iAcc, account.account,_salePrice,_fee,destAccount.account,_newOwnerPublicKey,_lockedUntil,LKey.PrivateKey, LHashLock, FEncodedPayload);
         end  else if (rbListAccountForCoinSwap.Checked) then begin
         end  else if (rbListAccountForCoinSwap.Checked) then begin
-          op := TOpListAccountForSaleOrSwap.CreateListAccountForSaleOrSwap(FNode.Bank.SafeBox.CurrentProtocol, CT_OpSubtype_ListAccountForCoinSwap, signerAccount.account,signerAccount.n_operation+1+iAcc, account.account,_salePrice,_fee,destAccount.account,_newOwnerPublicKey,_lockedUntil,wk.PrivateKey, LHashLock, FEncodedPayload);
+          op := TOpListAccountForSaleOrSwap.CreateListAccountForSaleOrSwap(FNode.Bank.SafeBox.CurrentProtocol, CT_OpSubtype_ListAccountForCoinSwap, signerAccount.account,signerAccount.n_operation+1+iAcc, account.account,_salePrice,_fee,destAccount.account,_newOwnerPublicKey,_lockedUntil,LKey.PrivateKey, LHashLock, FEncodedPayload);
         end else raise Exception.Create('Select Sale type');
         end else raise Exception.Create('Select Sale type');
         {%endregion}
         {%endregion}
       end else if (PageControlOpType.ActivePage = tsDelistAccount) then begin
       end else if (PageControlOpType.ActivePage = tsDelistAccount) then begin
@@ -318,13 +314,15 @@ loop_start:
         // Special fee account:
         // Special fee account:
         if signerAccount.balance>DefaultFee then _fee := DefaultFee
         if signerAccount.balance>DefaultFee then _fee := DefaultFee
         else _fee := signerAccount.balance;
         else _fee := signerAccount.balance;
-        op := TOpDelistAccountForSale.CreateDelistAccountForSale(FNode.Bank.SafeBox.CurrentProtocol,signerAccount.account,signerAccount.n_operation+1+iAcc,account.account,_fee,wk.PrivateKey,FEncodedPayload);
+        op := TOpDelistAccountForSale.CreateDelistAccountForSale(FNode.Bank.SafeBox.CurrentProtocol,signerAccount.account,signerAccount.n_operation+1+iAcc,account.account,_fee,LKey.PrivateKey,FEncodedPayload);
         {%endregion}
         {%endregion}
       end else if (PageControlOpType.ActivePage = tsBuyAccount) then begin
       end else if (PageControlOpType.ActivePage = tsBuyAccount) then begin
         {%region Operation: Buy Account}
         {%region Operation: Buy Account}
-        if Not UpdateOpBuyAccount(account,accountToBuy,_amount,_newOwnerPublicKey,errors) then raise Exception.Create(errors);
+        if Not UpdateOpBuyAccount(account,accountToBuy,_amount,_newOwnerPublicKey, LRecipientSigned, errors) then raise Exception.Create(errors);
+        if LRecipientSigned AND (NOT WalletKeys.TryGetKey(account.accountInfo.new_publicKey, LKey)) then
+          raise Exception.Create('Recipient-signed key not found in Wallet');
         op := TOpBuyAccount.CreateBuy(FNode.Bank.Safebox.CurrentProtocol,account.account,account.n_operation+1,accountToBuy.account,accountToBuy.accountInfo.account_to_pay,
         op := TOpBuyAccount.CreateBuy(FNode.Bank.Safebox.CurrentProtocol,account.account,account.n_operation+1,accountToBuy.account,accountToBuy.accountInfo.account_to_pay,
-          accountToBuy.accountInfo.price,_amount,_fee,_newOwnerPublicKey,wk.PrivateKey,FEncodedPayload);
+          accountToBuy.accountInfo.price,_amount,_fee,_newOwnerPublicKey,LKey.PrivateKey,FEncodedPayload);
         {%endregion}
         {%endregion}
       end else if (PageControlOpType.ActivePage = tsChangeInfo) then begin
       end else if (PageControlOpType.ActivePage = tsChangeInfo) then begin
         {%region Operation: Change Info}
         {%region Operation: Change Info}
@@ -333,7 +331,7 @@ loop_start:
         end else begin
         end else begin
           if signerAccount.balance>DefaultFee then _fee := DefaultFee
           if signerAccount.balance>DefaultFee then _fee := DefaultFee
           else _fee := signerAccount.balance;
           else _fee := signerAccount.balance;
-          op := TOpChangeAccountInfo.CreateChangeAccountInfo(FNode.Bank.SafeBox.CurrentProtocol,signerAccount.account,signerAccount.n_operation+1,account.account,wk.PrivateKey,false,CT_TECDSA_Public_Nul,
+          op := TOpChangeAccountInfo.CreateChangeAccountInfo(FNode.Bank.SafeBox.CurrentProtocol,signerAccount.account,signerAccount.n_operation+1,account.account,LKey.PrivateKey,false,CT_TECDSA_Public_Nul,
              _changeName,_newName,_changeType,_newType,_fee,FEncodedPayload);
              _changeName,_newName,_changeType,_newType,_fee,FEncodedPayload);
         end;
         end;
         {%endregion}
         {%endregion}
@@ -815,11 +813,11 @@ begin
   end;
   end;
 end;
 end;
 
 
-function TFRMOperation.UpdateOpBuyAccount(const SenderAccount: TAccount; var AccountToBuy: TAccount; var amount: Int64; var NewPublicKey: TAccountKey; var errors: String): Boolean;
+function TFRMOperation.UpdateOpBuyAccount(const SenderAccount: TAccount; var AccountToBuy: TAccount; var amount: Int64; var NewPublicKey: TAccountKey; var ARecipientSigned : Boolean; var errors: String): Boolean;
 var c : Cardinal;
 var c : Cardinal;
   i : Integer;
   i : Integer;
 begin
 begin
-  //
+  ARecipientSigned := false;
   lblBuyAccountErrors.Caption := ''; c:=0;
   lblBuyAccountErrors.Caption := ''; c:=0;
   errors := '';
   errors := '';
   Try
   Try
@@ -831,12 +829,18 @@ begin
       errors := 'Invalid account to buy '+ebAccountToBuy.Text;
       errors := 'Invalid account to buy '+ebAccountToBuy.Text;
       exit;
       exit;
     end;
     end;
-    If (c<0) Or (c>=FNode.Bank.AccountsCount) Or (c=SenderAccount.account) then begin
+    If (c<0) Or (c>=FNode.Bank.AccountsCount) then begin
       errors := 'Invalid account number';
       errors := 'Invalid account number';
       exit;
       exit;
     end;
     end;
     AccountToBuy := FNode.GetMempoolAccount(c);
     AccountToBuy := FNode.GetMempoolAccount(c);
-    If not TAccountComp.IsAccountForSaleOrSwap(AccountToBuy.accountInfo) then begin
+    ARecipientSigned := TAccountComp.IsOperationRecipientSignable(SenderAccount, AccountToBuy, Amount, FNode.Bank.BlocksCount);
+    if (SenderAccount.account = AccountToBuy.Account) AND (NOT ARecipientSigned) then begin
+      errors := 'Not recipient signable';
+      exit;
+    end;
+
+    If not TAccountComp.IsAccountForSaleOrSwap(AccountToBuy.accountInfo, FNode.Bank.BlocksCount) then begin
       errors := 'Account '+TAccountComp.AccountNumberToAccountTxtNumber(c)+' is not for sale or swap';
       errors := 'Account '+TAccountComp.AccountNumberToAccountTxtNumber(c)+' is not for sale or swap';
       exit;
       exit;
     end;
     end;
@@ -844,11 +848,11 @@ begin
       errors := 'Invalid amount value';
       errors := 'Invalid amount value';
       exit;
       exit;
     end;
     end;
-    if (AccountToBuy.accountInfo.price>amount) then begin
+     if (AccountToBuy.accountInfo.price>amount) AND (NOT TAccountComp.IsAccountForCoinSwap(AccountToBuy.accountInfo, FNode.Bank.BlocksCount)) then begin
       errors := 'Account price '+TAccountComp.FormatMoney(AccountToBuy.accountInfo.price);
       errors := 'Account price '+TAccountComp.FormatMoney(AccountToBuy.accountInfo.price);
       exit;
       exit;
     end;
     end;
-    if TAccountComp.IsAccountForSale(AccountToBuy.accountInfo) AND (amount+DefaultFee > SenderAccount.balance) then begin
+    if TAccountComp.IsAccountForSale(AccountToBuy.accountInfo, FNode.Bank.BlocksCount) AND (amount+DefaultFee > SenderAccount.balance) then begin
       errors := 'Insufficient funds';
       errors := 'Insufficient funds';
       exit;
       exit;
     end;
     end;
@@ -1025,7 +1029,7 @@ begin
   Result := false;
   Result := false;
   if not (PageControlOpType.ActivePage=tsDelistAccount) then exit;
   if not (PageControlOpType.ActivePage=tsDelistAccount) then exit;
   try
   try
-    if Not TAccountComp.IsAccountForSaleOrSwap(TargetAccount.accountInfo) then begin
+    if Not TAccountComp.IsAccountForSaleOrSwap(TargetAccount.accountInfo, FNode.Bank.BlocksCount) then begin
       errors := 'Account '+TAccountComp.AccountNumberToAccountTxtNumber(TargetAccount.account)+' is not for sale or swap';
       errors := 'Account '+TAccountComp.AccountNumberToAccountTxtNumber(TargetAccount.account)+' is not for sale or swap';
       exit;
       exit;
     end;
     end;
@@ -1077,7 +1081,7 @@ Var
   LHashLock : T32Bytes;
   LHashLock : T32Bytes;
   salePrice, amount : Int64;
   salePrice, amount : Int64;
   auxC : Cardinal;
   auxC : Cardinal;
-  changeName,changeType : Boolean;
+  changeName,changeType, LRecipientSigned : Boolean;
   newName : TRawBytes;
   newName : TRawBytes;
   newType : Word;
   newType : Word;
 begin
 begin
@@ -1146,7 +1150,7 @@ begin
   end else if (PageControlOpType.ActivePage = tsDelistAccount) then begin
   end else if (PageControlOpType.ActivePage = tsDelistAccount) then begin
     Result := UpdateOpDelist(GetDefaultSenderAccount,signer_account,errors);
     Result := UpdateOpDelist(GetDefaultSenderAccount,signer_account,errors);
   end else if (PageControlOpType.ActivePage = tsBuyAccount) then begin
   end else if (PageControlOpType.ActivePage = tsBuyAccount) then begin
-    Result := UpdateOpBuyAccount(GetDefaultSenderAccount,account_to_buy,amount,publicKey,errors);
+    Result := UpdateOpBuyAccount(GetDefaultSenderAccount,account_to_buy,amount,publicKey,LRecipientSigned, errors);
   end else if (PageControlOpType.ActivePage = tsChangeInfo) then begin
   end else if (PageControlOpType.ActivePage = tsChangeInfo) then begin
     Result := UpdateOpChangeInfo(GetDefaultSenderAccount,signer_account,changeName,newName,changeType,newType,errors);
     Result := UpdateOpChangeInfo(GetDefaultSenderAccount,signer_account,changeName,newName,changeType,newType,errors);
   end else begin
   end else begin

+ 12 - 0
src/gui-classic/UFRMWallet.dfm

@@ -916,6 +916,10 @@ object FRMWallet: TFRMWallet
     object tsBlockChain: TTabSheet
     object tsBlockChain: TTabSheet
       Caption = 'Block Explorer'
       Caption = 'Block Explorer'
       ImageIndex = 1
       ImageIndex = 1
+      ExplicitLeft = 0
+      ExplicitTop = 0
+      ExplicitWidth = 0
+      ExplicitHeight = 0
       object Panel2: TPanel
       object Panel2: TPanel
         Left = 0
         Left = 0
         Top = 0
         Top = 0
@@ -1095,6 +1099,10 @@ object FRMWallet: TFRMWallet
     object tsNodeStats: TTabSheet
     object tsNodeStats: TTabSheet
       Caption = 'Node Stats'
       Caption = 'Node Stats'
       ImageIndex = 3
       ImageIndex = 3
+      ExplicitLeft = 0
+      ExplicitTop = 0
+      ExplicitWidth = 0
+      ExplicitHeight = 0
       DesignSize = (
       DesignSize = (
         857
         857
         438)
         438)
@@ -1164,6 +1172,10 @@ object FRMWallet: TFRMWallet
     object tsMessages: TTabSheet
     object tsMessages: TTabSheet
       Caption = 'Messages'
       Caption = 'Messages'
       ImageIndex = 6
       ImageIndex = 6
+      ExplicitLeft = 0
+      ExplicitTop = 0
+      ExplicitWidth = 0
+      ExplicitHeight = 0
       DesignSize = (
       DesignSize = (
         857
         857
         438)
         438)

+ 2 - 2
src/gui-classic/UFRMWallet.pas

@@ -899,12 +899,12 @@ begin
   Strings.Add(Format('Updated on block: %d  (%d blocks ago)',[account.updated_block,FNode.Bank.BlocksCount-account.updated_block]));
   Strings.Add(Format('Updated on block: %d  (%d blocks ago)',[account.updated_block,FNode.Bank.BlocksCount-account.updated_block]));
   Strings.Add(Format('Public key type: %s',[TAccountComp.GetECInfoTxt(account.accountInfo.accountKey.EC_OpenSSL_NID)]));
   Strings.Add(Format('Public key type: %s',[TAccountComp.GetECInfoTxt(account.accountInfo.accountKey.EC_OpenSSL_NID)]));
   Strings.Add(Format('Base58 Public key: %s',[TAccountComp.AccountPublicKeyExport(account.accountInfo.accountKey)]));
   Strings.Add(Format('Base58 Public key: %s',[TAccountComp.AccountPublicKeyExport(account.accountInfo.accountKey)]));
-  if TAccountComp.IsAccountForSale(account.accountInfo) then begin
+  if TAccountComp.IsAccountForSale(account.accountInfo, FNode.Bank.BlocksCount) then begin
     Strings.Add('');
     Strings.Add('');
     Strings.Add('** Account is for sale: **');
     Strings.Add('** Account is for sale: **');
     Strings.Add(Format('Price: %s',[TAccountComp.FormatMoney(account.accountInfo.price)]));
     Strings.Add(Format('Price: %s',[TAccountComp.FormatMoney(account.accountInfo.price)]));
     Strings.Add(Format('Seller account (where to pay): %s',[TAccountComp.AccountNumberToAccountTxtNumber(account.accountInfo.account_to_pay)]));
     Strings.Add(Format('Seller account (where to pay): %s',[TAccountComp.AccountNumberToAccountTxtNumber(account.accountInfo.account_to_pay)]));
-    if TAccountComp.IsAccountForSaleAcceptingTransactions(account.accountInfo) then begin
+    if TAccountComp.IsAccountForPrivateSale(account.accountInfo, FNode.Bank.BlocksCount) then begin
       Strings.Add('');
       Strings.Add('');
       Strings.Add('** Private sale **');
       Strings.Add('** Private sale **');
       Strings.Add(Format('New Base58 Public key: %s',[TAccountComp.AccountPublicKeyExport(account.accountInfo.new_publicKey)]));
       Strings.Add(Format('New Base58 Public key: %s',[TAccountComp.AccountPublicKeyExport(account.accountInfo.new_publicKey)]));

+ 2 - 2
src/gui-classic/UGridUtils.pas

@@ -722,10 +722,10 @@ begin
           Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfRight,tfVerticalCenter,tfSingleLine]);
           Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfRight,tfVerticalCenter,tfSingleLine]);
         end;
         end;
         act_saleprice : Begin
         act_saleprice : Begin
-          if TAccountComp.IsAccountForSale(account.accountInfo) then begin
+          if TAccountComp.IsAccountForSale(account.accountInfo, LNodeBlocksCount) then begin
             // Show price for sale
             // Show price for sale
             s := TAccountComp.FormatMoney(account.accountInfo.price);
             s := TAccountComp.FormatMoney(account.accountInfo.price);
-            if TAccountComp.IsAccountForSaleAcceptingTransactions(account.accountInfo) then begin
+            if TAccountComp.IsAccountForPrivateSale(account.accountInfo, LNodeBlocksCount) then begin
               if TAccountComp.IsAccountLocked(account.accountInfo,LNodeBlocksCount) then begin
               if TAccountComp.IsAccountLocked(account.accountInfo,LNodeBlocksCount) then begin
                 DrawGrid.Canvas.Font.Color := clNavy;
                 DrawGrid.Canvas.Font.Color := clNavy;
               end else begin
               end else begin