Browse Source

PIP-0032: Atomic swap implementation

Herman Schoenfeld 6 years ago
parent
commit
81ebc63ad7

+ 1 - 0
.gitignore

@@ -45,5 +45,6 @@ src/ppas.sh
 
 ## Delphi files
 __history/
+__recovery/
 dunit.ini
 *.res

+ 148 - 77
src/core/UAccounts.pas

@@ -33,7 +33,7 @@ Type
   TAccountKey = TECDSA_Public;
   PAccountKey = ^TAccountKey;
 
-  TAccountState = (as_Unknown, as_Normal, as_ForSale);
+  TAccountState = (as_Unknown, as_Normal, as_ForSale, as_ForAtomicAccountSwap, as_ForAtomicCoinSwap);
 
   TAccountInfo = Record
     state : TAccountState;
@@ -130,8 +130,13 @@ Type
   public
     Class Function IsValidAccountKey(const account: TAccountKey; var errors : String): Boolean;
     Class Function IsValidAccountInfo(const accountInfo: TAccountInfo; var errors : String): Boolean;
+    Class Function IsValidHashLockKey(const AAccount : TAccount; const AKey : TRawBytes) : Boolean;
+    Class Function CalculateHashLock(const AKey : TRawBytes) : T32Bytes;
     Class Function IsAccountForSale(const accountInfo: TAccountInfo) : Boolean;
-    Class Function IsAccountForSaleAcceptingTransactions(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 GetECInfoTxt(Const EC_OpenSSL_NID: Word) : String;
     Class Procedure ValidsEC_OpenSSL_NID(list : TList<Word>);
     Class Function AccountKey2RawString(const account: TAccountKey): TRawBytes; overload;
@@ -456,7 +461,7 @@ Type
     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 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(previous : TAccountPreviousBlockInfo; const AOpID : TRawBytes; buyer,account_to_buy,seller: Cardinal; n_operation : Cardinal; amount, account_price, fee : UInt64; const new_account_key : TAccountKey; 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 Commit(Const operationBlock : TOperationBlock; var errors : String) : Boolean;
     Function Account(account_number : Cardinal) : TAccount;
     Procedure Rollback;
@@ -474,7 +479,9 @@ Type
   TStreamOp = Class
   public
     class Function WriteAnsiString(Stream: TStream; const value: TRawBytes): Integer; overload;
-    class Function ReadAnsiString(Stream: TStream; var value: TRawBytes): Integer; overload;
+    class Function WriteAnsiString(Stream: TStream; const value: T32Bytes): Integer; overload;
+    class Function ReadAnsiString(Stream: TStream; var value: TRawBytes; ACheckLength : Integer = 0) : Integer; overload;
+    class Function ReadAnsiString(Stream: TStream; var value: T32Bytes): Integer; overload;
     class Function ReadString(Stream: TStream; var value: String): Integer;
     class Function WriteAccountKey(Stream: TStream; const value: TAccountKey): Integer;
     class Function ReadAccountKey(Stream: TStream; var value : TAccountKey): Integer;
@@ -504,6 +511,7 @@ Const
 
   CT_SafeBoxChunkIdentificator = 'SafeBoxChunk';
   CT_PCSafeBoxHeader_NUL : TPCSafeBoxHeader = (protocol:0;startBlock:0;endBlock:0;blocksCount:0;safeBoxHash:Nil);
+  CT_HashLock_NUL : T32Bytes = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
 
 function Check_Safebox_Names_Consistency(sb : TPCSafeBox; const title :String; var errors : String) : Boolean;
 Procedure Check_Safebox_Integrity(sb : TPCSafebox; title: String);
@@ -968,7 +976,7 @@ begin
   Stream.WriteBuffer(raw[Low(raw)],Length(raw));
 end;
 
-class function TStreamOp.ReadAnsiString(Stream: TStream; var value: TRawBytes): Integer;
+class function TStreamOp.ReadAnsiString(Stream: TStream; var value: TRawBytes; ACheckLength : Integer = 0): Integer;
 Var
   w: Word;
 begin
@@ -978,7 +986,7 @@ begin
     Exit;
   end;
   Stream.Read(w, 2);
-  if Stream.Size - Stream.Position < w then begin
+  if (Stream.Size - Stream.Position < w) OR ((ACheckLength > 0) AND (w <> ACheckLength)) then begin
     Stream.Position := Stream.Position - 2; // Go back!
     SetLength(value,0);
     Result := -1;
@@ -991,6 +999,17 @@ begin
   Result := w+2;
 end;
 
+class function TStreamOp.ReadAnsiString(Stream: TStream;var value: T32Bytes): Integer;
+var
+  LBytes : TRawBytes;
+   i : Integer;
+begin
+  Result := ReadAnsiString(Stream, LBytes, 32);
+  if Result > 0 then
+    value := TBaseType.To32Bytes(LBytes);
+end;
+
+
 class function TStreamOp.ReadString(Stream: TStream; var value: String): Integer;
 var raw : TRawBytes;
 begin
@@ -1021,6 +1040,12 @@ begin
   Result := w+2;
 end;
 
+class function TStreamOp.WriteAnsiString(Stream: TStream; const value: T32Bytes): Integer;
+begin
+  Result := WriteAnsiString(Stream, TBaseType.ToRawBytes(value));
+end;
+
+
 { TAccountComp }
 Const CT_Base58 : String = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
 
@@ -1040,10 +1065,15 @@ Var ms : TMemoryStream;
 begin
   case AccountInfo.state of
     as_Normal: AccountKey2RawString(AccountInfo.accountKey,dest);
-    as_ForSale: begin
+    as_ForSale, as_ForAtomicAccountSwap, as_ForAtomicCoinSwap: begin
+      case AccountInfo.state of
+        as_ForSale: w := CT_AccountInfo_ForSale;
+        as_ForAtomicAccountSwap: w := CT_AccountInfo_ForAccountSwap;
+        as_ForAtomicCoinSwap: w := CT_AccountInfo_ForCoinSwap;
+      end;
       ms := TMemoryStream.Create;
       Try
-        w := CT_AccountInfo_ForSale;
+
         ms.Write(w,SizeOf(w));
         //
         TStreamOp.WriteAccountKey(ms,AccountInfo.accountKey);
@@ -1353,20 +1383,55 @@ begin
   end;
 end;
 
+Class Function TAccountComp.IsValidHashLockKey(const AAccount : TAccount; const AKey : TRawBytes) : Boolean;
+begin
+  Result := BytesEqual( TBaseType.ToRawBytes( CalculateHashLock( AKey ) ), AAccount.account_data);
+end;
+
+Class Function TAccountComp.CalculateHashLock(const AKey : TRawBytes) : T32Bytes;
+begin
+  Result := TBaseType.To32Bytes( TCrypto.DoSha256(AKey) );
+end;
+
 class function TAccountComp.IsAccountForSale(const accountInfo: TAccountInfo): Boolean;
 begin
-  Result := (AccountInfo.state=as_ForSale);
+  Result := AccountInfo.state in [as_ForSale];
+end;
+
+class function TAccountComp.IsAccountForSwap(const accountInfo: TAccountInfo): Boolean;
+begin
+  Result := AccountInfo.state in [as_ForAtomicAccountSwap, as_ForAtomicCoinSwap];
 end;
 
-class function TAccountComp.IsAccountForSaleAcceptingTransactions(const accountInfo: TAccountInfo): Boolean;
+class function TAccountComp.IsAccountForSaleOrSwap(const accountInfo: TAccountInfo): Boolean;
+begin
+  Result := IsAccountForSale(accountInfo) OR IsAccountForSwap(accountInfo);
+end;
+
+class function TAccountComp.IsAccountForSaleAcceptingTransactions(const AAccountInfo: TAccountInfo): Boolean;
+var LErrors : String;
+begin
+  Result := IsAccountForSale(AAccountInfo) AND IsValidAccountKey(AAccountInfo.new_publicKey, LErrors);
+end;
+
+class function TAccountComp.IsAccountForSaleOrSwapAcceptingTransactions(const account: TAccount; const APayload : TRawBytes): Boolean;
 var errors : String;
 begin
-  Result := (AccountInfo.state=as_ForSale) And (IsValidAccountKey(AccountInfo.new_publicKey,errors));
+  if Not IsAccountForSaleOrSwap(account.accountInfo) then
+    exit(false);
+
+  if (account.accountInfo.state in [as_ForSale, as_ForAtomicAccountSwap]) then
+    if NOT IsValidAccountKey(account.accountInfo.new_publicKey,errors) then
+      exit(false);
+
+   if (account.accountInfo.state in [as_ForAtomicAccountSwap, as_ForAtomicCoinSwap]) then
+     if NOT IsValidHashLockKey(account, APayload) then
+       exit(false);
 end;
 
 class function TAccountComp.IsAccountLocked(const AccountInfo: TAccountInfo; blocks_count: Cardinal): Boolean;
 begin
-  Result := (AccountInfo.state=as_ForSale) And ((AccountInfo.locked_until_block)>=blocks_count);
+  Result := IsAccountForSaleOrSwap(accountInfo) And ((AccountInfo.locked_until_block)>=blocks_count);
 end;
 
 class procedure TAccountComp.SaveTOperationBlockToStream(const stream: TStream; const operationBlock: TOperationBlock);
@@ -1480,13 +1545,17 @@ begin
         dest.account_to_pay:=CT_AccountInfo_NUL.account_to_pay;
         dest.new_publicKey:=CT_AccountInfo_NUL.new_publicKey;
       End;
-      CT_AccountInfo_ForSale : Begin
+      CT_AccountInfo_ForSale, CT_AccountInfo_ForAccountSwap, CT_AccountInfo_ForCoinSwap : Begin
         TStreamOp.ReadAccountKey(ms,dest.accountKey);
         ms.Read(dest.locked_until_block,SizeOf(dest.locked_until_block));
         ms.Read(dest.price,SizeOf(dest.price));
         ms.Read(dest.account_to_pay,SizeOf(dest.account_to_pay));
         TStreamOp.ReadAccountKey(ms,dest.new_publicKey);
-        dest.state := as_ForSale;
+        case w of
+          CT_AccountInfo_ForSale: dest.state := as_ForSale;
+          CT_AccountInfo_ForAccountSwap: dest.state := as_ForAtomicAccountSwap;
+          CT_AccountInfo_ForCoinSwap: dest.state := as_ForAtomicCoinSwap;
+        end;
       End;
     else
       raise Exception.Create('DEVELOP ERROR 20170214-2');
@@ -3891,111 +3960,113 @@ begin
   end;
 end;
 
-function TPCSafeBoxTransaction.BuyAccount(previous : TAccountPreviousBlockInfo; const AOpID : TRawBytes;
-  buyer, account_to_buy,
-  seller: Cardinal; n_operation: Cardinal; amount, account_price, fee: UInt64;
-  const new_account_key: TAccountKey; var errors: String): Boolean;
-Var PaccBuyer, PaccAccountToBuy, PaccSeller : PAccount;
-    PaccBuyer_Sealed, PaccAccountToBuy_Sealed, PaccSeller_Sealed : PSealedAccount;
+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;
+var
+  LPBuyerAccount, LPAccountToBuy, LPSellerAccount : PAccount;
+  LPBuyerAccount_Sealed, LPAccountToBuy_Sealed, LPSellerAccount_Sealed : PSealedAccount;
 begin
   Result := false;
-  errors := '';
+  AErrors := '';
   if not CheckIntegrity then begin
-    errors := 'Invalid integrity in accounts transaction';
+    AErrors := 'Invalid integrity in accounts transaction';
     exit;
   end;
-  if (buyer<0) Or (buyer>=(Origin_BlocksCount*CT_AccountsPerBlock)) Or
-     (account_to_buy<0) Or (account_to_buy>=(Origin_BlocksCount*CT_AccountsPerBlock)) Or
-     (seller<0) Or (seller>=(Origin_BlocksCount*CT_AccountsPerBlock)) then begin
-     errors := 'Invalid account number on buy';
+  if (ABuyer<0) Or (ABuyer>=(Origin_BlocksCount*CT_AccountsPerBlock)) Or
+     (AAccountToBuy<0) Or (AAccountToBuy>=(Origin_BlocksCount*CT_AccountsPerBlock)) Or
+     (ASeller<0) Or (ASeller>=(Origin_BlocksCount*CT_AccountsPerBlock)) then begin
+     AErrors := 'Invalid account number on buy';
      exit;
   end;
-  if TAccountComp.IsAccountBlockedByProtocol(buyer,Origin_BlocksCount) then begin
-    errors := 'Buyer account is blocked for protocol';
+  if TAccountComp.IsAccountBlockedByProtocol(ABuyer,Origin_BlocksCount) then begin
+    AErrors := 'Buyer account is blocked for protocol';
     Exit;
   end;
-  if TAccountComp.IsAccountBlockedByProtocol(account_to_buy,Origin_BlocksCount) then begin
-    errors := 'Account to buy is blocked for protocol';
+  if TAccountComp.IsAccountBlockedByProtocol(AAccountToBuy,Origin_BlocksCount) then begin
+    AErrors := 'Account to buy is blocked for protocol';
     Exit;
   end;
-  if TAccountComp.IsAccountBlockedByProtocol(seller,Origin_BlocksCount) then begin
-    errors := 'Seller account is blocked for protocol';
+  if TAccountComp.IsAccountBlockedByProtocol(ASeller,Origin_BlocksCount) then begin
+    AErrors := 'Seller account is blocked for protocol';
     Exit;
   end;
-  PaccBuyer := GetInternalAccount(buyer,PaccBuyer_Sealed);
-  PaccAccountToBuy := GetInternalAccount(account_to_buy,PaccAccountToBuy_Sealed);
-  PaccSeller := GetInternalAccount(seller,PaccSeller_Sealed);
-  if (PaccBuyer^.n_operation+1<>n_operation) then begin
-    errors := 'Incorrect n_operation';
+  LPBuyerAccount := GetInternalAccount(ABuyer,LPBuyerAccount_Sealed);
+  LPAccountToBuy := GetInternalAccount(AAccountToBuy,LPAccountToBuy_Sealed);
+  LPSellerAccount := GetInternalAccount(ASeller,LPSellerAccount_Sealed);
+  if (LPBuyerAccount^.n_operation+1<>ANOperation) then begin
+    AErrors := 'Incorrect n_operation';
     Exit;
   end;
-  if (PaccBuyer^.balance < (amount+fee)) then begin
-    errors := 'Insuficient founds';
+  if (LPBuyerAccount^.balance < (AAmount+AFee)) then begin
+    AErrors := 'Insuficient founds';
     Exit;
   end;
-  if (fee>CT_MaxTransactionFee) then begin
-    errors := 'Max fee';
+  if (AFee>CT_MaxTransactionFee) then begin
+    AErrors := 'Max fee';
     Exit;
   end;
-  if (TAccountComp.IsAccountLocked(PaccBuyer^.accountInfo,Origin_BlocksCount)) then begin
-    errors := 'Buyer account is locked until block '+Inttostr(PaccBuyer^.accountInfo.locked_until_block);
+  if (TAccountComp.IsAccountLocked(LPBuyerAccount^.accountInfo,Origin_BlocksCount)) then begin
+    AErrors := 'Buyer account is locked until block '+Inttostr(LPBuyerAccount^.accountInfo.locked_until_block);
     Exit;
   end;
-  If not (TAccountComp.IsAccountForSale(PaccAccountToBuy^.accountInfo)) then begin
-    errors := 'Account is not for sale';
+  If not (TAccountComp.IsAccountForSaleOrSwap(LPAccountToBuy^.accountInfo)) then begin
+    AErrors := 'Account is not for sale or swap';
     Exit;
   end;
-  if (PaccAccountToBuy^.accountInfo.new_publicKey.EC_OpenSSL_NID<>CT_TECDSA_Public_Nul.EC_OpenSSL_NID) And
-     (Not TAccountComp.EqualAccountKeys(PaccAccountToBuy^.accountInfo.new_publicKey,new_account_key)) then begin
-    errors := 'New public key is not equal to allowed new public key for account';
+  if (LPAccountToBuy^.accountInfo.new_publicKey.EC_OpenSSL_NID<>CT_TECDSA_Public_Nul.EC_OpenSSL_NID) And
+     (Not TAccountComp.EqualAccountKeys(LPAccountToBuy^.accountInfo.new_publicKey,ANewAccountKey)) then begin
+    AErrors := 'New public key is not equal to allowed new public key for account';
     Exit;
   end;
   // Buy an account applies when account_to_buy.amount + operation amount >= price
   // Then, account_to_buy.amount will be (account_to_buy.amount + amount - price)
   // and buyer.amount will be buyer.amount + price
-  if (PaccAccountToBuy^.accountInfo.price > (PaccAccountToBuy^.balance+amount)) then begin
-    errors := 'Account price '+TAccountComp.FormatMoney(PaccAccountToBuy^.accountInfo.price)+' < balance '+
-      TAccountComp.FormatMoney(PaccAccountToBuy^.balance)+' + amount '+TAccountComp.FormatMoney(amount);
+  if (LPAccountToBuy^.accountInfo.price > (LPAccountToBuy^.balance+AAmount)) then begin
+    AErrors := 'Account price '+TAccountComp.FormatMoney(LPAccountToBuy^.accountInfo.price)+' < balance '+
+      TAccountComp.FormatMoney(LPAccountToBuy^.balance)+' + amount '+TAccountComp.FormatMoney(AAmount);
+    Exit;
+  end;
+  if TAccountComp.IsAccountForSwap(LPAccountToBuy^.accountInfo) AND (NOT TAccountComp.IsValidHashLockKey(LPAccountToBuy^, AHashLockKey)) then begin
+    AErrors := 'Account is not unlocked by supplied hash lock key';
     Exit;
   end;
 
-  UpdateSeal(PaccBuyer_Sealed,AOpID);
-  UpdateSeal(PaccAccountToBuy_Sealed,AOpID);
-  UpdateSeal(PaccSeller_Sealed,AOpID);
+  UpdateSeal(LPBuyerAccount_Sealed,AOpID);
+  UpdateSeal(LPAccountToBuy_Sealed,AOpID);
+  UpdateSeal(LPSellerAccount_Sealed,AOpID);
 
-  previous.UpdateIfLower(PaccBuyer^.account,PaccBuyer^.updated_block);
-  previous.UpdateIfLower(PaccAccountToBuy^.account,PaccAccountToBuy^.updated_block);
-  previous.UpdateIfLower(PaccSeller^.account,PaccSeller^.updated_block);
+  APrevious.UpdateIfLower(LPBuyerAccount^.account,LPBuyerAccount^.updated_block);
+  APrevious.UpdateIfLower(LPAccountToBuy^.account,LPAccountToBuy^.updated_block);
+  APrevious.UpdateIfLower(LPSellerAccount^.account,LPSellerAccount^.updated_block);
 
-  If PaccBuyer^.updated_block<>Origin_BlocksCount then begin
-    PaccBuyer^.previous_updated_block := PaccBuyer^.updated_block;
-    PaccBuyer^.updated_block := Origin_BlocksCount;
+  If LPBuyerAccount^.updated_block<>Origin_BlocksCount then begin
+    LPBuyerAccount^.previous_updated_block := LPBuyerAccount^.updated_block;
+    LPBuyerAccount^.updated_block := Origin_BlocksCount;
   end;
 
-  If PaccAccountToBuy^.updated_block<>Origin_BlocksCount then begin
-    PaccAccountToBuy^.previous_updated_block := PaccAccountToBuy^.updated_block;
-    PaccAccountToBuy^.updated_block := Origin_BlocksCount;
+  If LPAccountToBuy^.updated_block<>Origin_BlocksCount then begin
+    LPAccountToBuy^.previous_updated_block := LPAccountToBuy^.updated_block;
+    LPAccountToBuy^.updated_block := Origin_BlocksCount;
   end;
 
-  If PaccSeller^.updated_block<>Origin_BlocksCount then begin
-    PaccSeller^.previous_updated_block := PaccSeller^.updated_block;
-    PaccSeller^.updated_block := Origin_BlocksCount;
+  If LPSellerAccount^.updated_block<>Origin_BlocksCount then begin
+    LPSellerAccount^.previous_updated_block := LPSellerAccount^.updated_block;
+    LPSellerAccount^.updated_block := Origin_BlocksCount;
   end;
 
   // Inc buyer n_operation
-  PaccBuyer^.n_operation := n_operation;
+  LPBuyerAccount^.n_operation := ANOperation;
   // Set new balance values
-  PaccBuyer^.balance := PaccBuyer^.balance - (amount + fee);
-  PaccAccountToBuy^.balance := PaccAccountToBuy^.balance + amount - PaccAccountToBuy^.accountInfo.price;
-  PaccSeller^.balance := PaccSeller^.balance + PaccAccountToBuy^.accountInfo.price;
+  LPBuyerAccount^.balance := LPBuyerAccount^.balance - (AAmount + AFee);
+  LPAccountToBuy^.balance := LPAccountToBuy^.balance + AAmount - LPAccountToBuy^.accountInfo.price;
+  LPSellerAccount^.balance := LPSellerAccount^.balance + LPAccountToBuy^.accountInfo.price;
 
   // After buy, account will be unlocked and set to normal state and new account public key changed
-  PaccAccountToBuy^.accountInfo := CT_AccountInfo_NUL;
-  PaccAccountToBuy^.accountInfo.state := as_Normal;
-  PaccAccountToBuy^.accountInfo.accountKey := new_account_key;
+  LPAccountToBuy^.accountInfo := CT_AccountInfo_NUL;
+  LPAccountToBuy^.accountInfo.state := as_Normal;
+  LPAccountToBuy^.accountInfo.accountKey := ANewAccountKey;
 
-  Dec(FTotalBalance,Int64(fee));
-  inc(FTotalFee,Int64(fee));
+  Dec(FTotalBalance,Int64(AFee));
+  inc(FTotalFee,Int64(AFee));
   Result := true;
 end;
 
@@ -4236,11 +4307,11 @@ begin
       Exit;
     end;
     if (PaccSender^.balance < (amount)) then begin
-      errors := 'Insuficient sender funds';
+      errors := 'Insufficient sender funds';
       Exit;
     end;
     if (PaccSigner^.balance < (fee)) then begin
-      errors := 'Insuficient signer funds';
+      errors := 'Insufficient signer funds';
       Exit;
     end;
     if (TAccountComp.IsAccountLocked(PaccSigner^.accountInfo,Origin_BlocksCount)) then begin

+ 40 - 16
src/core/UBlockChain.pas

@@ -112,7 +112,7 @@ uses
 
 Type
   // Moved from UOpTransaction to here
-  TOpChangeAccountInfoType = (public_key,account_name,account_type,list_for_public_sale,list_for_private_sale,delist);
+  TOpChangeAccountInfoType = (public_key, account_name, account_type, list_for_public_sale, list_for_private_sale, delist, account_data );
   TOpChangeAccountInfoTypes = Set of TOpChangeAccountInfoType;
 
   // MultiOp... will allow a MultiOperation
@@ -145,6 +145,7 @@ Type
     New_Accountkey: TAccountKey;  // If (changes_mask and $0001)=$0001 then change account key
     New_Name: TRawBytes;          // If (changes_mask and $0002)=$0002 then change name
     New_Type: Word;               // If (changes_mask and $0004)=$0004 then change type
+    New_Data: TRawBytes;
     Seller_Account : Int64;
     Account_Price : Int64;
     Locked_Until_Block : Cardinal;
@@ -210,6 +211,7 @@ Type
   TPCOperation = Class
   Private
   Protected
+    FCurrentProtocol : Word;
     FPrevious_Signer_updated_block: Cardinal;
     FPrevious_Destination_updated_block : Cardinal;
     FPrevious_Seller_updated_block : Cardinal;
@@ -227,7 +229,7 @@ Type
     function IsValidECDSASignature(const PubKey: TECDSA_Public; current_protocol : Word; const Signature: TECDSA_SIG): Boolean;
     procedure CopyUsedPubkeySignatureFrom(SourceOperation : TPCOperation); virtual;
   public
-    constructor Create; virtual;
+    constructor Create(ACurrentProtocol : Word); virtual;
     destructor Destroy; override;
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; virtual;
     function DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors: String): Boolean; virtual; abstract;
@@ -268,7 +270,7 @@ Type
     function GetOpID : TRawBytes; // OPID is RipeMD160 hash of the operation
     //
     function GetOperationStreamData : TBytes;
-    class function GetOperationFromStreamData(StreamData : TBytes) : TPCOperation;
+    class function GetOperationFromStreamData(ACurrentProtocol: word; StreamData : TBytes) : TPCOperation;
     //
     function IsValidSignatureBasedOnCurrentSafeboxState(ASafeBoxTransaction : TPCSafeBoxTransaction) : Boolean; virtual; abstract;
   End;
@@ -559,7 +561,7 @@ Const
   CT_TMultiOpSender_NUL : TMultiOpSender =  (Account:0;Amount:0;N_Operation:0;Payload:Nil;Signature:(r:Nil;s:Nil));
   CT_TMultiOpReceiver_NUL : TMultiOpReceiver = (Account:0;Amount:0;Payload:Nil);
   CT_TMultiOpChangeInfo_NUL : TMultiOpChangeInfo = (Account:0;N_Operation:0;Changes_type:[];New_Accountkey:(EC_OpenSSL_NID:0;x:Nil;y:Nil);New_Name:Nil;New_Type:0;Seller_Account:-1;Account_Price:-1;Locked_Until_Block:0;Fee:0;Signature:(r:Nil;s:Nil));
-  CT_TOpChangeAccountInfoType_Txt : Array[Low(TOpChangeAccountInfoType)..High(TOpChangeAccountInfoType)] of String = ('public_key','account_name','account_type','list_for_public_sale','list_for_private_sale','delist');
+  CT_TOpChangeAccountInfoType_Txt : Array[Low(TOpChangeAccountInfoType)..High(TOpChangeAccountInfoType)] of String = ('public_key','account_name','account_type','list_for_public_sale','list_for_private_sale', 'delist', 'account_data');
 
 implementation
 
@@ -2619,7 +2621,7 @@ begin
         errors := 'Invalid operation structure ' + inttostr(i) + '/' + inttostr(c) + ' optype not valid:' + InttoHex(OpType, 4);
         exit;
       end;
-      bcop := OpClass.Create;
+      bcop := OpClass.Create(LoadProtocolVersion);
       Try
         if LoadingFromStorage then begin
           If not bcop.LoadFromStorage(Stream,LoadProtocolVersion,PreviousUpdatedBlocks) then begin
@@ -2864,8 +2866,9 @@ end;
 
 { TPCOperation }
 
-constructor TPCOperation.Create;
+constructor TPCOperation.Create(ACurrentProtocol: word);
 begin
+  FCurrentProtocol := ACurrentProtocol;
   FHasValidSignature := False;
   FBufferedSha256:=Nil;
   FBufferedRipeMD160:=Nil;
@@ -2902,7 +2905,7 @@ begin
   end else Raise Exception.Create('ERROR DEV 20170426-1'); // This should never happen, if good coded
 end;
 
-class function TPCOperation.GetOperationFromStreamData(StreamData : TBytes): TPCOperation;
+class function TPCOperation.GetOperationFromStreamData(ACurrentProtocol: word; StreamData : TBytes): TPCOperation;
   // Loads an TPCOperation saved using "GetOperationStreamData"
   // 1 byte for OpType
   // N bytes for Operation specific data (saved at SaveOpToStream)
@@ -2922,7 +2925,7 @@ begin
     if j >= 0 then
       OpClass := _OperationsClass[j]
     else Exit;
-    auxOp := OpClass.Create;
+    auxOp := OpClass.Create(ACurrentProtocol);
     if auxOp.LoadOpFromStream(stream,False) then Result := auxOp
     else auxOp.Free;
   Finally
@@ -3253,15 +3256,36 @@ begin
       OperationResume.OperationTxt := 'Recover founds';
       Result := true;
     End;
-    CT_Op_ListAccountForSale : Begin
-      If TOpListAccount(Operation).IsPrivateSale then begin
-        OperationResume.OpSubtype := CT_OpSubtype_ListAccountForPrivateSale;
-        OperationResume.OperationTxt := 'List account '+TAccountComp.AccountNumberToAccountTxtNumber(TOpListAccount(Operation).Data.account_target)+' for private sale price '+
-          TAccountComp.FormatMoney(TOpListAccount(Operation).Data.account_price)+' PASC pay to '+TAccountComp.AccountNumberToAccountTxtNumber(TOpListAccount(Operation).Data.account_to_pay);
-      end else begin
-        OperationResume.OpSubtype := CT_OpSubtype_ListAccountForPublicSale;
-        OperationResume.OperationTxt := 'List account '+TAccountComp.AccountNumberToAccountTxtNumber(TOpListAccount(Operation).Data.account_target)+' for sale price '+
+    CT_Op_ListAccountForSale : begin
+      case TOpListAccountForSale(Operation).SubType of
+        CT_OpSubtype_ListAccountForPrivateSale:  begin
+          OperationResume.OpSubtype := CT_OpSubtype_ListAccountForPrivateSale;
+          OperationResume.OperationTxt := 'List account '+TAccountComp.AccountNumberToAccountTxtNumber(TOpListAccount(Operation).Data.account_target)+' for private sale price '+
           TAccountComp.FormatMoney(TOpListAccount(Operation).Data.account_price)+' PASC pay to '+TAccountComp.AccountNumberToAccountTxtNumber(TOpListAccount(Operation).Data.account_to_pay);
+        end;
+        CT_OpSubtype_ListAccountForPublicSale:  begin
+          OperationResume.OpSubtype := CT_OpSubtype_ListAccountForPublicSale;
+          OperationResume.OperationTxt := 'List account '+TAccountComp.AccountNumberToAccountTxtNumber(TOpListAccount(Operation).Data.account_target)+' for sale price '+
+            TAccountComp.FormatMoney(TOpListAccount(Operation).Data.account_price)+' PASC pay to '+TAccountComp.AccountNumberToAccountTxtNumber(TOpListAccount(Operation).Data.account_to_pay);
+        end;
+        CT_OpSubtype_ListAccountForAccountSwap:  begin
+            OperationResume.OpSubtype := CT_OpSubtype_ListAccountForAccountSwap;
+            OperationResume.OperationTxt :=
+            'List atomic account swap '+
+            ' for account ' + TAccountComp.AccountNumberToAccountTxtNumber(TOpListAccountForSale(Operation).Data.account_target) +
+            ' hash-locked by ' + TCrypto.ToHexaString( TBaseType.ToRawBytes( TOpListAccountForSale(Operation).Data.hash_lock) ) +
+            ' time-locked until block ' + inttostr(TOpListAccountForSale(Operation).Data.locked_until_block) +
+            ' to counterparty key ' + TAccountComp.AccountPublicKeyExport( TOpListAccountForSale(Operation).Data.new_public_key);
+        end;
+        CT_OpSubtype_ListAccountForCoinSwap:  begin
+            OperationResume.OpSubtype := CT_OpSubtype_ListAccountForCoinSwap;
+            OperationResume.OperationTxt :=
+            'List atomic coin swap '+TAccountComp.AccountNumberToAccountTxtNumber(TOpListAccountForSale(Operation).Data.account_target)+
+            ' for ' + TAccountComp.FormatMoney(TOpListAccountForSale(Operation).Data.account_price) + ' PASC' +
+            ' hash-locked by ' + TCrypto.ToHexaString( TBaseType.ToRawBytes( TOpListAccountForSale(Operation).Data.hash_lock) ) +
+            ' time-locked until block ' + inttostr(TOpListAccountForSale(Operation).Data.locked_until_block) +
+            ' to counterparty account ' + TAccountComp.AccountNumberToAccountTxtNumber(TOpListAccountForSale(Operation).Data.account_to_pay);
+        end;
       end;
       OperationResume.newKey := TOpListAccount(Operation).Data.new_public_key;
       OperationResume.SellerAccount := Operation.SellerAccount;

+ 4 - 0
src/core/UConst.pas

@@ -105,6 +105,8 @@ Const
   CT_Default_EC_OpenSSL_NID = CT_NID_secp256k1;
 
   CT_AccountInfo_ForSale = 1000;
+  CT_AccountInfo_ForAccountSwap = 1001;
+  CT_AccountInfo_ForCoinSwap = 1002;
 
   CT_PROTOCOL_1 = 1;
   CT_PROTOCOL_2 = 2;
@@ -163,6 +165,8 @@ Const
   CT_OpSubtype_Recover                    = 31;
   CT_OpSubtype_ListAccountForPublicSale   = 41;
   CT_OpSubtype_ListAccountForPrivateSale  = 42;
+  CT_OpSubtype_ListAccountForAccountSwap  = 43;
+  CT_OpSubtype_ListAccountForCoinSwap     = 44;
   CT_OpSubtype_DelistAccount              = 51;
   CT_OpSubtype_BuyAccountBuyer            = 61;
   CT_OpSubtype_BuyAccountTarget           = 62;

+ 2 - 2
src/core/UNetProtocol.pas

@@ -2672,7 +2672,7 @@ begin
       if not DataBuffer.Read(optype,1)=1 then exit;
       opclass := TPCOperationsComp.GetOperationClassByOpType(optype);
       if Not Assigned(opclass) then exit;
-      op := opclass.Create;
+      op := opclass.Create(Self.NetProtocolVersion.protocol_version);
       Try
         op.LoadFromNettransfer(DataBuffer);
         operations.AddOperationToHashTree(op);
@@ -3770,7 +3770,7 @@ var operationsComp : TPCOperationsComp;
     end;
     // Now we have nfpboarr with full data
     for i := 0 to High(nfpboarr) do begin
-      auxOp := TPCOperation.GetOperationFromStreamData( nfpboarr[i].opStreamData );
+      auxOp := TPCOperation.GetOperationFromStreamData(Self.FNetProtocolVersion.protocol_version,  nfpboarr[i].opStreamData );
       if not Assigned(auxOp) then begin
         errors := Format('Op index not available (%d/%d) OpReference:%d size:%d',[i,High(nfpboarr),nfpboarr[i].opReference,Length(nfpboarr[i].opStreamData)]);
         Exit;

+ 334 - 184
src/core/UOpTransaction.pas

@@ -1,7 +1,10 @@
-unit UOpTransaction;
+unit UOpTransaction;
 
 { Copyright (c) 2016 by Albert Molina
 
+  Acknowledgements:
+  - Herman Schoenfeld for implementing atomic swap operations (HLTC)
+
   Distributed under the MIT software license, see the accompanying file LICENSE
   or visit http://www.opensource.org/licenses/mit-license.php.
 
@@ -28,7 +31,7 @@ Uses UCrypto, UBlockChain, Classes, UAccounts, UBaseTypes,
 
 Type
   // Operations Type
-  TOpTransactionStyle = (transaction, transaction_with_auto_buy_account, buy_account);
+  TOpTransactionStyle = (transaction, transaction_with_auto_buy_account, buy_account, atomic_swap, transaction_with_auto_atomic_swap);
   TOpTransactionData = Record
     sender: Cardinal;
     n_operation : Cardinal;
@@ -44,6 +47,7 @@ Type
     AccountPrice : UInt64;
     SellerAccount : Cardinal;
     new_accountkey : TAccountKey;
+
   End;
 
   TOpChangeKeyData = Record
@@ -81,7 +85,7 @@ Type
     procedure FillOperationResume(Block : Cardinal; getInfoForAllAccounts : Boolean; Affected_account_number : Cardinal; var OperationResume : TOperationResume); override;
   public
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
-    function DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : String) : Boolean; override;
+    function DoOperation(APrevious : TAccountPreviousBlockInfo; ASafeBoxTransaction : TPCSafeBoxTransaction; var AErrors : String) : Boolean; override;
     procedure AffectedAccounts(list : TList<Cardinal>); override;
     //
     class function OpType : Byte; override;
@@ -95,7 +99,7 @@ Type
     function OperationAmountByAccount(account : Cardinal) : Int64; override;
     Property Data : TOpTransactionData read FData;
 
-    Constructor CreateTransaction(current_protocol : Word; sender, n_operation, target: Cardinal; key: TECPrivateKey; amount, fee: UInt64; payload: TRawBytes);
+    Constructor CreateTransaction(ACurrentProtocol : Word; sender, n_operation, target: Cardinal; key: TECPrivateKey; amount, fee: UInt64; payload: TRawBytes);
     Function toString : String; Override;
     Function GetDigestToSign(current_protocol : Word) : TRawBytes; override;
 
@@ -125,7 +129,7 @@ Type
     function N_Operation : Cardinal; override;
     procedure AffectedAccounts(list : TList<Cardinal>); override;
     function OperationAmountByAccount(account : Cardinal) : Int64; override;
-    Constructor Create(current_protocol : Word; account_signer, n_operation, account_target: Cardinal; key:TECPrivateKey; new_account_key : TAccountKey; fee: UInt64; payload: TRawBytes);
+    Constructor Create(ACurrentProtocol : Word; account_signer, n_operation, account_target: Cardinal; key:TECPrivateKey; new_account_key : TAccountKey; fee: UInt64; payload: TRawBytes);
     Property Data : TOpChangeKeyData read FData;
     Function toString : String; Override;
     Function GetDigestToSign(current_protocol : Word) : TRawBytes; override;
@@ -163,14 +167,14 @@ Type
     function N_Operation : Cardinal; override;
     function OperationAmountByAccount(account : Cardinal) : Int64; override;
     procedure AffectedAccounts(list : TList<Cardinal>); override;
-    Constructor Create(account_number, n_operation: Cardinal; fee: UInt64);
+    Constructor Create(ACurrentProtocol : word; account_number, n_operation: Cardinal; fee: UInt64);
     Property Data : TOpRecoverFoundsData read FData;
     Function toString : String; Override;
     Function GetDigestToSign(current_protocol : Word) : TRawBytes; override;
     function IsValidSignatureBasedOnCurrentSafeboxState(ASafeBoxTransaction : TPCSafeBoxTransaction) : Boolean; override;
   End;
 
-  // NEW OPERATIONS PROTOCOL 2
+  // NEW OPERATIONS PROTOCOL 2 (note: lat_SetHLTC* added in v5 as a separate network operation implemented as a private sale subclass)
   TOpListAccountOperationType = (lat_Unknown, lat_ListForSale, lat_DelistAccount);
 
   TOpListAccountData = Record
@@ -178,9 +182,11 @@ Type
     account_target: Cardinal;
     operation_type : TOpListAccountOperationType;
     n_operation : Cardinal;
+    account_state: TAccountState;
     account_price: UInt64;
     account_to_pay : Cardinal;
     fee: UInt64;
+    hash_lock : T32Bytes;
     payload: TRawBytes;
     public_key: TAccountKey;
     new_public_key: TAccountKey;   // If EC_OpenSSL_NID=0 then is OPEN, otherwise is for only 1 public key
@@ -211,6 +217,7 @@ Const
 Type
 
   { TOpListAccount }
+
   TOpListAccount = Class(TPCOperation)
   private
     FData : TOpListAccountData;
@@ -220,9 +227,6 @@ Type
     function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
     procedure FillOperationResume(Block : Cardinal; getInfoForAllAccounts : Boolean; Affected_account_number : Cardinal; var OperationResume : TOperationResume); override;
   public
-    Function IsPrivateSale : Boolean;
-    Function IsDelist : Boolean; virtual; abstract;
-
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
     function DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : String) : Boolean; override;
     function OperationAmount : Int64; override;
@@ -241,17 +245,18 @@ Type
   End;
 
   TOpListAccountForSale = Class(TOpListAccount)
+  private
+    FSubType : Integer;
   public
     class function OpType : Byte; override;
-    Constructor CreateListAccountForSale(current_protocol : Word; account_signer, n_operation, account_target: Cardinal; account_price, fee : UInt64; account_to_pay:Cardinal; new_public_key:TAccountKey; locked_until_block : Cardinal; key:TECPrivateKey; payload: TRawBytes);
-    Function IsDelist : Boolean; override;
+    Constructor CreateListAccountForSale(ACurrentProtocol : Word; AListOpSubType : Integer; AAccountSigner, ANOperation, AAccountTarget: Cardinal; AAccountPrice, AFee: UInt64; AAccountToPay: Cardinal;  ANewPublicKey: TAccountKey; ALockedUntilBlock: Cardinal; AKey: TECPrivateKey; const AHashLock : T32Bytes; const APayload: TRawBytes);
+    property SubType : Integer read FSubType;
   End;
 
   TOpDelistAccountForSale = Class(TOpListAccount)
   public
     class function OpType : Byte; override;
-    Constructor CreateDelistAccountForSale(current_protocol : Word; account_signer, n_operation, account_target: Cardinal; fee: UInt64; key: TECPrivateKey; payload: TRawBytes);
-    Function IsDelist : Boolean; override;
+    Constructor CreateDelistAccountForSale(ACurrentProtocol : Word; account_signer, n_operation, account_target: Cardinal; fee: UInt64; key: TECPrivateKey; payload: TRawBytes);
   End;
 
   { TOpBuyAccount }
@@ -261,7 +266,7 @@ Type
     procedure InitializeData; override;
   public
     class function OpType : Byte; override;
-    Constructor CreateBuy(current_protocol : Word; account_number, n_operation, account_to_buy, account_to_pay: Cardinal; price, amount, fee : UInt64; new_public_key:TAccountKey; key:TECPrivateKey; payload: TRawBytes);
+    Constructor CreateBuy(ACurrentProtocol : Word; account_number, n_operation, account_to_buy, account_to_pay: Cardinal; price, amount, fee : UInt64; new_public_key:TAccountKey; key:TECPrivateKey; payload: TRawBytes);
   End;
 
   { TOpChangeAccountInfo }
@@ -287,7 +292,7 @@ Type
     function N_Operation : Cardinal; override;
     procedure AffectedAccounts(list : TList<Cardinal>); override;
     function OperationAmountByAccount(account : Cardinal) : Int64; override;
-    Constructor CreateChangeAccountInfo(current_protocol : word;
+    Constructor CreateChangeAccountInfo(ACurrentProtocol : word;
       account_signer, n_operation, account_target: Cardinal; key:TECPrivateKey;
       change_key : Boolean; const new_account_key : TAccountKey;
       change_name: Boolean; const new_name : TRawBytes;
@@ -337,12 +342,7 @@ Type
     function N_Operation : Cardinal; override;
     procedure AffectedAccounts(list : TList<Cardinal>); override;
     function OperationAmountByAccount(account : Cardinal) : Int64; override;
-    Constructor CreateOpData(
-      account_signer, account_sender, account_target : Cardinal; signer_key:TECPrivateKey;
-      n_operation : Cardinal;
-      dataType, dataSequence : Word;
-      amount, fee : UInt64;
-      payload: TRawBytes);
+    Constructor CreateOpData( ACurrentProtocol : word; account_signer, account_sender, account_target : Cardinal; signer_key:TECPrivateKey; n_operation : Cardinal; dataType, dataSequence : Word; amount, fee : UInt64; payload: TRawBytes);
     Property Data : TOpDataData read FData;
     Function toString : String; Override;
     Function GetDigestToSign(current_protocol : Word) : TRawBytes; override;
@@ -628,14 +628,14 @@ begin
   else Result := 0;
 end;
 
-constructor TOpChangeAccountInfo.CreateChangeAccountInfo(current_protocol : word;
+constructor TOpChangeAccountInfo.CreateChangeAccountInfo(ACurrentProtocol : word;
   account_signer, n_operation,
   account_target: Cardinal; key: TECPrivateKey; change_key: Boolean;
   const new_account_key: TAccountKey; change_name: Boolean;
   const new_name: TRawBytes; change_type: Boolean; const new_type: Word;
   fee: UInt64; payload: TRawBytes);
 begin
-  inherited Create;
+  inherited Create(ACurrentProtocol);
   FData.account_signer:=account_signer;
   FData.account_target:=account_target;
   FData.n_operation:=n_operation;
@@ -658,7 +658,7 @@ begin
   end;
 
   if Assigned(key) then begin
-    FData.sign := TCrypto.ECDSASign(key.PrivateKey, GetDigestToSign(current_protocol));
+    FData.sign := TCrypto.ECDSASign(key.PrivateKey, GetDigestToSign(ACurrentProtocol));
     FHasValidSignature := true;
     FUsedPubkeyForSignature := key.PublicKey;
   end else begin
@@ -726,16 +726,16 @@ procedure TOpTransaction.AffectedAccounts(list: TList<Cardinal>);
 begin
   list.Add(FData.sender);
   list.Add(FData.target);
-  if (FData.opTransactionStyle in [transaction_with_auto_buy_account, buy_account]) then begin
+  if (FData.opTransactionStyle in [transaction_with_auto_buy_account, buy_account, atomic_swap, transaction_with_auto_atomic_swap]) then begin
     list.Add(FData.SellerAccount);
   end;
 end;
 
-constructor TOpTransaction.CreateTransaction(current_protocol : Word;
+constructor TOpTransaction.CreateTransaction(ACurrentProtocol : Word;
   sender, n_operation, target: Cardinal;
   key: TECPrivateKey; amount, fee: UInt64; payload: TRawBytes);
 begin
-  inherited Create;
+  inherited Create(ACurrentProtocol);
   FData.sender := sender;
   FData.n_operation := n_operation;
   FData.target := target;
@@ -745,7 +745,7 @@ begin
   // V2: No need to store public key because it's at safebox. Saving at least 64 bytes!
   // FData.public_key := key.PublicKey;
   if Assigned(key) then begin
-    FData.sign := TCrypto.ECDSASign(key.PrivateKey, GetDigestToSign(current_protocol));
+    FData.sign := TCrypto.ECDSASign(key.PrivateKey, GetDigestToSign(ACurrentProtocol));
     FHasValidSignature := true;
     FUsedPubkeyForSignature := key.PublicKey;
   end else begin
@@ -754,156 +754,215 @@ begin
   end;
 end;
 
-function TOpTransaction.DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : String) : Boolean;
+function TOpTransaction.DoOperation(APrevious : TAccountPreviousBlockInfo; ASafeBoxTransaction : TPCSafeBoxTransaction; var AErrors : String) : Boolean;
 Var s_new, t_new : Int64;
-  totalamount : Cardinal;
-  sender,target,seller : TAccount;
-  _IsBuyTransaction :  Boolean;
+  LTotalAmount : Cardinal;
+  LSender,LTarget,LSeller : TAccount;
 begin
   Result := false;
-  errors := '';
-  //
-  if (FData.sender>=AccountTransaction.FreezedSafeBox.AccountsCount) then begin
-    errors := Format('Invalid sender %d',[FData.sender]);
+  AErrors := '';
+
+  {$region 'Common Validation'}
+
+  if (FData.sender>=ASafeBoxTransaction.FreezedSafeBox.AccountsCount) then begin
+    AErrors := Format('Invalid sender %d',[FData.sender]);
     Exit;
   end;
-  if (FData.target>=AccountTransaction.FreezedSafeBox.AccountsCount) then begin
-    errors := Format('Invalid target %d',[FData.target]);
+
+  if (FData.target>=ASafeBoxTransaction.FreezedSafeBox.AccountsCount) then begin
+    AErrors := Format('Invalid target %d',[FData.target]);
     Exit;
   end;
   if (FData.sender=FData.target) then begin
-    errors := Format('Sender=Target %d',[FData.sender]);
+    AErrors := Format('Sender=Target %d',[FData.sender]);
     Exit;
   end;
-  if TAccountComp.IsAccountBlockedByProtocol(FData.sender,AccountTransaction.FreezedSafeBox.BlocksCount) then begin
-    errors := Format('sender (%d) is blocked for protocol',[FData.sender]);
+  if TAccountComp.IsAccountBlockedByProtocol(FData.sender,ASafeBoxTransaction.FreezedSafeBox.BlocksCount) then begin
+    AErrors := Format('sender (%d) is blocked for protocol',[FData.sender]);
     Exit;
   end;
-  if TAccountComp.IsAccountBlockedByProtocol(FData.target,AccountTransaction.FreezedSafeBox.BlocksCount) then begin
-    errors := Format('target (%d) is blocked for protocol',[FData.target]);
+  if TAccountComp.IsAccountBlockedByProtocol(FData.target,ASafeBoxTransaction.FreezedSafeBox.BlocksCount) then begin
+    AErrors := Format('target (%d) is blocked for protocol',[FData.target]);
     Exit;
   end;
   if (FData.amount<=0) Or (FData.amount>CT_MaxTransactionAmount) then begin
-    errors := Format('Invalid amount %d (0 or max: %d)',[FData.amount,CT_MaxTransactionAmount]);
+    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
-    errors := Format('Invalid fee %d (max %d)',[FData.fee,CT_MaxTransactionFee]);
+    AErrors := Format('Invalid fee %d (max %d)',[FData.fee,CT_MaxTransactionFee]);
     Exit;
   end;
   if (length(FData.payload)>CT_MaxPayloadSize) then begin
-    errors := 'Invalid Payload size:'+inttostr(length(FData.payload))+' (Max: '+inttostr(CT_MaxPayloadSize)+')';
-    If (AccountTransaction.FreezedSafeBox.CurrentProtocol>=CT_PROTOCOL_2) then begin
+    AErrors := 'Invalid Payload size:'+inttostr(length(FData.payload))+' (Max: '+inttostr(CT_MaxPayloadSize)+')';
+    If (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol>=CT_PROTOCOL_2) then begin
       Exit; // BUG from protocol 1
     end;
   end;
 
-  sender := AccountTransaction.Account(FData.sender);
-  target := AccountTransaction.Account(FData.target);
-  if ((sender.n_operation+1)<>FData.n_operation) then begin
-    errors := Format('Invalid n_operation %d (expected %d)',[FData.n_operation,sender.n_operation+1]);
+  LSender := ASafeBoxTransaction.Account(FData.sender);
+  LTarget := ASafeBoxTransaction.Account(FData.target);
+  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]);
     Exit;
   end;
-  totalamount := FData.amount + FData.fee;
-  if (sender.balance<totalamount) then begin
-    errors := Format('Insuficient founds %d < (%d + %d = %d)',[sender.balance,FData.amount,FData.fee,totalamount]);
+  LTotalAmount := FData.amount + FData.fee;
+  if (LSender.balance<LTotalAmount) then begin
+    AErrors := Format('Insufficient funds %d < (%d + %d = %d)',[LSender.balance,FData.amount,FData.fee,LTotalAmount]);
     Exit;
   end;
-  if (target.balance+FData.amount>CT_MaxWalletAmount) then begin
-    errors := Format('Target cannot accept this transaction due to max amount %d+%d=%d > %d',[target.balance,FData.amount,target.balance+FData.amount,CT_MaxWalletAmount]);
+  if (LTarget.balance+FData.amount>CT_MaxWalletAmount) then begin
+    AErrors := Format('Target cannot accept this transaction due to max amount %d+%d=%d > %d',[LTarget.balance,FData.amount,LTarget.balance+FData.amount,CT_MaxWalletAmount]);
     Exit;
   end;
   // Is locked? Protocol 2 check
-  if (TAccountComp.IsAccountLocked(sender.accountInfo,AccountTransaction.FreezedSafeBox.BlocksCount)) then begin
-    errors := 'Sender Account is currently locked';
+  if (TAccountComp.IsAccountLocked(LSender.accountInfo,ASafeBoxTransaction.FreezedSafeBox.BlocksCount)) then begin
+    AErrors := 'Sender Account is currently locked';
     exit;
   end;
   // Build 1.4
-  If (FData.public_key.EC_OpenSSL_NID<>CT_TECDSA_Public_Nul.EC_OpenSSL_NID) And (Not TAccountComp.EqualAccountKeys(FData.public_key,sender.accountInfo.accountkey)) then begin
-    errors := Format('Invalid sender public key for account %d. Distinct from SafeBox public key! %s <> %s',[
+  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
+    AErrors := Format('Invalid sender public key for account %d. Distinct from SafeBox public key! %s <> %s',[
       FData.sender,
       TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(FData.public_key)),
-      TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(sender.accountInfo.accountkey))]);
+      TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(LSender.accountInfo.accountkey))]);
     exit;
   end;
-
   // Check signature
-  If Not IsValidECDSASignature(sender.accountInfo.accountkey,AccountTransaction.FreezedSafeBox.CurrentProtocol,FData.sign) then begin
-    errors := 'Invalid ECDSA signature';
+  If Not IsValidECDSASignature(LSender.accountInfo.accountkey, ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol, FData.sign) then begin
+    AErrors := 'Invalid ECDSA signature';
     Exit;
   end;
 
-  FPrevious_Signer_updated_block := sender.updated_block;
-  FPrevious_Destination_updated_block := target.updated_block;
+  {$endregion}
 
+  FPrevious_Signer_updated_block := LSender.updated_block;
+  FPrevious_Destination_updated_block := LTarget.updated_block;
 
   // Is buy account ?
-  if (FData.opTransactionStyle=buy_Account) then begin
-    if (AccountTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_2) then begin
-      errors := 'Buy account is not allowed on Protocol 1';
+  if (FData.opTransactionStyle = buy_Account ) then begin
+    {$region 'Buy Account Validation'}
+    if (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_2) then begin
+      AErrors := 'Buy account is not allowed on Protocol 1';
+      exit;
+    end;
+
+    if (TAccountComp.IsAccountForSwap(LTarget.accountInfo) AND (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_5)) then begin
+      AErrors := 'Atomic swaps are not allowed until Protocol 5';
       exit;
     end;
 
-    seller := AccountTransaction.Account(FData.SellerAccount);
-    if Not (TAccountComp.IsAccountForSale(target.accountInfo)) then begin
-      errors := Format('%d is not for sale',[target.account]);
+    LSeller := ASafeBoxTransaction.Account(FData.SellerAccount);
+    if Not TAccountComp.IsAccountForSaleOrSwap(LTarget.accountInfo) then begin
+      AErrors := Format('%d is not for sale or swap',[LTarget.account]);
       exit;
     end;
     // Check that seller is the expected seller
-    If (target.accountInfo.account_to_pay<>seller.account) then begin
-      errors := Format('Seller account %d is not expected account %d',[FData.SellerAccount,target.accountInfo.account_to_pay]);
+    If (LTarget.accountInfo.account_to_pay<>LSeller.account) then begin
+      AErrors := Format('Seller account %d is not expected account %d',[FData.SellerAccount,LTarget.accountInfo.account_to_pay]);
       exit;
     end;
-    if (target.balance + FData.amount < target.accountInfo.price) then begin
-      errors := Format('Account %d balance (%d) + amount (%d) < price (%d)',[target.account,target.balance,FData.amount,target.accountInfo.price]);
+    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]);
       exit;
     end;
-    if (FData.AccountPrice<>target.accountInfo.price) then begin
-      errors := Format('Signed price (%d) is not the same of account price (%d)',[FData.AccountPrice,target.accountInfo.price]);
+    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]);
       exit;
     end;
-    If Not (TAccountComp.IsValidAccountKey(FData.new_accountkey,errors)) then exit; // BUG 20171511
-    _IsBuyTransaction := True;
-    FPrevious_Seller_updated_block := seller.updated_block;
-  end else if
-    // Is automatic buy account?
-    (FData.opTransactionStyle = transaction_with_auto_buy_account)
-    Or // New automatic buy ?
-    ( (AccountTransaction.FreezedSafeBox.CurrentProtocol>=CT_PROTOCOL_2) And
-      (FData.opTransactionStyle=transaction) And
-      (TAccountComp.IsAccountForSaleAcceptingTransactions(target.accountInfo)) And
-      (target.balance + FData.amount >= target.accountInfo.price) ) then begin
 
-    if (AccountTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_2) then begin
-      errors := 'Tx-Buy account is not allowed on Protocol 1';
+    If Not (TAccountComp.IsValidAccountKey(FData.new_accountkey,AErrors)) then exit; // BUG 20171511
+    {$endregion}
+    FPrevious_Seller_updated_block := LSeller.updated_block;
+  end else if // (is auto buy) OR (is transaction that can buy)
+              (FData.opTransactionStyle = transaction_with_auto_buy_account) OR
+              (
+                (FData.opTransactionStyle = transaction) AND
+                (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol >= CT_PROTOCOL_2) AND
+                (TAccountComp.IsAccountForSaleOrSwapAcceptingTransactions(LTarget, FData.payload)) AND
+                ((LTarget.balance + FData.amount >= LTarget.accountInfo.price))
+              )  then begin
+    {$region 'Transaction Auto Buy Validation'}
+    if (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_2) then begin
+      AErrors := 'Tx-Buy account is not allowed on Protocol 1';
       exit;
     end;
-    If Not (TAccountComp.IsValidAccountKey(FData.new_accountkey,errors)) then exit; // BUG 20171511
 
-    _IsBuyTransaction := true; // Automatic buy
+    if (TAccountComp.IsAccountForSwap( LTarget.accountInfo ) AND (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_5)) then begin
+      AErrors := 'Tx-Buy atomic swaps are not allowed until Protocol 5';
+      exit;
+    end else If Not (TAccountComp.IsValidAccountKey(FData.new_accountkey,AErrors)) then exit; // BUG 20171511
+
     // Fill the purchase data
     FData.opTransactionStyle := transaction_with_auto_buy_account; // Set this data!
-    FData.AccountPrice := target.accountInfo.price;
-    FData.SellerAccount := target.accountInfo.account_to_pay;
-    seller := AccountTransaction.Account(target.accountInfo.account_to_pay);
-    FPrevious_Seller_updated_block := seller.updated_block;
-    FData.new_accountkey := target.accountInfo.new_publicKey;
-  end else begin
-    _IsBuyTransaction := false;
+    FData.AccountPrice := LTarget.accountInfo.price;
+    FData.SellerAccount := LTarget.accountInfo.account_to_pay;
+    LSeller := ASafeBoxTransaction.Account(LTarget.accountInfo.account_to_pay);
+    FPrevious_Seller_updated_block := LSeller.updated_block;
+    FData.new_accountkey := LTarget.accountInfo.new_publicKey;
+    {$endregion}
+  end else if (FData.opTransactionStyle <> transaction) then begin
+     AErrors := 'INTERNAL ERROR: 477C2A3C53C34E63A6B82C057741C44D';
+     exit;
+  end;
+
+  // 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
+    // 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
+    if NOT TAccountComp.EqualAccountKeys(LTarget.accountInfo.accountKey, FData.new_accountkey) then begin
+      AErrors := 'Target key cannot be changed during atomic coin swap';
+      exit;
+    end;
+
   end;
 
-  if (_IsBuyTransaction) then begin
-    if (AccountTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_2) then begin
-      errors := 'NOT ALLOWED ON PROTOCOL 1';
+
+  if (FData.opTransactionStyle in [buy_account, transaction_with_auto_buy_account]) then begin
+    // account purchase
+    if (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_2) then begin
+      AErrors := 'NOT ALLOWED ON PROTOCOL 1';
+      exit;
+    end;
+
+    if (LTarget.accountInfo.state in [as_ForAtomicAccountSwap, as_ForAtomicCoinSwap]) AND
+       (ASafeBoxTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_5) then begin
+      AErrors := 'NOT ALLOWED UNTIL PROTOCOL 5';
       exit;
     end;
-    Result := AccountTransaction.BuyAccount(AccountPreviousUpdatedBlock,
+
+    Result := ASafeBoxTransaction.BuyAccount(
+      APrevious,
       GetOpID,
-      sender.account,target.account,seller.account,FData.n_operation,FData.amount,target.accountInfo.price,FData.fee,FData.new_accountkey,errors);
+      LSender.account,
+      LTarget.account,
+      LSeller.account,
+      FData.n_operation,
+      FData.amount,
+      LTarget.accountInfo.price,
+      FData.fee,
+      FData.new_accountkey,
+      FData.payload,
+      AErrors
+    );
+
   end else begin
-    Result := AccountTransaction.TransferAmount(AccountPreviousUpdatedBlock,
+    // Standard transaction
+    Result := ASafeBoxTransaction.TransferAmount(
+      APrevious,
       GetOpID,
-      FData.sender,FData.sender,FData.target,FData.n_operation,FData.amount,FData.fee,errors);
+      FData.sender,
+      FData.sender,
+      FData.target,
+      FData.n_operation,
+      FData.amount,
+      FData.fee,AErrors
+    );
   end;
 end;
 
@@ -1197,9 +1256,9 @@ begin
   else Result := 0;
 end;
 
-constructor TOpChangeKey.Create(current_protocol : Word; account_signer, n_operation, account_target: Cardinal; key:TECPrivateKey; new_account_key : TAccountKey; fee: UInt64; payload: TRawBytes);
+constructor TOpChangeKey.Create(ACurrentProtocol : Word; account_signer, n_operation, account_target: Cardinal; key:TECPrivateKey; new_account_key : TAccountKey; fee: UInt64; payload: TRawBytes);
 begin
-  inherited Create;
+  inherited Create(ACurrentProtocol);
   FData.account_signer := account_signer;
   FData.account_target := account_target;
   If (OpType=CT_Op_Changekey) then begin
@@ -1214,7 +1273,7 @@ begin
   // FData.public_key := key.PublicKey;
   FData.new_accountkey := new_account_key;
   if Assigned(key) then begin
-    FData.sign := TCrypto.ECDSASign(key.PrivateKey, GetDigestToSign(current_protocol));
+    FData.sign := TCrypto.ECDSASign(key.PrivateKey, GetDigestToSign(ACurrentProtocol));
     FHasValidSignature := true;
     FUsedPubkeyForSignature := key.PublicKey;
   end else begin
@@ -1534,9 +1593,9 @@ begin
   list.Add(FData.account);
 end;
 
-constructor TOpRecoverFounds.Create(account_number, n_operation : Cardinal; fee: UInt64);
+constructor TOpRecoverFounds.Create(ACurrentProtocol : word; account_number, n_operation : Cardinal; fee: UInt64);
 begin
-  inherited Create;
+  inherited Create(ACurrentProtocol);
   FData.account := account_number;
   FData.n_operation := n_operation;
   FData.fee := fee;
@@ -1701,13 +1760,48 @@ begin
 end;
 
 function TOpListAccount.DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : String) : Boolean;
-Var account_signer, account_target : TAccount;
+Var
+  account_signer, account_target : TAccount;
+  LIsDelist, LIsSale, LIsPrivateSale, LIsPublicSale, LIsSwap, LIsAccountSwap, LIsCoinSwap : boolean;
 begin
   Result := false;
+  // Determine which flow this function will execute
+  LIsDelist := OpType = CT_Op_DelistAccount;
+  LIsSale := OpType = CT_Op_ListAccountForSale;
+  if LIsSale then begin
+    if (FData.account_state = as_ForSale) and (FData.new_public_key.EC_OpenSSL_NID <> CT_TECDSA_Public_Nul.EC_OpenSSL_NID) then begin
+      LIsPublicSale := false;
+      LIsPrivateSale := true;
+    end else begin
+      LIsPublicSale := true;
+      LIsPrivateSale := false;
+    end;
+  end else begin
+    LIsPublicSale := false;
+    LIsPrivateSale := false;
+  end;
+  LIsCoinSwap := FData.account_state in [as_ForAtomicAccountSwap, as_ForAtomicCoinSwap];
+  if LIsCoinSwap then begin
+    if FData.account_state =  as_ForAtomicCoinSwap then begin
+      LIsAccountSwap := false;
+      LIsCoinSwap := true;
+    end else begin
+      LIsAccountSwap := true;
+      LIsCoinSwap := false;
+    end;
+  end else begin
+    LIsAccountSwap := false;
+    LIsCoinSwap := false;
+  end;
+
   if (AccountTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_2) then begin
     errors := 'List/Delist Account is not allowed on Protocol 1';
     exit;
   end;
+  if LIsSwap AND (AccountTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_5) then begin
+    errors := 'Atomic Swaps are not allowed before Protocol 5';
+    exit;
+  end;
   if (FData.account_signer>=AccountTransaction.FreezedSafeBox.AccountsCount) then begin
     errors := 'Invalid signer account number';
     Exit;
@@ -1724,7 +1818,7 @@ begin
     errors := 'Target account is blocked for protocol';
     Exit;
   end;
-  if Not IsDelist then begin
+  if NOT LIsDelist then begin
     if (FData.account_to_pay>=AccountTransaction.FreezedSafeBox.AccountsCount) then begin
       errors := 'Invalid account to pay number';
       Exit;
@@ -1737,7 +1831,7 @@ begin
       errors := 'Account to pay is blocked for protocol';
       Exit;
     end;
-    if (FData.account_price<=0) then begin
+    if (NOT LIsSwap) AND (FData.account_price<=0) then begin
       errors := 'Account for sale price must be > 0';
       exit;
     end;
@@ -1745,7 +1839,7 @@ begin
       errors := 'Invalid locked block: Current block '+Inttostr(AccountTransaction.FreezedSafeBox.BlocksCount)+' cannot lock to block '+IntToStr(FData.locked_until_block);
       exit;
     end;
-    if IsPrivateSale then begin
+    if LIsPrivateSale OR LIsAccountSwap then begin
       If Not TAccountComp.IsValidAccountKey( FData.new_public_key, errors ) then begin
         errors := 'Invalid new public key: '+errors;
         exit;
@@ -1787,15 +1881,14 @@ begin
     errors := 'Target account is currently locked';
     exit;
   end;
-  if (IsPrivateSale) then begin
+  if LIsPrivateSale OR LIsAccountSwap then begin
     if TAccountComp.EqualAccountKeys(account_target.accountInfo.accountKey,FData.new_public_key) then begin
       errors := 'New public key for private sale is the same public key';
       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,account_signer.accountInfo.accountkey)) then begin
     errors := Format('Invalid public key for account %d. Distinct from SafeBox public key! %s <> %s',[
       FData.account_signer,
@@ -1812,27 +1905,36 @@ begin
 
   FPrevious_Signer_updated_block := account_signer.updated_block;
   FPrevious_Destination_updated_block := account_target.updated_block;
-  if IsDelist then begin
+
+  if LIsDelist then begin
     account_target.accountInfo.state := as_Normal;
     account_target.accountInfo.locked_until_block := CT_AccountInfo_NUL.locked_until_block;
     account_target.accountInfo.price := CT_AccountInfo_NUL.price;
     account_target.accountInfo.account_to_pay := CT_AccountInfo_NUL.account_to_pay;
     account_target.accountInfo.new_publicKey := CT_AccountInfo_NUL.new_publicKey;
   end else begin
-    account_target.accountInfo.state := as_ForSale;
+    account_target.accountInfo.state := FData.account_state;
     account_target.accountInfo.locked_until_block := FData.locked_until_block;
     account_target.accountInfo.price := FData.account_price;
     account_target.accountInfo.account_to_pay := FData.account_to_pay;
     account_target.accountInfo.new_publicKey := FData.new_public_key;
+    if LIsSwap then
+      account_target.account_data := TBaseType.ToRawBytes( FData.hash_lock );
   end;
-  Result := AccountTransaction.UpdateAccountInfo(AccountPreviousUpdatedBlock,
-         GetOpID,
-         FData.account_signer,FData.n_operation,FData.account_target,
-         account_target.accountInfo,
-         account_target.name,
-         account_target.account_data,
-         account_target.account_type,
-         FData.fee,errors);
+
+  Result := AccountTransaction.UpdateAccountInfo(
+    AccountPreviousUpdatedBlock,
+    GetOpID,
+    FData.account_signer,
+    FData.n_operation,
+    FData.account_target,
+    account_target.accountInfo,
+    account_target.name,
+    account_target.account_data,
+    account_target.account_type,
+    FData.fee,
+    errors
+  );
 end;
 
 function TOpListAccount.GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes;
@@ -1847,11 +1949,6 @@ begin
   FData := CT_TOpListAccountData_NUL;
 end;
 
-function TOpListAccount.IsPrivateSale: Boolean;
-begin
-  Result := (Not IsDelist) And (FData.new_public_key.EC_OpenSSL_NID<>0);
-end;
-
 function TOpListAccount.IsValidSignatureBasedOnCurrentSafeboxState(ASafeBoxTransaction: TPCSafeBoxTransaction): Boolean;
 var LAccount : TAccount;
 begin
@@ -1871,10 +1968,10 @@ begin
   case w of
     CT_Op_ListAccountForSale : FData.operation_type := lat_ListForSale;
     CT_Op_DelistAccount : FData.operation_type := lat_DelistAccount;
-  else exit; // Invalid data info
+    else exit; // Invalid data info
   end;
   Stream.Read(FData.n_operation,Sizeof(FData.n_operation));
-  if (FData.operation_type = lat_ListForSale) then begin
+  if (FData.operation_type in [lat_ListForSale]) then begin
     Stream.Read(FData.account_price,Sizeof(FData.account_price));
     Stream.Read(FData.account_to_pay,Sizeof(FData.account_to_pay));
     if Stream.Read(FData.public_key.EC_OpenSSL_NID,Sizeof(FData.public_key.EC_OpenSSL_NID))<0 then exit;
@@ -1883,6 +1980,12 @@ begin
     if TStreamOp.ReadAnsiString(Stream,raw)<0 then exit;
     FData.new_public_key := TAccountComp.RawString2Accountkey(raw);
     Stream.Read(FData.locked_until_block,Sizeof(FData.locked_until_block));
+    // VERSION 5: read the new account state and hash-lock
+    if FCurrentProtocol >= CT_PROTOCOL_5 then begin
+      if Stream.Read(w, 2) < 0 then exit;  // the new account state to set
+      FData.account_state := TAccountState(w);
+      if TStreamOp.ReadAnsiString(Stream, FData.hash_lock) < 0 then exit;  // the hash-lock if any
+    end;
   end;
   Stream.Read(FData.fee,Sizeof(FData.fee));
   if TStreamOp.ReadAnsiString(Stream,FData.payload)<0 then exit;
@@ -1892,6 +1995,8 @@ begin
 end;
 
 procedure TOpListAccount.FillOperationResume(Block: Cardinal; getInfoForAllAccounts: Boolean; Affected_account_number: Cardinal; var OperationResume: TOperationResume);
+var
+ LData : TMultiOpData;
 begin
   inherited FillOperationResume(Block, getInfoForAllAccounts, Affected_account_number, OperationResume);
   SetLength(OperationResume.Changers,1);
@@ -1908,6 +2013,8 @@ begin
         end;
         OperationResume.Changers[0].Seller_Account:=FData.account_to_pay;
         OperationResume.Changers[0].Account_Price:=FData.account_price;
+        if (FData.account_state in [as_ForAtomicAccountSwap, as_ForAtomicCoinSwap]) then
+          OperationResume.Changers[0].New_Data := TBaseType.ToRawBytes( FData.hash_lock );
       end;
     lat_DelistAccount : begin
         OperationResume.Changers[0].Changes_type:=[delist];
@@ -1959,7 +2066,7 @@ begin
   end;
   Stream.Write(w,2);
   Stream.Write(FData.n_operation,Sizeof(FData.n_operation));
-  if FData.operation_type=lat_ListForSale then begin
+  if FData.operation_type in [lat_ListForSale] then begin
     Stream.Write(FData.account_price,Sizeof(FData.account_price));
     Stream.Write(FData.account_to_pay,Sizeof(FData.account_to_pay));
     Stream.Write(FData.public_key.EC_OpenSSL_NID,Sizeof(FData.public_key.EC_OpenSSL_NID));
@@ -1967,6 +2074,12 @@ begin
     TStreamOp.WriteAnsiString(Stream,FData.public_key.y);
     TStreamOp.WriteAnsiString(Stream,TAccountComp.AccountKey2RawString(FData.new_public_key));
     Stream.Write(FData.locked_until_block,Sizeof(FData.locked_until_block));
+    // VERSION 5: write the new account state and hash-lock
+    if FCurrentProtocol >= CT_PROTOCOL_5 then begin
+      w := Word(FData.account_state);
+      Stream.Write(w, 2);  // the new account state to set
+      TStreamOp.WriteAnsiString(Stream, FData.hash_lock); // the hash-lock if any
+    end;
   end;
   Stream.Write(FData.fee,Sizeof(FData.fee));
   TStreamOp.WriteAnsiString(Stream,FData.payload);
@@ -1997,17 +2110,42 @@ function TOpListAccount.toString: String;
 begin
   case FData.operation_type of
     lat_ListForSale : begin
-      if (FData.new_public_key.EC_OpenSSL_NID=CT_TECDSA_Public_Nul.EC_OpenSSL_NID) then begin
-        Result := Format('List account %s for sale price %s locked until block:%d fee:%s (n_op:%d) payload size:%d',[
-          TAccountComp.AccountNumberToAccountTxtNumber(FData.account_target), TAccountComp.FormatMoney(FData.account_price),
-          FData.locked_until_block, TAccountComp.FormatMoney(FData.fee),
-          FData.n_operation, Length(FData.payload)])
-      end else begin
-        Result := Format('List account %s for private sale price %s reserved for %s locked until block:%d fee:%s (n_op:%d) payload size:%d',[
-          TAccountComp.AccountNumberToAccountTxtNumber(FData.account_target), TAccountComp.FormatMoney(FData.account_price),
-          TAccountComp.GetECInfoTxt(FData.new_public_key.EC_OpenSSL_NID),
-          FData.locked_until_block, TAccountComp.FormatMoney(FData.fee),
-          FData.n_operation, Length(FData.payload)])
+      case FData.account_state of
+        as_ForSale: begin
+          if (FData.new_public_key.EC_OpenSSL_NID=CT_TECDSA_Public_Nul.EC_OpenSSL_NID) then begin
+            Result := Format('List account %s for sale price %s locked until block:%d fee:%s (n_op:%d) payload size:%d',[
+              TAccountComp.AccountNumberToAccountTxtNumber(FData.account_target), TAccountComp.FormatMoney(FData.account_price),
+              FData.locked_until_block, TAccountComp.FormatMoney(FData.fee),
+              FData.n_operation, Length(FData.payload)])
+          end else begin
+            Result := Format('List account %s for private sale price %s reserved for %s locked until block:%d fee:%s (n_op:%d) payload size:%d',[
+              TAccountComp.AccountNumberToAccountTxtNumber(FData.account_target), TAccountComp.FormatMoney(FData.account_price),
+              TAccountComp.GetECInfoTxt(FData.new_public_key.EC_OpenSSL_NID),
+              FData.locked_until_block, TAccountComp.FormatMoney(FData.fee),
+              FData.n_operation, Length(FData.payload)])
+          end;
+        end;
+        as_ForAtomicAccountSwap: begin
+          Result := Format('List account %s for atomic account swap hash-lock:%s time-locked until block:%d fee:%s (n_op:%d) payload size:%d',[
+            TAccountComp.AccountNumberToAccountTxtNumber(FData.account_target),
+            TCrypto.ToHexaString( TBaseType.ToRawBytes( FData.hash_lock ) ),
+            FData.locked_until_block,
+            TAccountComp.FormatMoney(FData.fee),
+            FData.n_operation,
+            Length(FData.payload)]
+          );
+        end;
+        as_ForAtomicCoinSwap: begin
+          Result := Format('List account %s for atomic coin swap for %s PASC hash-lock:%s time-locked until block:%d fee:%s (n_op:%d) payload size:%d',[
+            TAccountComp.AccountNumberToAccountTxtNumber(FData.account_target),
+            TAccountComp.FormatMoney(FData.account_price),
+            TCrypto.ToHexaString( TBaseType.ToRawBytes( FData.hash_lock ) ),
+            FData.locked_until_block,
+            TAccountComp.FormatMoney(FData.fee),
+            FData.n_operation,
+            Length(FData.payload)]
+          );
+        end;
       end;
     end;
     lat_DelistAccount : begin
@@ -2023,11 +2161,14 @@ function TOpListAccount.GetDigestToSign(current_protocol : Word): TRawBytes;
 var ms : TMemoryStream;
   s : TRawBytes;
   b : Byte;
+  w : Word;
 begin
   ms := TMemoryStream.Create;
   try
     ms.Write(FData.account_signer,Sizeof(FData.account_signer));
     ms.Write(FData.account_target,Sizeof(FData.account_target));
+   // HS 2019-06-09: NOTE TO ALBERT on de-list, the below fields are included in the signable digest.
+   // This is unnecessary, but cannot be changed now.
     ms.Write(FData.n_operation,Sizeof(FData.n_operation));
     ms.Write(FData.account_price,Sizeof(FData.account_price));
     ms.Write(FData.account_to_pay,Sizeof(FData.account_to_pay));
@@ -2043,6 +2184,12 @@ begin
     if Length(s)>0 then
       ms.WriteBuffer(s[Low(s)],Length(s));
     ms.Write(FData.locked_until_block,Sizeof(FData.locked_until_block));
+    // VERSION 5: write the new account state and hash-lock
+    if (current_protocol >= CT_PROTOCOL_5) then begin
+      w := Word(FData.account_state);
+      ms.Write(w, 2);
+      TStreamOp.WriteAnsiString(ms, FData.hash_lock); // the hash-lock if any
+    end;
     if (current_protocol<=CT_PROTOCOL_3) then begin
       ms.Position := 0;
       SetLength(Result,ms.Size);
@@ -2059,50 +2206,58 @@ end;
 
 { TOpListAccountForSale }
 
-constructor TOpListAccountForSale.CreateListAccountForSale(current_protocol : Word; account_signer, n_operation, account_target: Cardinal;
-  account_price, fee: UInt64; account_to_pay: Cardinal;
-  new_public_key: TAccountKey; locked_until_block: Cardinal; key: TECPrivateKey;
-  payload: TRawBytes);
+constructor TOpListAccountForSale.CreateListAccountForSale(ACurrentProtocol : Word; AListOpSubType : Integer; AAccountSigner, ANOperation, AAccountTarget: Cardinal; AAccountPrice, AFee: UInt64; AAccountToPay: Cardinal;  ANewPublicKey: TAccountKey; ALockedUntilBlock: Cardinal; AKey: TECPrivateKey;  const AHashLock : T32Bytes; const APayload: TRawBytes);
 begin
-  inherited Create;
-  FData.account_signer := account_signer;
-  FData.account_target := account_target;
+  inherited Create(ACurrentProtocol);
+  if NOT (AListOpSubType IN [CT_OpSubtype_ListAccountForPublicSale, CT_OpSubtype_ListAccountForPrivateSale, CT_OpSubtype_ListAccountForAccountSwap, CT_OpSubtype_ListAccountForCoinSwap]) then
+    raise EArgumentOutOfRangeException.Create('Invalid list operation sub type');
+
+  case AListOpSubType of
+     CT_OpSubtype_ListAccountForPublicSale: begin
+       if (FData.new_public_key.EC_OpenSSL_NID<>0) then
+         raise EArgumentOutOfRangeException.Create('Public sale must be to a null key');
+       FData.account_state := as_ForSale;
+     end;
+     CT_OpSubtype_ListAccountForPrivateSale: FData.account_state := as_ForSale;
+     CT_OpSubtype_ListAccountForAccountSwap: FData.account_state := as_ForAtomicAccountSwap;
+     CT_OpSubtype_ListAccountForCoinSwap: FData.account_state := as_ForAtomicCoinSwap;
+  end;
+  FData.account_signer := AAccountSigner;
+  FData.account_target := AAccountTarget;
   FData.operation_type := lat_ListForSale;
-  FData.n_operation := n_operation;
-  FData.account_price := account_price;
-  FData.account_to_pay := account_to_pay;
-  FData.fee := fee;
-  FData.payload := payload;
+  FData.n_operation := ANOperation;
+  FData.account_price := AAccountPrice;
+  FData.account_to_pay := AAccountToPay;
+  FData.fee := AFee;
+  FData.payload := APayload;
   // V2: No need to store public key because it's at safebox. Saving at least 64 bytes!
   // FData.public_key := key.PublicKey;
-  FData.new_public_key := new_public_key;
-  FData.locked_until_block := locked_until_block;
+  FData.new_public_key := ANewPublicKey;
+  FData.locked_until_block := ALockedUntilBlock;
+  if AListOpSubType in [CT_OpSubtype_ListAccountForAccountSwap, CT_OpSubtype_ListAccountForCoinSwap] then
+    FData.hash_lock := AHashLock;
 
-  if Assigned(key) then begin
-    FData.sign := TCrypto.ECDSASign(key.PrivateKey, GetDigestToSign(current_protocol));
+  if Assigned(AKey) then begin
+    FData.sign := TCrypto.ECDSASign(AKey.PrivateKey, GetDigestToSign(ACurrentProtocol));
     FHasValidSignature := true;
-    FUsedPubkeyForSignature := key.PublicKey;
+    FUsedPubkeyForSignature := AKey.PublicKey;
   end else begin
     TLog.NewLog(ltdebug,Classname,'No key for signing a new list account for sale operation');
     FHasValidSignature := false;
   end;
 end;
 
-function TOpListAccountForSale.IsDelist: Boolean;
-begin
-  Result := False;
-end;
-
 class function TOpListAccountForSale.OpType: Byte;
 begin
   Result := CT_Op_ListAccountForSale;
 end;
 
+
 { TOpDelistAccountForSale }
 
-constructor TOpDelistAccountForSale.CreateDelistAccountForSale(current_protocol : Word; account_signer, n_operation, account_target: Cardinal; fee: UInt64; key: TECPrivateKey; payload: TRawBytes);
+constructor TOpDelistAccountForSale.CreateDelistAccountForSale(ACurrentProtocol : Word; account_signer, n_operation, account_target: Cardinal; fee: UInt64; key: TECPrivateKey; payload: TRawBytes);
 begin
-  inherited Create;
+  inherited Create(ACurrentProtocol);
   FData.account_signer := account_signer;
   FData.account_target := account_target;
   FData.operation_type := lat_DelistAccount;
@@ -2110,7 +2265,7 @@ begin
   FData.fee := fee;
   FData.payload := payload;
   if Assigned(key) then begin
-    FData.sign := TCrypto.ECDSASign(key.PrivateKey, GetDigestToSign(current_protocol));
+    FData.sign := TCrypto.ECDSASign(key.PrivateKey, GetDigestToSign(ACurrentProtocol));
     FHasValidSignature := true;
     FUsedPubkeyForSignature := key.PublicKey;
   end else begin
@@ -2119,11 +2274,6 @@ begin
   end;
 end;
 
-function TOpDelistAccountForSale.IsDelist: Boolean;
-begin
-  Result := True;
-end;
-
 class function TOpDelistAccountForSale.OpType: Byte;
 begin
   Result := CT_Op_DelistAccount;
@@ -2131,11 +2281,11 @@ end;
 
 { TOpBuyAccount }
 
-constructor TOpBuyAccount.CreateBuy(current_protocol : Word; account_number, n_operation, account_to_buy,
+constructor TOpBuyAccount.CreateBuy(ACurrentProtocol : Word; account_number, n_operation, account_to_buy,
   account_to_pay: Cardinal; price, amount, fee: UInt64;
   new_public_key: TAccountKey; key: TECPrivateKey; payload: TRawBytes);
 begin
-  inherited Create;
+  inherited Create(ACurrentProtocol);
   FData.sender := account_number;
   FData.n_operation := n_operation;
   FData.target := account_to_buy;
@@ -2150,7 +2300,7 @@ begin
   FData.new_accountkey := new_public_key;
 
   if Assigned(key) then begin
-    FData.sign := TCrypto.ECDSASign(key.PrivateKey, GetDigestToSign(current_protocol));
+    FData.sign := TCrypto.ECDSASign(key.PrivateKey, GetDigestToSign(ACurrentProtocol));
     FHasValidSignature := true;
     FUsedPubkeyForSignature := key.PublicKey;
   end else begin
@@ -2444,11 +2594,11 @@ begin
   end;
 end;
 
-constructor TOpData.CreateOpData(account_signer, account_sender,
+constructor TOpData.CreateOpData(ACurrentProtocol : word; account_signer, account_sender,
   account_target: Cardinal; signer_key: TECPrivateKey; n_operation: Cardinal;
   dataType, dataSequence: Word; amount, fee: UInt64; payload: TRawBytes);
 begin
-  Inherited Create;
+  Inherited Create(ACurrentProtocol);
   FData.account_sender:=account_sender;
   FData.account_signer:=account_signer;
   FData.account_target:=account_target;

+ 74 - 11
src/core/URPC.pas

@@ -769,7 +769,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     End;
   End;
 
-  Function HexaStringToOperationsHashTreeAndGetMultioperation(Const HexaStringOperationsHashTree : String; canCreateNewOne : Boolean; out OperationsHashTree : TOperationsHashTree; out multiOperation : TOpMultiOperation; var errors : String) : Boolean;
+  Function HexaStringToOperationsHashTreeAndGetMultioperation(AProtocolVersion : Word; Const HexaStringOperationsHashTree : String; canCreateNewOne : Boolean; out OperationsHashTree : TOperationsHashTree; out multiOperation : TOpMultiOperation; var errors : String) : Boolean;
     { This function will return true only if HexaString contains only 1 operation and is a multioperation.
       Also, if "canCreateNewOne" is true and has no operations, then will create new one and return True
       }
@@ -780,7 +780,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     If (Result) then begin
       Try
         If (OperationsHashTree.OperationsCount=0) And (canCreateNewOne) then begin
-          multiOperation := TOpMultiOperation.Create;
+          multiOperation := TOpMultiOperation.Create(AProtocolVersion);
           OperationsHashTree.AddOperationToHashTree(multiOperation);
           multiOperation.Free;
           multiOperation := OperationsHashTree.GetOperation(0) as TOpMultiOperation;
@@ -1145,9 +1145,9 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
 
   // This function creates a TOpListAccountForSale without looking for actual state (cold wallet)
   // It assumes that account_number,account_last_n_operation and account_pubkey are correct
-  Function CreateOperationListAccountForSale(current_protocol : Word; account_signer, account_last_n_operation, account_listed : Cardinal; const account_signer_pubkey: TAccountKey;
+  Function CreateOperationListAccountForSale(current_protocol : Word; AListType : Word; account_signer, account_last_n_operation, account_listed : Cardinal; const account_signer_pubkey: TAccountKey;
     account_price : UInt64; locked_until_block : Cardinal; account_to_pay : Cardinal; Const new_account_pubkey : TAccountKey;
-    fee : UInt64; RawPayload : TRawBytes; Const Payload_method, EncodePwd : String) : TOpListAccountForSale;
+    fee : UInt64; const AHashLock: T32Bytes; const RawPayload : TRawBytes; Const Payload_method, EncodePwd : String) : TOpListAccountForSale;
   // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
   var privateKey : TECPrivateKey;
     errors : String;
@@ -1161,8 +1161,21 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
        aux_target_pubkey := new_account_pubkey;
     end else aux_target_pubkey := account_signer_pubkey;
     if Not CheckAndGetEncodedRAWPayload(RawPayload,Payload_method,EncodePwd,account_signer_pubkey,aux_target_pubkey,f_raw) then Exit(Nil);
-    Result := TOpListAccountForSale.CreateListAccountForSale(current_protocol, account_signer,account_last_n_operation+1,account_listed,account_price,fee,account_to_pay,new_account_pubkey,locked_until_block,
-      privateKey,f_raw);
+    Result := TOpListAccountForSale.CreateListAccountForSale(
+      current_protocol,
+      AListType,
+      account_signer,
+      account_last_n_operation+1,
+      account_listed,
+      account_price,
+      fee,
+      account_to_pay,
+      new_account_pubkey,
+      locked_until_block,
+      privateKey,
+      AHashLock,
+      f_raw
+    );
     if Not Result.HasValidSignature then begin
       FreeAndNil(Result);
       ErrorNum:=CT_RPC_ErrNum_InternalError;
@@ -1523,18 +1536,24 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
 
   function SignListAccountForSaleEx(params : TPCJSONObject; OperationsHashTree : TOperationsHashTree; current_protocol : Word; const actualAccounKey : TAccountKey; last_n_operation : Cardinal) : boolean;
     // params:
+    // "type" (optional) is the type of listing to perform public_sale, private_sale, atomic_account_swap, atomic_coin_swap
     // "account_signer" is the account that signs operations and pays the fee
     // "account_target" is the account being listed
     // "locked_until_block" is until which block will be locked this account (Note: A locked account cannot change it's state until sold or finished lock)
     // "price" is the price
     // "seller_account" is the account to pay (seller account)
     // "new_b58_pubkey" or "new_enc_pubkey" is the future public key for this sale (private sale), otherwise is open and everybody can buy
+    // "enc_hash_lock" (optional) hex-encoded hash-lock for an atomic swap
   var
     opSale: TOpListAccountForSale;
+    listType : Integer;
     account_signer, account_target, seller_account : Cardinal;
     locked_until_block : Cardinal;
     price,fee : Int64;
     new_pubkey : TAccountKey;
+    LHasHashLock : Boolean;
+    LHashLock : T32Bytes;
+    LStrVal : String;
   begin
     Result := false;
     account_signer := params.AsInteger('account_signer',MaxInt);
@@ -1579,8 +1598,50 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
         Exit;
       end;
     end else new_pubkey := CT_TECDSA_Public_Nul;
-    opSale := CreateOperationListAccountForSale(current_protocol, account_signer,last_n_operation,account_target,actualAccounKey,price,locked_until_block,
-      seller_account, new_pubkey,fee,
+
+    LHasHashLock := False;
+    LHashLock := CT_HashLock_NUL;
+    if (params.IndexOfName('enc_hash_lock') >= 0) then begin
+      LStrVal := params.AsString('enc_hash_lock', '');
+      if (NOT TCrypto.IsHexString( LStrVal )) OR (Length(LStrVal) <> 32*2) then begin
+         ErrorNum := CT_RPC_ErrNum_InvalidData;
+         ErrorDesc := 'Invalid "enc_hash_lock" value. Must be 32 byte hexadecimal string.';
+         Exit;
+      end;
+      LHasHashLock := True;
+      LHashLock := TBaseType.To32Bytes( TCrypto.HexaToRaw( LStrVal ) );
+    end;
+
+    if params.IndexOfName('type') >= 0 then begin
+      LStrVal := params.AsString('type', '');
+      if (LStrVal = 'public_sale') then
+        listType := CT_OpSubtype_ListAccountForPublicSale
+      else if (LStrVal = 'private_sale') then
+        listType := CT_OpSubtype_ListAccountForPrivateSale
+      else if (LStrVal = 'atomic_coin_swap') then
+        listType := CT_OpSubtype_ListAccountForAccountSwap
+      else if (LStrVal = 'public_sale') then
+        listType := CT_OpSubtype_ListAccountForCoinSwap
+      else begin
+        ErrorNum := CT_RPC_ErrNum_InvalidData;
+        ErrorDesc := 'Invalid "type" value';
+        Exit;
+      end;
+      if (listType in [CT_OpSubtype_ListAccountForAccountSwap, CT_OpSubtype_ListAccountForCoinSwap]) and (NOT LHasHashLock) then begin
+        ErrorNum := CT_RPC_ErrNum_InvalidData;
+        ErrorDesc := 'Missing "enc_hash_lock" field. Required for atomic swaps';
+        Exit;
+      end;
+    end else begin
+      // type not specified, implied private or public sale, figure out based on key
+      if new_pubkey.EC_OpenSSL_NID = CT_TECDSA_Public_Nul.EC_OpenSSL_NID then
+        listType := CT_OpSubtype_ListAccountForPublicSale
+      else
+        listType := CT_OpSubtype_ListAccountForPrivateSale;
+    end;
+
+    opSale := CreateOperationListAccountForSale(current_protocol, listType, account_signer,last_n_operation,account_target,actualAccounKey,price,locked_until_block,
+      seller_account, new_pubkey,fee, LHashLock,
       TCrypto.HexaToRaw(params.AsString('payload','')),
       params.AsString('payload_method','dest'),params.AsString('pwd',''));
     if opSale=nil then exit;
@@ -2372,7 +2433,9 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
         - "new_type" : (optional) The new account type
         }
     Result := false;
-    if Not HexaStringToOperationsHashTreeAndGetMultioperation(HexaStringOperationsHashTree,True,OperationsHashTree,mop,errors) then begin
+    if Not HexaStringToOperationsHashTreeAndGetMultioperation(
+      Self.FNode.Bank.SafeBox.CurrentProtocol, // HS: 2019-07-09: use current protocol since this API used to build new unpublished operations, not historical ones
+      HexaStringOperationsHashTree,True,OperationsHashTree,mop,errors) then begin
       ErrorNum:=CT_RPC_ErrNum_InvalidData;
       ErrorDesc:= 'Error decoding param previous operations hash tree raw value: '+errors;
       Exit;
@@ -2592,7 +2655,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
       Exit;
     end;
     protocol := params.GetAsVariant('protocol').AsCardinal(CT_BUILD_PROTOCOL);
-    if Not HexaStringToOperationsHashTreeAndGetMultioperation(HexaStringOperationsHashTree,False,senderOperationsHashTree,mop,errors) then begin
+    if Not HexaStringToOperationsHashTreeAndGetMultioperation(FNode.Bank.SafeBox.CurrentProtocol, HexaStringOperationsHashTree,False,senderOperationsHashTree,mop,errors) then begin
       ErrorNum:=CT_RPC_ErrNum_InvalidData;
       ErrorDesc:= 'Error decoding param previous operations hash tree raw value: '+errors;
       Exit;
@@ -2622,7 +2685,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
       ErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
       Exit;
     end;
-    if Not HexaStringToOperationsHashTreeAndGetMultioperation(HexaStringOperationsHashTree,False,senderOperationsHashTree,mop,errors) then begin
+    if Not HexaStringToOperationsHashTreeAndGetMultioperation(FNode.Bank.SafeBox.CurrentProtocol, HexaStringOperationsHashTree,False,senderOperationsHashTree,mop,errors) then begin
       ErrorNum:=CT_RPC_ErrNum_InvalidData;
       ErrorDesc:= 'Error decoding param previous operations hash tree raw value: '+errors;
       Exit;

+ 1 - 1
src/core/UTxMultiOperation.pas

@@ -892,7 +892,7 @@ constructor TOpMultiOperation.CreateMultiOperation(current_protocol : Word;
   changes_keys: array of TECPrivateKey);
 Var i : Integer;
 begin
-  inherited Create;
+  inherited Create(current_protocol);
   AddTx(senders,receivers,True);
   AddChangeInfos(changes,True);
   // Protection for "Exit"

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

@@ -319,9 +319,9 @@ loop_start:
         if signerAccount.balance>DefaultFee then _fee := DefaultFee
         else _fee := signerAccount.balance;
         if (rbListAccountForPublicSale.Checked) then begin
-          op := TOpListAccountForSale.CreateListAccountForSale(FNode.Bank.SafeBox.CurrentProtocol,signerAccount.account,signerAccount.n_operation+1+iAcc, account.account,_salePrice,_fee,destAccount.account,CT_TECDSA_Public_Nul,0,wk.PrivateKey,FEncodedPayload);
+          op := TOpListAccountForSale.CreateListAccountForSale(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);
         end else if (rbListAccountForPrivateSale.Checked) then begin
-          op := TOpListAccountForSale.CreateListAccountForSale(FNode.Bank.SafeBox.CurrentProtocol,signerAccount.account,signerAccount.n_operation+1+iAcc, account.account,_salePrice,_fee,destAccount.account,_newOwnerPublicKey,_lockedUntil,wk.PrivateKey,FEncodedPayload);
+          op := TOpListAccountForSale.CreateListAccountForSale(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);
         end else raise Exception.Create('Select Sale type');
         {%endregion}
       end else if (PageControlOpType.ActivePage = tsDelist) then begin

+ 3 - 3
src/gui-classic/UFRMOperationsExplorer.pas

@@ -170,7 +170,7 @@ begin
     if (op is TOpMultiOperation) then mop := TOpMultiOperation(op);
   end;
   If Not Assigned(mop) then begin
-    mop := TOpMultiOperation.Create;
+    mop := TOpMultiOperation.Create(FSourceNode.Bank.SafeBox.CurrentProtocol);
     FOperationsHashTree.AddOperationToHashTree(mop);
     mop.Free;
     mop := FOperationsHashTree.GetOperation(FOperationsHashTree.OperationsCount-1) as TOpMultiOperation;
@@ -279,7 +279,7 @@ begin
   If Not InputQuery(Caption,Format('Multioperation: Remove account sender/receiver/changer. Which account?',[]),aux) then Exit;
   nAccount := StrToIntDef(aux,-1);
   If nAccount<0 then Exit;
-  newMop := TOpMultiOperation.Create;
+  newMop := TOpMultiOperation.Create(FSourceNode.Bank.SafeBox.CurrentProtocol);
   Try
     SetLength(txs,0);
     SetLength(txr,0);
@@ -369,7 +369,7 @@ begin
     if (op is TOpMultiOperation) then mop := TOpMultiOperation(op);
   end;
   If Not Assigned(mop) then begin
-    mop := TOpMultiOperation.Create;
+    mop := TOpMultiOperation.Create(FSourceNode.Bank.SafeBox.CurrentProtocol);
     FOperationsHashTree.AddOperationToHashTree(mop);
     mop.Free;
     mop := FOperationsHashTree.GetOperation(FOperationsHashTree.OperationsCount-1) as TOpMultiOperation;

+ 1 - 1
src/gui-classic/UFRMRandomOperations.pas

@@ -353,7 +353,7 @@ Var opMulti : TOpMultiOperation;
   errors : String;
 begin
   Result := False;
-  opMulti := TOpMultiOperation.Create;
+  opMulti := TOpMultiOperation.Create(current_protocol);
   Try
     if (Random(100)<5) then begin
       Repeat