Ver código fonte

Updates for PIP-0017

After chat with Herman, changed TMultitransaction to TMultioperation
TODO: TMultiOperation.DoOperation
PascalCoin 7 anos atrás
pai
commit
e8960a3a25
4 arquivos alterados com 583 adições e 500 exclusões
  1. 42 8
      src/core/UBlockChain.pas
  2. 5 1
      src/core/UConst.pas
  3. 2 239
      src/core/UOpTransaction.pas
  4. 534 252
      src/core/UTxMultiOperation.pas

+ 42 - 8
src/core/UBlockChain.pas

@@ -20,7 +20,7 @@ unit UBlockChain;
 interface
 
 uses
-  Classes, UCrypto, UAccounts, ULog, UThread, SyncObjs, UtxMultiOperation;
+  Classes, UCrypto, UAccounts, ULog, UThread, SyncObjs;
 {$I config.inc}
 
 {
@@ -106,10 +106,35 @@ uses
 }
 
 Type
-  TPCBank = Class;
-  TPCBankNotify = Class;
-  TPCOperation = Class;
-  TPCOperationClass = Class of TPCOperation;
+  // Moved from UOpTransaction to here
+  TOpChangeAccountInfoType = (public_key,account_name,account_type);
+  TOpChangeAccountInfoTypes = Set of TOpChangeAccountInfoType;
+
+  // MultiOp... will allow a MultiOperation
+  TMultiOpSender = Record
+    Account : Cardinal;
+    Amount : Int64;
+    N_Operation : Cardinal;
+    Payload : TRawBytes;
+    Signature : TECDSA_SIG;
+  end;
+  TMultiOpSenders = Array of TMultiOpSender;
+  TMultiOpReceiver = Record
+    Account : Cardinal;
+    Amount : Int64;
+    Payload : TRawBytes;
+  end;
+  TMultiOpReceivers = Array of TMultiOpReceiver;
+  TMultiOpChangeInfo = Record
+    Account: Cardinal;
+    N_Operation : Cardinal;
+    Changes_type : TOpChangeAccountInfoTypes; // bits mask. $0001 = New account key , $0002 = New name , $0004 = New 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
+    Signature: TECDSA_SIG;
+  end;
+  TMultiOpChangesInfo = Array of TMultiOpChangeInfo;
 
   TOperationResume = Record
     valid : Boolean;
@@ -134,10 +159,16 @@ Type
     OperationHash_OLD : TRawBytes; // Will include old oeration hash value
     errors : AnsiString;
     // New on V3 for PIP-0017
-    Senders : TAccountsTxInfoArray;
-    Receivers : TAccountsTxInfoArray;
+    Senders : TMultiOpSenders;
+    Receivers : TMultiOpReceivers;
+    Changers : TMultiOpChangesInfo;
   end;
 
+  TPCBank = Class;
+  TPCBankNotify = Class;
+  TPCOperation = Class;
+  TPCOperationClass = Class of TPCOperation;
+
   TOperationsResumeList = Class
   private
     FList : TPCThreadList;
@@ -427,6 +458,9 @@ Type
 
 Const
   CT_TOperationResume_NUL : TOperationResume = (valid:false;Block:0;NOpInsideBlock:-1;OpType:0;OpSubtype:0;time:0;AffectedAccount:0;SignerAccount:-1;n_operation:0;DestAccount:-1;SellerAccount:-1;newKey:(EC_OpenSSL_NID:0;x:'';y:'');OperationTxt:'';Amount:0;Fee:0;Balance:0;OriginalPayload:'';PrintablePayload:'';OperationHash:'';OperationHash_OLD:'';errors:'';Senders:Nil;Receivers:Nil);
+  CT_TMultiOpSender_NUL : TMultiOpSender =  (Account:0;Amount:0;N_Operation:0;Payload:'';Signature:(r:'';s:''));
+  CT_TMultiOpReceiver_NUL : TMultiOpReceiver = (Account:0;Amount:0;Payload:'');
+  CT_TMultiOpChangeInfo_NUL : TMultiOpChangeInfo = (Account:0;N_Operation:0;Changes_type:[];New_Accountkey:(EC_OpenSSL_NID:0;x:'';y:'');New_Name:'';New_Type:0;Signature:(r:'';s:''));
 
 implementation
 
@@ -2450,7 +2484,7 @@ begin
       OperationResume.OpSubtype:=CT_OpSubtype_ChangeAccountInfo;
       Result := True;
     end;
-    CT_Op_MultiTransaction : Begin
+    CT_Op_MultiOperation : Begin
 
     end
   else Exit;

+ 5 - 1
src/core/UConst.pas

@@ -123,7 +123,7 @@ Const
   CT_Op_ChangeKeySigned = $07;
   CT_Op_ChangeAccountInfo = $08;
   // Protocol 3 new operations
-  CT_Op_MultiTransaction = $09;  // PIP-0017
+  CT_Op_MultiOperation = $09;  // PIP-0017
 
   CT_PseudoOpSubtype_Miner                = 1;
   CT_PseudoOpSubtype_Developer            = 2;
@@ -153,6 +153,10 @@ Const
   CT_MAX_0_fee_operations_per_block_by_miner = {$IFDEF PRODUCTION}2000{$ELSE}{$IFDEF TESTNET}2{$ELSE}{$ENDIF}{$ENDIF};
   CT_MAX_Operations_per_block_by_miner =  {$IFDEF PRODUCTION}10000{$ELSE}{$IFDEF TESTNET}50{$ELSE}{$ENDIF}{$ENDIF};
 
+  CT_MAX_MultiOperation_Senders = 1000;
+  CT_MAX_MultiOperation_Receivers = 10000;
+  CT_MAX_MultiOperation_Changers = 1000;
+
   // App Params
   CT_PARAM_GridAccountsStream = 'GridAccountsStreamV2';
   CT_PARAM_GridAccountsPos = 'GridAccountsPos';

+ 2 - 239
src/core/UOpTransaction.pas

@@ -19,7 +19,7 @@ unit UOpTransaction;
 
 interface
 
-Uses UCrypto, UBlockChain, Classes, UAccounts, UTxMultiOperation;
+Uses UCrypto, UBlockChain, Classes, UAccounts;
 
 Type
   // Operations Type
@@ -173,9 +173,6 @@ Type
     sign: TECDSA_SIG;
   End;
 
-  TOpChangeAccountInfoType = (public_key,account_name,account_type);
-  TOpChangeAccountInfoTypes = Set of TOpChangeAccountInfoType;
-
   TOpChangeAccountInfoData = Record
     account_signer,
     account_target: Cardinal;
@@ -283,52 +280,12 @@ Type
     Function toString : String; Override;
   End;
 
-  // NEW OPERATIONS PROTOCOL 3
-
-  { TOpMultiTransaction }
-  // PIP-0017
-
-  TOpMultiTransactionData = Record
-    senders: TTxInfoSender;
-    receivers: TTxInfoReceiver;
-  end;
-
-  TOpMultiTransaction = Class(TPCOperation)
-  private
-    FData : TOpMultiTransactionData;
-  protected
-    procedure InitializeData; override;
-    function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; override;
-    function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
-  public
-    function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
-    function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
-    procedure AffectedAccounts(list : TList); override;
-    //
-    Class Function GetTransactionHashToSign(const multitrans : TOpMultiTransactionData) : TRawBytes;
-    Class Function DoSignMultiTransactionSigner(SignerAccount : Cardinal; key : TECPrivateKey; var trans : TOpMultiTransactionData) : Integer;
-    class function OpType : Byte; override;
-    function OperationAmount : Int64; override;
-    function OperationFee : UInt64; override;
-    function OperationPayload : TRawBytes; override;
-    function SignerAccount : Cardinal; override;
-    function DestinationAccount : Int64; override;
-    function SellerAccount : Int64; override;
-    function N_Operation : Cardinal; override;
-    Property Data : TOpMultiTransactionData read FData;
-    //
-    Constructor CreateMultiTransaction(const senders, receivers : TAccountsTxInfoArray; senders_keys: Array of TECPrivateKey);
-    Destructor Destroy; override;
-    Function toString : String; Override;
-  End;
-
-
 Procedure RegisterOperationsClass;
 
 implementation
 
 uses
-  SysUtils, UConst, ULog;
+  SysUtils, UConst, ULog, UTxMultiOperation;
 
 Procedure RegisterOperationsClass;
 Begin
@@ -342,200 +299,6 @@ Begin
   TPCOperationsComp.RegisterOperationClass(TOpChangeAccountInfo);
 End;
 
-{ TOpMultiTransaction }
-
-procedure TOpMultiTransaction.InitializeData;
-begin
-  inherited InitializeData;
-  FData.receivers := TTxInfoReceiver.Create;
-  FData.senders := TTxInfoSender.Create;
-end;
-
-function TOpMultiTransaction.SaveOpToStream(Stream: TStream; SaveExtendedData: Boolean): Boolean;
-begin
-  FData.senders.SaveToStream(Stream);
-  FData.receivers.SaveToStream(Stream);
-  Result := true;
-end;
-
-function TOpMultiTransaction.LoadOpFromStream(Stream: TStream; LoadExtendedData: Boolean): Boolean;
-var b : Byte;
-begin
-  Result := False;
-  Try
-    FData.senders.LoadFromStream(Stream);
-    FData.receivers.LoadFromStream(Stream);
-    Result := True;
-  Except
-    On E:Exception do begin
-      TLog.NewLog(lterror,Self.ClassName,'('+E.ClassName+'):'+E.Message);
-    end;
-  end;
-end;
-
-function TOpMultiTransaction.GetBufferForOpHash(UseProtocolV2: Boolean): TRawBytes;
-begin
-  Result:=inherited GetBufferForOpHash(UseProtocolV2);
-end;
-
-function TOpMultiTransaction.DoOperation(AccountTransaction: TPCSafeBoxTransaction; var errors: AnsiString): Boolean;
-begin
-  // TODO
-  { XXXXXXXXXXXXXXXXXXXXXXXXXX
-
-  Implementation as expected and explained at PIP-0017
-
-  Note: I've added "payload", that must be checked too
-
-  }
-  Raise Exception.Create('NOT IMPLEMENTED ERROR DEV 20180308-1');
-  Result := False;
-end;
-
-procedure TOpMultiTransaction.AffectedAccounts(list: TList);
-Var i : Integer;
-  Procedure _doAdd(nAcc : Cardinal);
-  Begin
-    If list.IndexOf(TObject(nAcc))<0 then list.Add(TObject(nAcc));
-  end;
-begin
-  For i:=0 to FData.senders.Count-1 do begin
-    _doAdd(FData.senders.AccountTxInfo[i].Account);
-  end;
-  For i:=0 to FData.receivers.Count-1 do begin
-    _doAdd(FData.senders.AccountTxInfo[i].Account);
-  end;
-end;
-
-class function TOpMultiTransaction.GetTransactionHashToSign(const multitrans: TOpMultiTransactionData): TRawBytes;
-Var ms : TMemoryStream;
-  rb : TRawBytes;
-begin
-  ms := TMemoryStream.Create;
-  try
-    rb := multitrans.senders.GetHash;
-    if (length(rb)>0) then ms.Write(rb[1],length(rb));
-    rb := multitrans.receivers.GetHash;
-    if (length(rb)>0) then ms.Write(rb[1],length(rb));
-    SetLength(Result,ms.Size);
-    ms.Position := 0;
-    ms.ReadBuffer(Result[1],ms.Size);
-  finally
-    ms.Free;
-  end;
-end;
-
-class function TOpMultiTransaction.DoSignMultiTransactionSigner(SignerAccount : Cardinal; key : TECPrivateKey; var trans : TOpMultiTransactionData) : Integer;
-Var i : Integer;
-var raw : TRawBytes;
-  _sign : TECDSA_SIG;
-begin
-  Result := 0;
-  If Not Assigned(key.PrivateKey) then begin
-    exit;
-  end;
-  raw := GetTransactionHashToSign(trans);
-  Try
-    _sign := TCrypto.ECDSASign(key.PrivateKey,raw);
-  Except
-    On E:Exception do begin
-      TLog.NewLog(ltError,ClassName,'Error signing ('+E.ClassName+') '+E.Message);
-      Exit;
-    end;
-  End;
-  Result := trans.senders.SetSignatureForAccount(SignerAccount,_sign);
-end;
-
-class function TOpMultiTransaction.OpType: Byte;
-begin
-  Result := CT_Op_MultiTransaction;
-end;
-
-function TOpMultiTransaction.OperationAmount: Int64;
-begin
-  Result := FData.senders.TotalAmount;
-end;
-
-function TOpMultiTransaction.OperationFee: UInt64;
-begin
-  Result := FData.senders.TotalFees;
-end;
-
-function TOpMultiTransaction.OperationPayload: TRawBytes;
-begin
-  Result := '';
-end;
-
-function TOpMultiTransaction.SignerAccount: Cardinal;
-begin
-  // On a multitransaction, the signer account are senders N accounts, cannot verify which one is correct... will send first one
-  If FData.senders.Count>0 then Result := FData.senders.AccountTxInfo[0].Account
-  else Result := MaxInt;
-end;
-
-function TOpMultiTransaction.DestinationAccount: Int64;
-begin
-  Result:=inherited DestinationAccount;
-end;
-
-function TOpMultiTransaction.SellerAccount: Int64;
-begin
-  Result:=inherited SellerAccount;
-end;
-
-function TOpMultiTransaction.N_Operation: Cardinal;
-begin
-  // On a multitransaction, there are senders N accounts, need specify
-  Result := 0;  // Note: N_Operation = 0 means NO OPERATION
-end;
-
-constructor TOpMultiTransaction.CreateMultiTransaction(const senders, receivers: TAccountsTxInfoArray; senders_keys: array of TECPrivateKey);
-Var i : Integer;
-begin
-  inherited Create;
-  FData.senders.Disable;
-  Try
-    For i:=low(senders) to high(senders) do begin
-      FData.senders.AddSender(senders[i].Account,senders[i].Amount,senders[i].Fee,senders[i].N_Operation,senders[i].Payload);
-    end;
-  finally
-    FData.senders.Enable;
-  end;
-  FData.receivers.Disable;
-  Try
-    For i:=low(receivers) to high(receivers) do begin
-      FData.receivers.AddReceiver(receivers[i].Account,receivers[i].Amount,receivers[i].Payload);
-    end;
-  finally
-    FData.receivers.Enable;
-  end;
-  FSignatureChecked:=True;
-  FHasValidSignature:=False;
-  If (length(senders_keys)<>length(senders)) then exit; // Cannot sign!
-  For i:=low(senders) to high(senders) do begin
-    If DoSignMultiTransactionSigner(senders[i].Account,senders_keys[i],FData)=0 then begin
-      TLog.NewLog(lterror,Classname,'Error signing a new MultiTransaction operation');
-      Exit;
-    end;
-  end;
-  FHasValidSignature:=True;
-end;
-
-destructor TOpMultiTransaction.Destroy;
-begin
-  FreeAndNil(FData.senders);
-  FreeAndNil(FData.receivers);
-  inherited Destroy;
-end;
-
-function TOpMultiTransaction.toString: String;
-begin
-  Result := Format('Multitransaction senders %s receivers %s Amount:%s Fees:%s',
-    [FData.senders.toString,FData.receivers.toString,
-     TAccountComp.FormatMoney(FData.senders.TotalAmount),
-     TAccountComp.FormatMoney(FData.senders.TotalFees)]);
-end;
-
 { TOpChangeAccountInfo }
 
 procedure TOpChangeAccountInfo.InitializeData;

+ 534 - 252
src/core/UTxMultiOperation.pas

@@ -20,361 +20,643 @@ unit UtxMultiOperation;
 interface
 
 uses
-  Classes, SysUtils, UCrypto;
+  Classes, SysUtils, UCrypto, UBlockChain, UAccounts;
 
 Type
-  TAccountTxInfo = Record
-    Account : Cardinal;
-    Amount : Int64;
-    Fee : Int64;               // Not used if AffectedAccount is the receiver
-    N_Operation : Cardinal;    // Not used if AffectedAccount is the receiver
-    Payload : TRawBytes;
-    Signature : TECDSA_SIG;    // Not used if AffectedAccount is the receiver
-  end;
 
-  TAccountsTxInfoArray = Array of TAccountTxInfo;
+  // NEW OPERATIONS PROTOCOL 3
 
-  { TTxInfo }
+  { TOpMultiOperation }
 
-  TTxInfo = Class
-  private
-    FAccountTxInfo : Array of TAccountTxInfo;
-    FDisableds : Integer;
-    FNeedRecalc : Boolean;
-    Procedure Recalc;
-    function GetAccounTxInfo(index : Integer): TAccountTxInfo;
-    function GetCount: Integer;
-  protected
-    FHash : TRawBytes;
-    FTotalAmount : Int64;
-    FTotalFees : Int64;
-    procedure InternalLoadTxFromStream(stream : TStream; var tx : TAccountTxInfo); virtual; abstract;
-    procedure InternalSaveTxToStream(stream : TStream; const tx : TAccountTxInfo); virtual; abstract;
-    Procedure InternalRecalc;
-  public
-    Constructor Create;
-    Destructor Destroy; override;
-    procedure LoadFromStream(stream : TStream);
-    procedure SaveToStream(stream : TStream);
-    Property Count : Integer read GetCount;
-    Function IndexOf(searchAccount : Cardinal) : Integer;
-    Procedure DeleteAccount(Account : Integer);
-    Procedure Disable;
-    Procedure Enable;
-    Property AccountTxInfo[index : Integer] : TAccountTxInfo read GetAccounTxInfo;
-    Procedure Clear;
-    Function GetHash : TRawBytes;
-    Function TotalAmount : Int64;
-    Function TotalFees : Int64;
-    Procedure ToArray(var txArray : TAccountsTxInfoArray);
-  end;
+  {
+    Based on PIP-0017, proposed by Herman Schoenfeld <[email protected]>
 
-  { TTxInfoSender }
+    Will include multiple operations (tx, changes of info...) in a single operation
 
-  TTxInfoSender = Class(TTxInfo)
-  protected
-    procedure InternalLoadTxFromStream(stream : TStream; var tx : TAccountTxInfo); override;
-    procedure InternalSaveTxToStream(stream : TStream; const tx : TAccountTxInfo); override;
-  public
-    Procedure AddSender(SenderAccount : Cardinal; Amount : Int64; Fee : Int64; N_Operation : Cardinal; Payload : TRawBytes);
-    Function SetSignatureForAccount(SignerAccount : Cardinal; const SignatureValue : TECDSA_SIG) : Integer;
-    Function toString : String; Override;
-  end;
+    Those operations could be:
+      - Send from N accounts to M receivers,  (transaction mixing, with anonymity)
+      - Change account info:
+        - Change account public key
+        - Change account type
+        - Change account name
+
+    This operation will work as a TRANSACTION operation, so, will execute ALL opererations or NONE
 
-  { TTxInfoReceiver }
+    Also, can be signed off-line by all senders/signers, knowing previously the OpHash because
+    the OpHash algo will not include the signature (due it's checked separately)
+
+  }
+
+  TOpMultiOperationData = Record
+    txSenders : TMultiOpSenders;
+    txReceivers : TMultiOpReceivers;
+    changesInfo : TMultiOpChangesInfo;
+  end;
 
-  TTxInfoReceiver = Class(TTxInfo)
+  TOpMultiOperation = Class(TPCOperation)
+  private
+    FData : TOpMultiOperationData;
+    //
+    FSaveSignatureValue : Boolean;
+    FTotalAmount : Int64;
+    FTotalFee : Int64;
+    Function IndexOfAccountSender(nAccount : Cardinal) : Integer;
+    Function IndexOfAccountReceiver(nAccount : Cardinal) : Integer;
+    Function IndexOfAccountChanger(nAccount : Cardinal) : Integer;
+    Function IndexOfAccountChangeNameTo(const newName : AnsiString) : Integer;
   protected
-    procedure InternalLoadTxFromStream(stream : TStream; var tx : TAccountTxInfo); override;
-    procedure InternalSaveTxToStream(stream : TStream; const tx : TAccountTxInfo); override;
+    procedure InitializeData; override;
+    function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; override;
+    function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
   public
-    Procedure AddReceiver(DestinationAccount : Cardinal; Amount : Int64; Payload : TRawBytes);
+    function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
+
+    function CheckSignatures(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean;
+
+    function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
+    procedure AffectedAccounts(list : TList); override;
+    //
+    Function GetTransactionHashToSign : TRawBytes;
+    Function DoSignMultiOperationSigner(SignerAccount : Cardinal; key : TECPrivateKey) : Integer;
+    class function OpType : Byte; override;
+    function OperationAmount : Int64; override;
+    function OperationFee : UInt64; override;
+    function OperationPayload : TRawBytes; override;
+    function SignerAccount : Cardinal; override;
+    function DestinationAccount : Int64; override;
+    function SellerAccount : Int64; override;
+    function N_Operation : Cardinal; override;
+    //
+    Constructor CreateMultiOperation(const senders : TMultiOpSenders; const receivers : TMultiOpReceivers; const changes : TMultiOpChangesInfo; const senders_keys, changes_keys: Array of TECPrivateKey);
+    Function AddTx(const senders : TMultiOpSenders; const receivers : TMultiOpReceivers; setInRandomOrder : Boolean) : Boolean;
+    Function AddChangeInfo(const changes : TMultiOpChangesInfo; setInRandomOrder : Boolean) : Boolean;
+    Destructor Destroy; override;
     Function toString : String; Override;
-  end;
+  End;
 
-Const
-  CT_TAccountTxInfo_NULL : TAccountTxInfo = (Account:0;Amount:0;Fee:0;N_Operation:0;Payload:'';Signature:(r:'';s:''));
 
 implementation
 
-Uses UAccounts;
+Uses ULog, UConst;
 
-{ TTxInfoReceiver }
+{ TOpMultiOperation }
 
-procedure TTxInfoReceiver.InternalLoadTxFromStream(stream: TStream; var tx: TAccountTxInfo);
+function TOpMultiOperation.IndexOfAccountSender(nAccount: Cardinal): Integer;
 begin
-  tx := CT_TAccountTxInfo_NULL;
-  // Note: Receiver will read ONLY account, amount and Payload (not fee, not N_Operation nor signature...)
-  stream.Read(tx.Account,SizeOf(tx.Account));
-  stream.Read(tx.Amount,SizeOf(tx.Amount));
-  TStreamOp.ReadAnsiString(stream,tx.Payload);
-end;
-
-procedure TTxInfoReceiver.InternalSaveTxToStream(stream: TStream; const tx: TAccountTxInfo);
-begin
-  stream.Write(tx.Account,SizeOf(tx.Account));
-  stream.Write(tx.Amount,SizeOf(tx.Amount));
-  TStreamOp.WriteAnsiString(stream,tx.Payload);
+  for Result:=0 to high(FData.txSenders) do begin
+    If (FData.txSenders[Result].Account = nAccount) then exit;
+  end;
+  Result := -1;
 end;
 
-procedure TTxInfoReceiver.AddReceiver(DestinationAccount: Cardinal; Amount: Int64; Payload : TRawBytes);
-var i : Integer;
+function TOpMultiOperation.IndexOfAccountReceiver(nAccount: Cardinal): Integer;
 begin
-  i := IndexOf(DestinationAccount);
-  If i>=0 then Raise Exception.Create(Format('Cannot add Destination Account %d (found at pos %d)',[DestinationAccount,i]));
-  i := length(FAccountTxInfo);
-  SetLength(FAccountTxInfo,i+1);
-  FAccountTxInfo[i] := CT_TAccountTxInfo_NULL;
-  FAccountTxInfo[i].Account:= DestinationAccount;
-  FAccountTxInfo[i].Amount:= Amount;
-  FAccountTxInfo[i].Payload:= Payload;
-  Recalc;
+  for Result:=0 to high(FData.txReceivers) do begin
+    If (FData.txReceivers[Result].Account = nAccount) then exit;
+  end;
+  Result := -1;
 end;
 
-function TTxInfoReceiver.toString: String;
-Var i : Integer;
+function TOpMultiOperation.IndexOfAccountChanger(nAccount: Cardinal): Integer;
 begin
-  Result := '';
-  for i:=0 to Count-1 do begin
-    Result := Result + Format('%d:(%s,%s)',[i+1,TAccountComp.AccountNumberToAccountTxtNumber(AccountTxInfo[i].Account),
-      TAccountComp.FormatMoney(AccountTxInfo[i].Amount)]);
+  for Result:=0 to high(FData.changesInfo) do begin
+    If (FData.changesInfo[Result].Account = nAccount) then exit;
   end;
+  Result := -1;
 end;
 
-{ TTxInfoSender }
-
-procedure TTxInfoSender.InternalLoadTxFromStream(stream: TStream; var tx : TAccountTxInfo);
+function TOpMultiOperation.IndexOfAccountChangeNameTo(const newName: AnsiString): Integer;
 begin
-  tx := CT_TAccountTxInfo_NULL;
-  stream.Read(tx.Account,SizeOf(tx.Account));
-  stream.Read(tx.Amount,SizeOf(tx.Amount));
-  stream.Read(tx.Fee,SizeOf(tx.Fee));
-  stream.Read(tx.N_Operation,SizeOf(tx.N_Operation));
-  TStreamOp.ReadAnsiString(stream,tx.Payload);
-  TStreamOp.ReadAnsiString(stream,tx.Signature.r);
-  TStreamOp.ReadAnsiString(stream,tx.Signature.s);
+  If (newName<>'') then begin
+    for Result:=0 to high(FData.changesInfo) do begin
+      If (account_name in FData.changesInfo[Result].Changes_type) And
+         (AnsiCompareText(FData.changesInfo[Result].New_Name,newName)=0) then exit;
+    end;
+  end;
+  Result := -1;
 end;
 
-procedure TTxInfoSender.InternalSaveTxToStream(stream: TStream; const tx : TAccountTxInfo);
+procedure TOpMultiOperation.InitializeData;
 begin
-  stream.Write(tx.Account,SizeOf(tx.Account));
-  stream.Write(tx.Amount,SizeOf(tx.Amount));
-  stream.Write(tx.Fee,SizeOf(tx.Fee));
-  stream.Write(tx.N_Operation,SizeOf(tx.N_Operation));
-  TStreamOp.WriteAnsiString(stream,tx.Payload);
-  TStreamOp.WriteAnsiString(stream,tx.Signature.r);
-  TStreamOp.WriteAnsiString(stream,tx.Signature.s);
+  inherited InitializeData;
+  SetLength(FData.txSenders,0);
+  SetLength(FData.txReceivers,0);
+  SetLength(FData.changesInfo,0);
+  FSaveSignatureValue := True;
+  FTotalAmount:=0;
+  FTotalFee:=0;
 end;
 
-procedure TTxInfoSender.AddSender(SenderAccount: Cardinal; Amount: Int64; Fee: Int64; N_Operation: Cardinal; Payload: TRawBytes);
+function TOpMultiOperation.SaveOpToStream(Stream: TStream; SaveExtendedData: Boolean): Boolean;
 var i : Integer;
+  w : Word;
+  txs : TMultiOpSender;
+  txr : TMultiOpReceiver;
+  chi : TMultiOpChangeInfo;
+  b : Byte;
 begin
-  i := IndexOf(SenderAccount);
-  If i>=0 then Raise Exception.Create(Format('Cannot add Sender Account %d (found at pos %d)',[SenderAccount,i]));
-  i := length(FAccountTxInfo);
-  SetLength(FAccountTxInfo,i+1);
-  FAccountTxInfo[i] := CT_TAccountTxInfo_NULL;
-  FAccountTxInfo[i].Account:= SenderAccount;
-  FAccountTxInfo[i].Amount:= Amount;
-  FAccountTxInfo[i].Fee:= Fee;
-  FAccountTxInfo[i].N_Operation:= N_Operation;
-  FAccountTxInfo[i].Payload:=Payload;
-  Recalc;
+  // Will save protocol info
+  w := CT_PROTOCOL_2;
+  stream.Write(w,SizeOf(w));
+  // Save senders count
+  w := Length(FData.txSenders);
+  stream.Write(w,SizeOf(w));
+  for i:=0 to length(FData.txSenders)-1 do begin
+    txs := FData.txSenders[i];
+    stream.Write(txs.Account,SizeOf(txs.Account));
+    stream.Write(txs.Amount,SizeOf(txs.Amount));
+    stream.Write(txs.N_Operation,SizeOf(txs.N_Operation));
+    TStreamOp.WriteAnsiString(stream,txs.Payload);
+    If FSaveSignatureValue then begin
+      TStreamOp.WriteAnsiString(stream,txs.Signature.r);
+      TStreamOp.WriteAnsiString(stream,txs.Signature.s);
+    end;
+  end;
+  // Save receivers count
+  w := Length(FData.txReceivers);
+  stream.Write(w,SizeOf(w));
+  for i:=0 to length(FData.txReceivers)-1 do begin
+    txr := FData.txReceivers[i];
+    stream.Write(txr.Account,SizeOf(txr.Account));
+    stream.Write(txr.Amount,SizeOf(txr.Amount));
+    TStreamOp.WriteAnsiString(stream,txr.Payload);
+  end;
+  // Save changes info count
+  w := Length(FData.changesInfo);
+  stream.Write(w,SizeOf(w));
+  for i:=0 to length(FData.changesInfo)-1 do begin
+    chi := FData.changesInfo[i];
+    stream.Write(chi.Account,SizeOf(chi.Account));
+    Stream.Write(chi.N_Operation,Sizeof(chi.N_Operation));
+    b := 0;
+    if (public_key in chi.Changes_type) then b:=b OR $01;
+    if (account_name in chi.changes_type) then b:=b OR $02;
+    if (account_type in chi.changes_type) then b:=b OR $04;
+    Stream.Write(b,Sizeof(b));
+    TStreamOp.WriteAccountKey(Stream,chi.New_Accountkey);
+    TStreamOp.WriteAnsiString(Stream,chi.New_Name);
+    Stream.Write(chi.New_Type,Sizeof(chi.New_Type));
+    If FSaveSignatureValue then begin
+      TStreamOp.WriteAnsiString(Stream,chi.Signature.r);
+      TStreamOp.WriteAnsiString(Stream,chi.Signature.s);
+    end;
+  end;
+  Result := true;
 end;
 
-function TTxInfoSender.SetSignatureForAccount(SignerAccount: Cardinal; const SignatureValue: TECDSA_SIG): Integer;
-Var i : Integer;
+function TOpMultiOperation.LoadOpFromStream(Stream: TStream; LoadExtendedData: Boolean): Boolean;
+var i : Integer;
+  w : Word;
+  txs : TMultiOpSender;
+  txr : TMultiOpReceiver;
+  chi : TMultiOpChangeInfo;
+  b : Byte;
+  txsenders : TMultiOpSenders;
+  txreceivers : TMultiOpReceivers;
+  changes : TMultiOpChangesInfo;
 begin
-  Result := 0;
-  For i:=0 to Count-1 do begin
-    If (AccountTxInfo[i].Account=SignerAccount) then begin
-      FAccountTxInfo[i].Signature := SignatureValue;
-      Inc(Result);
+  // Clear all data!
+  SetLength(FData.txSenders,0);
+  SetLength(FData.txReceivers,0);
+  SetLength(FData.changesInfo,0);
+  FTotalAmount:=0;
+  FTotalFee:=0;
+  FSignatureChecked:=False;
+  FHasValidSignature:=False;
+
+  SetLength(txsenders,0);
+  SetLength(txreceivers,0);
+  SetLength(changes,0);
+
+  Result := False;
+  Try
+    // Read protocol info
+    stream.Read(w,SizeOf(w));
+    If w<>CT_PROTOCOL_2 then Raise Exception.Create('Invalid protocol found');
+    // Load senders
+    stream.Read(w,SizeOf(w));
+    If w>CT_MAX_MultiOperation_Senders then Raise Exception.Create('Max senders');
+    setLength(txsenders,w);
+    If (w>0) then begin
+      for i:=0 to w-1 do begin
+        txs := CT_TMultiOpSender_NUL;
+        stream.Read(txs.Account,SizeOf(txs.Account));
+        stream.Read(txs.Amount,SizeOf(txs.Amount));
+        stream.Read(txs.N_Operation,SizeOf(txs.N_Operation));
+        TStreamOp.ReadAnsiString(stream,txs.Payload);
+        TStreamOp.ReadAnsiString(stream,txs.Signature.r);
+        TStreamOp.ReadAnsiString(stream,txs.Signature.s);
+        //
+        txsenders[i] := txs;
+      end;
+    end;
+    // Load receivers
+    stream.Read(w,SizeOf(w));
+    If w>CT_MAX_MultiOperation_Receivers then Raise Exception.Create('Max receivers');
+    SetLength(txreceivers,w);
+    If (w>0) then begin
+      for i:=0 to w-1 do begin
+        txr := CT_TMultiOpReceiver_NUL;
+        stream.Read(txr.Account,SizeOf(txr.Account));
+        stream.Read(txr.Amount,SizeOf(txr.Amount));
+        TStreamOp.ReadAnsiString(stream,txr.Payload);
+        //
+        txreceivers[i] := txr;
+      end;
+    end;
+    // Load changes info
+    stream.Read(w,SizeOf(w));
+    If w>CT_MAX_MultiOperation_Changers then Raise Exception.Create('Max changers');
+    SetLength(changes,w);
+    if (w>0) then begin
+      for i:=0 to w-1 do begin
+        chi := CT_TMultiOpChangeInfo_NUL;
+        stream.Read(chi.Account,SizeOf(chi.Account));
+        Stream.Read(chi.N_Operation,Sizeof(chi.N_Operation));
+        Stream.Read(b,Sizeof(b));
+        chi.Changes_type:=[];
+        if (b AND $01)=$01 then chi.changes_type:=chi.changes_type + [public_key];
+        if (b AND $02)=$02 then chi.changes_type:=chi.changes_type + [account_name];
+        if (b AND $04)=$04 then chi.changes_type:=chi.changes_type + [account_type];
+        // Check
+        if (b AND $F8)<>0 then Exit;
+        TStreamOp.ReadAccountKey(Stream,chi.New_Accountkey);
+        TStreamOp.ReadAnsiString(Stream,chi.New_Name);
+        Stream.Read(chi.New_Type,Sizeof(chi.New_Type));
+        TStreamOp.ReadAnsiString(Stream,chi.Signature.r);
+        TStreamOp.ReadAnsiString(Stream,chi.Signature.s);
+        //
+        changes[i]:=chi;
+      end;
+    end;
+    Result := AddTx(txsenders,txreceivers,False); // Important: Set in same order!
+    Result := Result or AddChangeInfo(changes,False); // Important: Set in same order!
+  Except
+    On E:Exception do begin
+      TLog.NewLog(lterror,Self.ClassName,'('+E.ClassName+'):'+E.Message);
+      Result := False;
     end;
   end;
-  // Note: SignatureValue has not effect on Recalc because is not hashed, so we don't need to call Recalc
 end;
 
-function TTxInfoSender.toString: String;
-Var i : Integer;
+function TOpMultiOperation.GetBufferForOpHash(UseProtocolV2: Boolean): TRawBytes;
+var old : Boolean;
 begin
-  Result := '';
-  for i:=0 to Count-1 do begin
-    Result := Result + Format('%d:(%s,%s,%s)',[i+1,TAccountComp.AccountNumberToAccountTxtNumber(AccountTxInfo[i].Account),
-      TAccountComp.FormatMoney(AccountTxInfo[i].Amount),TAccountComp.FormatMoney(AccountTxInfo[i].Fee)]);
+  // Note: Will set FIsBufferingForOpHash to true because we don't want to hash the signature value
+  old := FSaveSignatureValue;
+  try
+    FSaveSignatureValue := False;
+    Result:=inherited GetBufferForOpHash(UseProtocolV2);
+  finally
+    FSaveSignatureValue:=old;
   end;
 end;
 
-{ TTxInfo }
-
-procedure TTxInfo.Recalc;
+function TOpMultiOperation.CheckSignatures(AccountTransaction: TPCSafeBoxTransaction; var errors: AnsiString): Boolean;
+var i : Integer;
+  acc : TAccount;
+  ophtosign : TRawBytes;
 begin
-  If (FDisableds>0) then begin
-    FNeedRecalc:=True;
-    Exit;
+  // Init
+  FSignatureChecked:=True;
+  FHasValidSignature:=False;
+  SetLength(errors,0);
+  // Do check it!
+  Try
+    ophtosign := GetTransactionHashToSign;
+    // Tx verification
+    For i:=Low(FData.txSenders) to High(FData.txSenders) do begin
+      acc := AccountTransaction.Account(FData.txSenders[i].Account);
+      If (length(FData.txSenders[i].Signature.r)>0) And
+         (length(FData.txSenders[i].Signature.s)>0) then begin
+        If Not TCrypto.ECDSAVerify(acc.accountInfo.accountkey,ophtosign,FData.txSenders[i].Signature) then begin
+          errors := Format('Invalid signature for sender %d/%d',[i+1,length(FData.txSenders)]);
+          Exit;
+        end;
+      end else begin
+        errors := Format('sender not signed %d/%d',[i+1,length(FData.txSenders)]);
+        Exit;
+      end;
+    end;
+    // Change verification
+    For i:=Low(FData.changesInfo) to High(FData.changesInfo) do begin
+      acc := AccountTransaction.Account(FData.changesInfo[i].Account);
+      If (length(FData.changesInfo[i].Signature.r)>0) And
+         (length(FData.changesInfo[i].Signature.s)>0) then begin
+        If Not TCrypto.ECDSAVerify(acc.accountInfo.accountkey,ophtosign,FData.changesInfo[i].Signature) then begin
+          errors := Format('Invalid signature for change info %d/%d',[i+1,length(FData.changesInfo)]);
+          Exit;
+        end;
+      end else begin
+        errors := Format('change info not signed %d/%d',[i+1,length(FData.changesInfo)]);
+        Exit;
+      end;
+    end;
+    // If here... all Ok
+    FHasValidSignature:=True;
+  finally
+    Result := FHasValidSignature;
   end;
-  FNeedRecalc:=False;
-  If Count>0 then InternalRecalc
-  else FHash:='';
 end;
 
-function TTxInfo.GetAccounTxInfo(index : Integer): TAccountTxInfo;
+function TOpMultiOperation.DoOperation(AccountTransaction: TPCSafeBoxTransaction; var errors: AnsiString): Boolean;
 begin
-  If (index<low(FAccountTxInfo)) Or (index>High(FAccountTxInfo)) then Raise Exception.Create(ClassName+' invalid index');
-  Result := FAccountTxInfo[index];
+  // TODO
+  { XXXXXXXXXXXXXXXXXXXXXXXXXX
+
+  Implementation as expected and explained at PIP-0017
+
+  Note: I've added "payload", that must be checked too
+
+
+
+  TODO:
+  - If a destination account is for a PRIVATE SALE... must work same as working currently for TOpTransaction
+    when opTransactionStyle is in transaction_with_auto_buy_account?
+    - IMPORTANT: If Yes, then is possible that a future change operation in same multioperation
+      does not work due changed PUBLIC KEY when executing transaction_with_auto_buy_account
+    - We can limit multioperation to not be able to "auto buy" account, simply add coins to target,
+      this solves possible bad checking
+
+  - When changing name of accounts in a multioperation, is possible that 2 accounts wants
+    to set same name for account. Must prevent this! <-- Partially Prevented thanks to "IndexOfAccountChangeNameTo"
+    - Must prevent that can set new names in real safebox
+
+  - Conclusion:
+    - Prior to execute each "setAccount", must check ALL is ok
+    - HARD JOB!
+
+  }
+  Raise Exception.Create('NOT IMPLEMENTED ERROR DEV 20180308-1');
+  Result := False;
 end;
 
-function TTxInfo.GetCount: Integer;
+procedure TOpMultiOperation.AffectedAccounts(list: TList);
+Var i : Integer;
+  Procedure _doAdd(nAcc : Cardinal);
+  Begin
+    If list.IndexOf(TObject(nAcc))<0 then list.Add(TObject(nAcc));
+  end;
 begin
-  Result := Length(FAccountTxInfo);
+  For i:=low(FData.txSenders) to High(FData.txSenders) do begin
+    _doAdd(FData.txSenders[i].Account);
+  end;
+  For i:=Low(FData.txReceivers) to High(FData.txReceivers) do begin
+    _doAdd(FData.txReceivers[i].Account);
+  end;
+  For i:=Low(FData.changesInfo) to High(FData.changesInfo) do begin
+    _doAdd(FData.changesInfo[i].Account);
+  end;
 end;
 
-procedure TTxInfo.LoadFromStream(stream: TStream);
-Var w : Word;
-  i : Integer;
-  tx : TAccountTxInfo;
+function TOpMultiOperation.GetTransactionHashToSign : TRawBytes;
+Var ms : TMemoryStream;
+  rb : TRawBytes;
+  old : Boolean;
 begin
-  Clear;
-  stream.read(w,SizeOf(w));
-  SetLength(FAccountTxInfo,w);
-  If w>0 then begin
-    for i:=0 to w-1 do begin
-      InternalLoadTxFromStream(stream,tx);
-      If IndexOf(tx.Account)<0 then FAccountTxInfo[i] := tx
-      else begin
-        Clear;
-        Raise Exception.Create(Format('Error reading TTxInfo Stream. Cannot add Account %d (%d/%d)',[tx.Account,i+1,w]));
-      end;
+  ms := TMemoryStream.Create;
+  try
+    old := FSaveSignatureValue;
+    Try
+      FSaveSignatureValue:=False;
+      SaveOpToStream(ms,False);
+    finally
+      FSaveSignatureValue:=old;
     end;
+    SetLength(Result,ms.Size);
+    ms.Position := 0;
+    ms.ReadBuffer(Result[1],ms.Size);
+  finally
+    ms.Free;
   end;
-  Recalc;
 end;
 
-procedure TTxInfo.SaveToStream(stream: TStream);
+function TOpMultiOperation.DoSignMultiOperationSigner(SignerAccount : Cardinal; key : TECPrivateKey) : Integer;
 Var i : Integer;
-  w : Word;
+  raw : TRawBytes;
+  _sign : TECDSA_SIG;
 begin
-  w := Count;
-  stream.Write(w,Sizeof(w));
-  If w>0 then begin
-    for i:=0 to w-1 do begin
-      InternalSaveTxToStream(stream,AccountTxInfo[i]);
+  Result := 0;
+  If Not Assigned(key.PrivateKey) then begin
+    exit;
+  end;
+  raw := GetTransactionHashToSign;
+  Try
+    _sign := TCrypto.ECDSASign(key.PrivateKey,raw);
+  Except
+    On E:Exception do begin
+      TLog.NewLog(ltError,ClassName,'Error signing ('+E.ClassName+') '+E.Message);
+      Exit;
     end;
+  End;
+  For i:=Low(FData.txSenders) to High(FData.txSenders) do begin
+    If FData.txSenders[i].Account=SignerAccount then begin
+      FData.txSenders[i].Signature := _sign;
+      inc(Result);
+    end;
+  end;
+  For i:=Low(FData.changesInfo) to High(FData.changesInfo) do begin
+    If FData.changesInfo[i].Account=SignerAccount then begin
+      FData.changesInfo[i].Signature := _sign;
+      inc(Result);
+    end;
+  end;
+  If (Result>0) Then begin
+    FSignatureChecked := False;
+    FHasValidSignature := False;
   end;
 end;
 
-procedure TTxInfo.InternalRecalc;
-Var stream : TMemoryStream;
-  i : Integer;
-  tx : TAccountTxInfo;
+class function TOpMultiOperation.OpType: Byte;
 begin
-  { Will SHA256( for each account (account+amount+fee+N_Operation+payload) )
-    Note: Will NOT hash the Signature value as explained at PIP-0017 in order to create
-    a OpHash that can be known prior to every account has signed }
-  If Count=0 then begin
-    FHash:='';
-    FTotalAmount:=0;
-    FTotalFees:=0;
-    Exit;
-  end;
-  stream := TMemoryStream.Create;
-  try
-    for i:=0 to Count-1 do begin
-      tx := AccountTxInfo[i];
-      stream.Write(tx.Account,SizeOf(tx.Account));
-      stream.Write(tx.Amount,SizeOf(tx.Amount));
-      stream.Write(tx.Fee,SizeOf(tx.Fee));
-      stream.Write(tx.N_Operation,SizeOf(tx.N_Operation));
-      If length(tx.Payload)>0 then stream.WriteBuffer(tx.Payload[1],Length(tx.Payload));
-      inc(FTotalAmount,tx.Amount);
-      inc(FTotalFees,tx.Fee);
-    end;
-    stream.Position:=0;
-    FHash := TCrypto.DoSha256(TMemoryStream(stream).Memory,stream.Size);
-  finally
-    stream.Free;
-  end;
+  Result := CT_Op_MultiOperation;
 end;
 
-constructor TTxInfo.Create;
+function TOpMultiOperation.OperationAmount: Int64;
 begin
-  SetLength(FAccountTxInfo,0);
-  FDisableds := 0;
-  FNeedRecalc:=False;
-  FHash:='';
-  Clear;
+  Result := FTotalAmount;
 end;
 
-destructor TTxInfo.Destroy;
+function TOpMultiOperation.OperationFee: UInt64;
 begin
-  inherited Destroy;
+  If FTotalFee<0 then Result := 0 // Alert!
+  else Result := FTotalFee;
 end;
 
-function TTxInfo.IndexOf(searchAccount: Cardinal): Integer;
+function TOpMultiOperation.OperationPayload: TRawBytes;
 begin
-  For Result:=low(FAccountTxInfo) to high(FAccountTxInfo) do begin
-    If (FAccountTxInfo[Result].Account = searchAccount) then Exit;
-  end;
-  Result := -1;
+  Result := '';
 end;
 
-procedure TTxInfo.DeleteAccount(Account: Integer);
-Var i,j : Integer;
+function TOpMultiOperation.SignerAccount: Cardinal;
 begin
-  i := IndexOf(Account);
-  If i<0 then Exit;
-  For j:=i+1 to High(FAccountTxInfo) do begin
-    FAccountTxInfo[j-1] := FAccountTxInfo[j];
-  end;
-  SetLength(FAccountTxInfo,length(FAccountTxInfo)-1);
-  Recalc;
+  // On a multioperation, the signer account are senders N accounts, cannot verify which one is correct... will send first one
+  If length(FData.txSenders)>0 then Result := FData.txSenders[0].Account
+  else Result := MaxInt;
 end;
 
-procedure TTxInfo.Disable;
+function TOpMultiOperation.DestinationAccount: Int64;
 begin
-  Inc(FDisableds);
+  Result:=inherited DestinationAccount;
 end;
 
-procedure TTxInfo.Enable;
+function TOpMultiOperation.SellerAccount: Int64;
 begin
-  If FDisableds<=0 then Raise Exception.Create('ERROR DEV 20180306-2');
-  Dec(FDisableds);
-  If (FDisableds=0) And (FNeedRecalc) then Recalc;
+  Result:=inherited SellerAccount;
 end;
 
-procedure TTxInfo.Clear;
+function TOpMultiOperation.N_Operation: Cardinal;
 begin
-  SetLength(FAccountTxInfo,0);
-  FHash:='';
-  FTotalAmount:=0;
-  FTotalFees:=0;
+  // On a multitoperation, there are senders N accounts, need specify
+  Result := 0;  // Note: N_Operation = 0 means NO OPERATION
 end;
 
-function TTxInfo.GetHash: TRawBytes;
+constructor TOpMultiOperation.CreateMultiOperation(
+  const senders: TMultiOpSenders; const receivers: TMultiOpReceivers;
+  const changes: TMultiOpChangesInfo; const senders_keys,
+  changes_keys: array of TECPrivateKey);
+Var i : Integer;
 begin
-  If (FDisableds>0) Or (FNeedRecalc) then Raise Exception.Create('ERROR DEV 20180306-3');
-  Result := FHash;
+  inherited Create;
+  AddTx(senders,receivers,True);
+  AddChangeInfo(changes,True);
+  // Protection for "Exit"
+  FSignatureChecked:=True;
+  FHasValidSignature:=False;
+  If (length(senders_keys)<>length(senders)) then exit; // Cannot sign!
+  If (length(changes_keys)<>length(changes)) then exit; // Cannot sign!
+  For i:=low(senders) to high(senders) do begin
+    If DoSignMultiOperationSigner(senders[i].Account,senders_keys[i])=0 then begin
+      TLog.NewLog(lterror,Classname,'Error signing a new MultiOperation sender');
+      Exit;
+    end;
+  end;
+  For i:=Low(changes) to high(changes) do begin
+    If DoSignMultiOperationSigner(changes[i].Account,changes_keys[i])=0 then begin
+      TLog.NewLog(lterror,Classname,'Error signing a new MultiOperation change');
+      Exit;
+    end;
+  end;
+  // Check as a Valid after everybody signed properly
+  FSignatureChecked:=True;
+  FHasValidSignature:=True;
 end;
 
-function TTxInfo.TotalAmount: Int64;
+function TOpMultiOperation.AddTx(const senders: TMultiOpSenders; const receivers: TMultiOpReceivers; setInRandomOrder : Boolean) : Boolean;
+Var i,j,k : Integer;
+  total_spend, total_receive : Int64;
 begin
-  If (FDisableds>0) Or (FNeedRecalc) then Raise Exception.Create('ERROR DEV 20180306-4');
-  Result := FTotalAmount;
+  Result := False;
+  total_spend:=0;
+  total_receive:=0;
+  // Check not duplicate
+  For i:=Low(senders) to High(senders) do begin
+    If IndexOfAccountSender(senders[i].Account)>=0 then Exit;
+  end;
+  For i:=Low(receivers) to High(receivers) do begin
+    If IndexOfAccountReceiver(receivers[i].Account)>=0 then Exit;
+  end;
+  // Ok, let's go
+  FHasValidSignature:=False;
+  FSignatureChecked:=False;
+  // Important:
+  // When a sender/receiver is added, everybody must sign again
+  // In order to create high anonymity, will add senders/receivers in random order
+  // to difficult know who was the first or last to add
+  For i:=Low(senders) to High(senders) do begin
+    SetLength(FData.txSenders,length(FData.txSenders)+1);
+    If setInRandomOrder then begin
+      // Set sender in a random order
+      If (length(FData.txSenders)>0) then begin
+        j := Random(length(FData.txSenders)); // Find random position 0..n-1
+      end else j:=0;
+      for k:=High(FData.txSenders) downto (j+1) do FData.txSenders[k] := FData.txSenders[k-1];
+    end else j:=High(FData.txSenders);
+    FData.txSenders[j] := senders[i];
+    inc(total_spend,senders[i].Amount);
+  end;
+  For i:=Low(receivers) to High(receivers) do begin
+    SetLength(FData.txReceivers,length(FData.txReceivers)+1);
+    If setInRandomOrder then begin
+      // Set receiver in a random order
+      If (length(FData.txReceivers)>0) then begin
+        j := Random(length(FData.txReceivers)); // Find random position 0..n-1
+      end else j:=0;
+      for k:=High(FData.txReceivers) downto (j+1) do FData.txReceivers[k] := FData.txReceivers[k-1];
+    end else j:=High(FData.txReceivers);
+    FData.txReceivers[j] := receivers[i];
+    inc(total_receive,receivers[i].Amount);
+  end;
+  inc(FTotalAmount,total_receive);
+  inc(FTotalFee,total_spend - total_receive);
+  Result := True;
 end;
 
-function TTxInfo.TotalFees: Int64;
+function TOpMultiOperation.AddChangeInfo(const changes: TMultiOpChangesInfo; setInRandomOrder : Boolean): Boolean;
+Var i,j,k : Integer;
 begin
-  If (FDisableds>0) Or (FNeedRecalc) then Raise Exception.Create('ERROR DEV 20180306-5');
-  Result := FTotalFees;
+  Result := False;
+  // Check not duplicate
+  For i:=Low(changes) to High(changes) do begin
+    If IndexOfAccountChanger(changes[i].Account)>=0 then Exit;
+  end;
+  // Ok, let's go
+  FHasValidSignature:=False;
+  FSignatureChecked:=False;
+  // Important:
+  // When a change is added, everybody must sign again
+  // In order to create high anonymity, will add in random order
+  // to difficult know who was the first or last to add
+  For i:=Low(changes) to High(changes) do begin
+    If (account_name in changes[i].Changes_type) And
+       (IndexOfAccountChangeNameTo(changes[i].New_Name)>=0) then Begin
+       // Does not allow set same name twice!
+       Exit;
+    end;
+    SetLength(FData.changesInfo,length(FData.changesInfo)+1);
+    If setInRandomOrder then begin
+      // Set sender in a random order
+      If (length(FData.changesInfo)>0) then begin
+        j := Random(length(FData.changesInfo)); // Find random position 0..n-1
+      end else j:=0;
+      for k:=High(FData.changesInfo) downto (j+1) do FData.changesInfo[k] := FData.changesInfo[k-1];
+    end else j := High(FData.changesInfo);
+    FData.changesInfo[j] := changes[i];
+  end;
+  Result := True;
 end;
 
-procedure TTxInfo.ToArray(var txArray: TAccountsTxInfoArray);
-Var i : Integer;
+destructor TOpMultiOperation.Destroy;
 begin
-  SetLength(txArray,Count);
-  For i:=0 to Count-1 do begin
-    txArray[i] := GetAccounTxInfo(i);
+  SetLength(FData.txSenders,0);
+  SetLength(FData.txReceivers,0);
+  SetLength(FData.changesInfo,0);
+  inherited Destroy;
+end;
+
+function TOpMultiOperation.toString: String;
+var i : Integer;
+  ssenders,sreceivers,schanges : String;
+begin
+  ssenders := '';
+  for i:=low(FData.txSenders) to High(FData.txSenders) do begin
+    ssenders := ssenders + Format('%d:(%s,%s,%d)',[i+1,TAccountComp.AccountNumberToAccountTxtNumber(FData.txSenders[i].Account),
+        TAccountComp.FormatMoney(FData.txSenders[i].Amount),FData.txSenders[i].N_Operation]);
+  end;
+  sreceivers:='';
+  for i:=low(FData.txReceivers) to High(FData.txReceivers) do begin
+    sreceivers := sreceivers + Format('%d:(%s,%s)',[i+1,TAccountComp.AccountNumberToAccountTxtNumber(FData.txReceivers[i].Account),
+        TAccountComp.FormatMoney(FData.txReceivers[i].Amount)]);
+  end;
+  schanges := '';
+  for i:=low(FData.changesInfo) to High(FData.changesInfo) do begin
+    schanges := schanges + Format('%d:(%s,%d)',[i+1,TAccountComp.AccountNumberToAccountTxtNumber(FData.changesInfo[i].Account),
+        FData.changesInfo[i].N_Operation]);
   end;
+  Result := Format('Multioperation senders %s receivers %s changes %s Amount:%s Fees:%s',
+    [ssenders,sreceivers,schanges,
+     TAccountComp.FormatMoney(FTotalAmount),
+     TAccountComp.FormatMoney(FTotalFee)]);
 end;
 
 end.