فهرست منبع

JSON RPC methods multioperation

New V3 multioperations JSON-RPC methods and objects
PascalCoin 7 سال پیش
والد
کامیت
9ed18d7c3b
5فایلهای تغییر یافته به همراه627 افزوده شده و 16 حذف شده
  1. 90 3
      README.md
  2. 16 0
      src/core/UAccounts.pas
  3. 54 1
      src/core/UCrypto.pas
  4. 466 11
      src/core/URPC.pas
  5. 1 1
      src/core/UTxMultiOperation.pas

+ 90 - 3
README.md

@@ -37,8 +37,8 @@ Also, consider a donation at PascalCoin development account: "0-10"
 ### DEVELOPMENT STATUS
 ### DEVELOPMENT STATUS
 - TODO: PIP - 0010
 - TODO: PIP - 0010
 - TODO: Add new network operations
 - TODO: Add new network operations
-- New target calc on protocol V3 in order to reduce the sinusoidal effect
-  - On V3 will look last 5 blocks instead of last 10 in order to decide if continue increasing/decreasing target
+- TODO: New target calc on protocol V3 in order to reduce the sinusoidal effect
+  - TODO: (on testing...) On V3 will look last 5 blocks instead of last 10 in order to decide if continue increasing/decreasing target
 - New Safebox Snapshoting
 - New Safebox Snapshoting
   - This allow quickly rollback/commit directly to Safebox instead of create a separate Safebox on memory (read from disk... use more ram...)
   - This allow quickly rollback/commit directly to Safebox instead of create a separate Safebox on memory (read from disk... use more ram...)
   - Is usefull when detecting posible orphan blocks in order to check which chain is the highest chain without duplicating a safebox to compare
   - Is usefull when detecting posible orphan blocks in order to check which chain is the highest chain without duplicating a safebox to compare
@@ -70,7 +70,7 @@ Also, consider a donation at PascalCoin development account: "0-10"
         - "amount" : In negative value, due it's outgoing form "account"
         - "amount" : In negative value, due it's outgoing form "account"
         - "payload"
         - "payload"
       - "receivers"
       - "receivers"
-        - "account" : Receoving Account 
+        - "account" : Receiving Account 
         - "amount" : In positive value, due it's incoming from a sender to "account"
         - "amount" : In positive value, due it's incoming from a sender to "account"
         - "payload"
         - "payload"
       - "changers" : Will return an Array with Objects
       - "changers" : Will return an Array with Objects
@@ -79,6 +79,93 @@ Also, consider a donation at PascalCoin development account: "0-10"
         - "new_enc_pubkey" : If public key is changed
         - "new_enc_pubkey" : If public key is changed
         - "new_name" : If name is changed
         - "new_name" : If name is changed
         - "new_type" : If type is changed
         - "new_type" : If type is changed
+  - New object "MultiOperation Object" : Will return info about a MultiOperation
+    - "rawoperations" : HEXASTRING with this single MultiOperation in RAW format
+    - "senders" : Will return an Array with Objects
+      - "account" : Sending Account 
+      - "n_operation"
+      - "amount" : In negative value, due it's outgoing form "account"
+      - "payload"
+    - "receivers"
+      - "account" : Receiving Account 
+      - "amount" : In positive value, due it's incoming from a sender to "account"
+      - "payload"
+    - "changers" : Will return an Array with Objects
+      - "account" : changing Account 
+      - "n_operation"
+      - "new_enc_pubkey" : If public key is changed
+      - "new_name" : If name is changed
+      - "new_type" : If type is changed
+    - "amount" : PASCURRENCY Amount received by receivers
+    - "fee" : PASCURRENCY Equal to "total send" - "total received"
+	- "signed_count" : Integer with info about how many accounts are signed 
+	- "not_signed_count" : Integer with info about how many accounts are pending to be signed
+    - "signed_can_execute"	: Boolean. True if everybody signed. Does not check if MultiOperation is well formed or can be added to Network because is an offline call
+  - New method "signmessage": Signs a digest message using a public key
+    - Params:
+      - "digest" : HEXASTRING with the message to sign
+	  - "b58_pubkey" or "enc_pubkey" : HEXASTRING with the public key that will use to sign "digest" data
+    - Result: False on error
+      - "digest" : HEXASTRING with the message to sign
+	  - "enc_pubkey" : HESATRING with the public key that used to sign "digest" data
+	  - "signature" : HEXASTRING with signature
+  - New method "verifysign": Verify if a digest message is signed by a public key
+    - Params:
+      - "digest" : HEXASTRING with the message to check
+	  - "b58_pubkey" or "enc_pubkey" : HEXASTRING with the public key that used to sign "digest" data
+	  - "signature" : HEXASTRING returned by "signmessage" call
+    - Result: False on error
+      - "digest" : HEXASTRING with the message to check
+	  - "enc_pubkey" : HESATRING with the public key that used to sign "digest" data
+	  - "signature" : HEXASTRING with signature
+  - New method "multioperationaddoperation": Adds operations to a multioperation (or creates a new multioperation and adds new operations)
+    This method does not work with current Safebox state, so can be used offline or on COLD wallets
+    - Params:
+      - "rawoperations" : HEXASTRING (optional) with previous multioperation. If is valid and contains a single  multiopertion will add operations to this one, otherwise will generate a NEW MULTIOPERATION
+      - "senders" : ARRAY of objects that will be Senders of the multioperation
+        - "account" : Integer
+        - "n_operation" : Integer - Must provide a valid n_operation+1 value
+        - "amount" : PASCURRENCY in positive format
+        - "payload" : HEXASTRING
+      - "receivers" : ARRAY of objects that will be Receivers of the multioperation
+        - "account" : Integer
+        - "amount" : PASCURRENCY in positive format
+        - "payload" : HEXASTRING
+      - "changesinfo" : ARRAY of objects that will be accounts executing a changing info
+        - "account" : Integer
+        - "n_operation" : Integer - Must provide a valid n_operation+1 value
+        - "new_b58_pubkey"/"new_enc_pubkey": (optional) If provided will update Public key of "account"
+        - "new_name" : STRING (optional) If provided will change account name
+        - "new_type" : Integer (optional) If provided will change account type
+    - Result:
+      If success will return a "MultiOperation Object"
+  - New method "multioperationsignoffline"
+    This method will sign a Multioperation found in a "rawoperations"
+	Must provide info about accounts and keys (current Safebox state, provided by an ONLINE wallet)
+    - Params:
+      -	"rawoperations" : HEXASTRING with 1 multioperation in Raw format
+      - "accounts_and_keys"	: ARRAY of objects with info about accounts and public keys to sign
+        - "account" : Integer 
+        - "b58_pubkey" or "enc_pubkey" : HEXASTRING with the public key of the account
+    - Result:
+      If success will return a "MultiOperation Object"
+  - New method "multioperationsignonline"
+    This method will sign a Multioperation found in a "rawoperations" based on current safebox state public keys
+	Must provide info about accounts and keys (current Safebox state, provided by an ONLINE wallet)
+    - Params:
+      -	"rawoperations" : HEXASTRING with 1 multioperation in Raw format
+    - Result:
+      If success will return a "MultiOperation Object"
+  - New method "operationsdelete"
+    This method will delete an operation included in a Raw operations object
+    - Params:
+      -	"rawoperations" : HEXASTRING with Raw Operations Object
+    - Result:
+      If success will return a "Raw Operations Object"
+      - "rawoperations" : HEXASTRING with operations in Raw format
+      - "operations" : Integer
+      - "amount" : PASCURRENCY
+      - "fee" : PASCURRENCY  
 - Protections against invalid nodes (scammers):
 - Protections against invalid nodes (scammers):
   - Protection on GetBlocks and GetBlockOperations
   - Protection on GetBlocks and GetBlockOperations
 - Merged new GUI with current stable core
 - Merged new GUI with current stable core

+ 16 - 0
src/core/UAccounts.pas

@@ -459,12 +459,16 @@ Type
     Function FindAccountByNameInTransaction(const findName : TRawBytes; out isAddedInThisTransaction, isDeletedInThisTransaction : Boolean) : Integer;
     Function FindAccountByNameInTransaction(const findName : TRawBytes; out isAddedInThisTransaction, isDeletedInThisTransaction : Boolean) : Integer;
   End;
   End;
 
 
+  { TStreamOp }
+
   TStreamOp = Class
   TStreamOp = Class
   public
   public
     class Function WriteAnsiString(Stream: TStream; const value: AnsiString): Integer; overload;
     class Function WriteAnsiString(Stream: TStream; const value: AnsiString): Integer; overload;
     class Function ReadAnsiString(Stream: TStream; var value: AnsiString): Integer; overload;
     class Function ReadAnsiString(Stream: TStream; var value: AnsiString): Integer; overload;
     class Function WriteAccountKey(Stream: TStream; const value: TAccountKey): Integer;
     class Function WriteAccountKey(Stream: TStream; const value: TAccountKey): Integer;
     class Function ReadAccountKey(Stream: TStream; var value : TAccountKey): Integer;
     class Function ReadAccountKey(Stream: TStream; var value : TAccountKey): Integer;
+    class Function SaveStreamToRaw(Stream: TStream) : TRawBytes;
+    class procedure LoadStreamFromRaw(Stream: TStream; const raw : TRawBytes);
   End;
   End;
 
 
 
 
@@ -856,6 +860,18 @@ begin
   Result := value.EC_OpenSSL_NID;
   Result := value.EC_OpenSSL_NID;
 end;
 end;
 
 
+class function TStreamOp.SaveStreamToRaw(Stream: TStream): TRawBytes;
+begin
+  SetLength(Result,Stream.Size);
+  Stream.Position:=0;
+  Stream.ReadBuffer(Result[1],Stream.Size);
+end;
+
+class procedure TStreamOp.LoadStreamFromRaw(Stream: TStream; const raw: TRawBytes);
+begin
+  Stream.WriteBuffer(raw[1],Length(raw));
+end;
+
 class function TStreamOp.ReadAnsiString(Stream: TStream; var value: AnsiString): Integer;
 class function TStreamOp.ReadAnsiString(Stream: TStream; var value: AnsiString): Integer;
 Var
 Var
   l: Word;
   l: Word;

+ 54 - 1
src/core/UCrypto.pas

@@ -70,7 +70,8 @@ Type
   public
   public
     class function IsHexString(const AHexString: AnsiString) : boolean;
     class function IsHexString(const AHexString: AnsiString) : boolean;
     class function ToHexaString(const raw : TRawBytes) : AnsiString;
     class function ToHexaString(const raw : TRawBytes) : AnsiString;
-    class function HexaToRaw(const HexaString : AnsiString) : TRawBytes;
+    class function HexaToRaw(const HexaString : AnsiString) : TRawBytes; overload;
+    class function HexaToRaw(const HexaString : AnsiString; out raw : TRawBytes) : Boolean; overload;
     class function DoSha256(p : PAnsiChar; plength : Cardinal) : TRawBytes; overload;
     class function DoSha256(p : PAnsiChar; plength : Cardinal) : TRawBytes; overload;
     class function DoSha256(const TheMessage : AnsiString) : TRawBytes; overload;
     class function DoSha256(const TheMessage : AnsiString) : TRawBytes; overload;
     class procedure DoSha256(const TheMessage : AnsiString; out ResultSha256 : TRawBytes);  overload;
     class procedure DoSha256(const TheMessage : AnsiString; out ResultSha256 : TRawBytes);  overload;
@@ -85,6 +86,8 @@ Type
     class function ECDSAVerify(PubKey : TECDSA_Public; const digest : AnsiString; Signature : TECDSA_SIG) : Boolean; overload;
     class function ECDSAVerify(PubKey : TECDSA_Public; const digest : AnsiString; Signature : TECDSA_SIG) : Boolean; overload;
     class procedure InitCrypto;
     class procedure InitCrypto;
     class function IsHumanReadable(Const ReadableText : TRawBytes) : Boolean;
     class function IsHumanReadable(Const ReadableText : TRawBytes) : Boolean;
+    class function EncodeSignature(const signature : TECDSA_SIG) : TRawBytes;
+    class function DecodeSignature(const rawSignature : TRawBytes; out signature : TECDSA_SIG) : Boolean;
   End;
   End;
 
 
   TBigNum = Class
   TBigNum = Class
@@ -129,6 +132,7 @@ Type
 
 
 Const
 Const
   CT_TECDSA_Public_Nul : TECDSA_Public = (EC_OpenSSL_NID:0;x:'';y:'');
   CT_TECDSA_Public_Nul : TECDSA_Public = (EC_OpenSSL_NID:0;x:'';y:'');
+  CT_TECDSA_SIG_Nul : TECDSA_SIG = (r:'';s:'');
 
 
 implementation
 implementation
 
 
@@ -524,6 +528,24 @@ begin
   end;
   end;
 end;
 end;
 
 
+class function TCrypto.HexaToRaw(const HexaString: AnsiString; out raw: TRawBytes): Boolean;
+Var P : PAnsiChar;
+ lc : AnsiString;
+ i : Integer;
+begin
+  Result := False; raw := '';
+  if ((length(HexaString) MOD 2)<>0) then Exit;
+  if (length(HexaString)=0) then begin
+    Result := True;
+    exit;
+  end;
+  SetLength(raw,length(HexaString) DIV 2);
+  P := @raw[1];
+  lc := LowerCase(HexaString);
+  i := HexToBin(PAnsiChar(@lc[1]),P,length(raw));
+  Result := (i = (length(HexaString) DIV 2));
+end;
+
 class procedure TCrypto.InitCrypto;
 class procedure TCrypto.InitCrypto;
 begin
 begin
   _DoInit;
   _DoInit;
@@ -541,6 +563,37 @@ Begin
   end;
   end;
 end;
 end;
 
 
+class function TCrypto.EncodeSignature(const signature: TECDSA_SIG): TRawBytes;
+Var ms : TStream;
+begin
+  ms := TMemoryStream.Create;
+  Try
+    TStreamOp.WriteAnsiString(ms,signature.r);
+    TStreamOp.WriteAnsiString(ms,signature.s);
+    Result := TStreamOp.SaveStreamToRaw(ms);
+  finally
+    ms.Free;
+  end;
+end;
+
+class function TCrypto.DecodeSignature(const rawSignature : TRawBytes; out signature : TECDSA_SIG) : Boolean;
+var ms : TStream;
+begin
+  signature := CT_TECDSA_SIG_Nul;
+  Result := False;
+  ms := TMemoryStream.Create;
+  Try
+    TStreamOp.LoadStreamFromRaw(ms,rawSignature);
+    ms.Position:=0;
+    if TStreamOp.ReadAnsiString(ms,signature.r)<0 then Exit;
+    if TStreamOp.ReadAnsiString(ms,signature.s)<0 then Exit;
+    if ms.Position<ms.Size then Exit; // Invalid position
+    Result := True;
+  finally
+    ms.Free;
+  end;
+end;
+
 class function TCrypto.PrivateKey2Hexa(Key: PEC_KEY): AnsiString;
 class function TCrypto.PrivateKey2Hexa(Key: PEC_KEY): AnsiString;
 Var p : PAnsiChar;
 Var p : PAnsiChar;
 begin
 begin

+ 466 - 11
src/core/URPC.pas

@@ -20,7 +20,7 @@ unit URPC;
 interface
 interface
 
 
 Uses UThread, ULog, UConst, UNode, UAccounts, UCrypto, UBlockChain,
 Uses UThread, ULog, UConst, UNode, UAccounts, UCrypto, UBlockChain,
-  UNetProtocol, UOpTransaction, UWallet, UTime, UAES, UECIES,
+  UNetProtocol, UOpTransaction, UWallet, UTime, UAES, UECIES, UTxMultiOperation,
   UJSONFunctions, classes, blcksock, synsock, IniFiles, Variants, math;
   UJSONFunctions, classes, blcksock, synsock, IniFiles, Variants, math;
 
 
 Const
 Const
@@ -36,6 +36,7 @@ Const
   CT_RPC_ErrNum_NotFound = 1010;
   CT_RPC_ErrNum_NotFound = 1010;
   CT_RPC_ErrNum_WalletPasswordProtected = 1015;
   CT_RPC_ErrNum_WalletPasswordProtected = 1015;
   CT_RPC_ErrNum_InvalidData = 1016;
   CT_RPC_ErrNum_InvalidData = 1016;
+  CT_RPC_ErrNum_InvalidSignature = 1020;
 
 
 Type
 Type
 
 
@@ -52,6 +53,7 @@ Type
     class procedure FillBlockObject(nBlock : Cardinal; ANode : TNode; jsonObject: TPCJSONObject);
     class procedure FillBlockObject(nBlock : Cardinal; ANode : TNode; jsonObject: TPCJSONObject);
     class procedure FillOperationObject(Const OPR : TOperationResume; currentNodeBlocksCount : Cardinal; jsonObject : TPCJSONObject);
     class procedure FillOperationObject(Const OPR : TOperationResume; currentNodeBlocksCount : Cardinal; jsonObject : TPCJSONObject);
     class procedure FillOperationsHashTreeObject(Const OperationsHashTree : TOperationsHashTree; jsonObject : TPCJSONObject);
     class procedure FillOperationsHashTreeObject(Const OperationsHashTree : TOperationsHashTree; jsonObject : TPCJSONObject);
+    class procedure FillMultiOperationObject(Const multiOperation : TOpMultiOperation; jsonObject : TPCJSONObject);
     class procedure FillPublicKeyObject(const PubKey : TAccountKey; jsonObj : TPCJSONObject);
     class procedure FillPublicKeyObject(const PubKey : TAccountKey; jsonObj : TPCJSONObject);
   end;
   end;
 
 
@@ -283,6 +285,72 @@ begin
   jsonObject.GetAsVariant('rawoperations').Value:=OperationsHashTreeToHexaString(OperationsHashTree);
   jsonObject.GetAsVariant('rawoperations').Value:=OperationsHashTreeToHexaString(OperationsHashTree);
 end;
 end;
 
 
+class procedure TPascalCoinJSONComp.FillMultiOperationObject(const multiOperation: TOpMultiOperation; jsonObject: TPCJSONObject);
+Var i, nSigned, nNotSigned : Integer;
+  opht : TOperationsHashTree;
+  jsonArr : TPCJSONArray;
+  auxObj : TPCJSONObject;
+begin
+  opht := TOperationsHashTree.Create;
+  Try
+    opht.AddOperationToHashTree(multiOperation);
+    jsonObject.GetAsVariant('rawoperations').Value:=OperationsHashTreeToHexaString(opht);
+  finally
+    opht.Free;
+  end;
+  nSigned := 0; nNotSigned := 0;
+  for i:=0 to High(multiOperation.Data.txSenders) do begin
+    If (multiOperation.Data.txSenders[i].Signature.r<>'') And  (multiOperation.Data.txSenders[i].Signature.s<>'') then inc(nSigned)
+    else inc(nNotSigned);
+  end;
+  for i:=0 to High(multiOperation.Data.changesInfo) do begin
+    If (multiOperation.Data.changesInfo[i].Signature.r<>'') And  (multiOperation.Data.changesInfo[i].Signature.s<>'') then inc(nSigned)
+    else inc(nNotSigned);
+  end;
+  //
+  jsonArr := jsonObject.GetAsArray('senders');
+  for i:=Low(multiOperation.Data.txSenders) to High(multiOperation.Data.txSenders) do begin
+    auxObj := jsonArr.GetAsObject(jsonArr.Count);
+    auxObj.GetAsVariant('account').Value := multiOperation.Data.txSenders[i].Account;
+    auxObj.GetAsVariant('n_operation').Value := multiOperation.Data.txSenders[i].N_Operation;
+    auxObj.GetAsVariant('amount').Value := ToJSONCurrency(multiOperation.Data.txSenders[i].Amount * (-1));
+    auxObj.GetAsVariant('payload').Value := TCrypto.ToHexaString(multiOperation.Data.txSenders[i].Payload);
+  end;
+  //
+  jsonArr := jsonObject.GetAsArray('receivers');
+  for i:=Low(multiOperation.Data.txReceivers) to High(multiOperation.Data.txReceivers) do begin
+    auxObj := jsonArr.GetAsObject(jsonArr.Count);
+    auxObj.GetAsVariant('account').Value := multiOperation.Data.txReceivers[i].Account;
+    auxObj.GetAsVariant('amount').Value := ToJSONCurrency(multiOperation.Data.txReceivers[i].Amount);
+    auxObj.GetAsVariant('payload').Value := TCrypto.ToHexaString(multiOperation.Data.txReceivers[i].Payload);
+  end;
+  jsonArr := jsonObject.GetAsArray('changers');
+  for i:=Low(multiOperation.Data.changesInfo) to High(multiOperation.Data.changesInfo) do begin
+    auxObj := jsonArr.GetAsObject(jsonArr.Count);
+    auxObj.GetAsVariant('account').Value := multiOperation.Data.changesInfo[i].Account;
+    auxObj.GetAsVariant('n_operation').Value := multiOperation.Data.changesInfo[i].N_Operation;
+    If public_key in multiOperation.Data.changesInfo[i].Changes_type then begin
+      auxObj.GetAsVariant('new_enc_pubkey').Value := TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(multiOperation.Data.changesInfo[i].New_Accountkey));
+    end;
+    If account_name in multiOperation.Data.changesInfo[i].Changes_type then begin
+      auxObj.GetAsVariant('new_name').Value := multiOperation.Data.changesInfo[i].New_Name;
+    end;
+    If account_type in multiOperation.Data.changesInfo[i].Changes_type then begin
+      auxObj.GetAsVariant('new_type').Value := multiOperation.Data.changesInfo[i].New_Type;
+    end;
+  end;
+  jsonObject.GetAsVariant('amount').Value:=ToJSONCurrency( multiOperation.OperationAmount );
+  jsonObject.GetAsVariant('fee').Value:=ToJSONCurrency( multiOperation.OperationFee );
+
+  jsonObject.GetAsVariant('senders_count').Value:=Length(multiOperation.Data.txSenders);
+  jsonObject.GetAsVariant('receivers_count').Value:=Length(multiOperation.Data.txReceivers);
+  jsonObject.GetAsVariant('changesinfo_count').Value:=Length(multiOperation.Data.changesInfo);
+  jsonObject.GetAsVariant('signed_count').Value:=nSigned;
+  jsonObject.GetAsVariant('not_signed_count').Value:=nNotSigned;
+  //
+  jsonObject.GetAsVariant('signed_can_execute').Value:=(nSigned>0) And (nNotSigned=0);
+end;
+
 class procedure TPascalCoinJSONComp.FillPublicKeyObject(const PubKey: TAccountKey; jsonObj: TPCJSONObject);
 class procedure TPascalCoinJSONComp.FillPublicKeyObject(const PubKey: TAccountKey; jsonObj: TPCJSONObject);
 begin
 begin
   jsonObj.GetAsVariant('ec_nid').Value := PubKey.EC_OpenSSL_NID;
   jsonObj.GetAsVariant('ec_nid').Value := PubKey.EC_OpenSSL_NID;
@@ -646,6 +714,35 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     End;
     End;
   End;
   End;
 
 
+  Function HexaStringToOperationsHashTreeAndGetMultioperation(Const HexaStringOperationsHashTree : AnsiString; canCreateNewOne : Boolean; out OperationsHashTree : TOperationsHashTree; out multiOperation : TOpMultiOperation; var errors : AnsiString) : 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
+      }
+  var op : TPCOperation;
+  Begin
+    multiOperation := Nil;
+    Result := HexaStringToOperationsHashTree(HexaStringOperationsHashTree,OperationsHashTree,errors);
+    If (Result) then begin
+      Try
+        If (OperationsHashTree.OperationsCount=0) And (canCreateNewOne) then begin
+          multiOperation := TOpMultiOperation.Create;
+          OperationsHashTree.AddOperationToHashTree(multiOperation);
+          multiOperation.Free;
+          multiOperation := OperationsHashTree.GetOperation(0) as TOpMultiOperation;
+        end else if (OperationsHashTree.OperationsCount=1) then begin
+          op := OperationsHashTree.GetOperation(0);
+          if (op is TOpMultiOperation) then multiOperation := op as TOpMultiOperation
+          else errors := 'operation is not a multioperation';
+        end else errors := 'No multioperation found';
+      finally
+        If (Not Assigned(multiOperation)) then begin
+          FreeAndNil(OperationsHashTree);
+          Result := false;
+        end;
+      end;
+    end;
+  End;
+
   Function GetBlock(nBlock : Cardinal; jsonObject : TPCJSONObject) : Boolean;
   Function GetBlock(nBlock : Cardinal; jsonObject : TPCJSONObject) : Boolean;
   begin
   begin
     If FNode.Bank.BlocksCount<=nBlock then begin
     If FNode.Bank.BlocksCount<=nBlock then begin
@@ -1384,37 +1481,42 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     Result := true;
     Result := true;
   End;
   End;
 
 
-  Function CapturePubKey(const prefix : String; var pubkey : TAccountKey; var errortxt : String) : Boolean;
+  Function CapturePubKeyExt(const jsonObjParams : TPCJSONObject; const prefix : String; var pubkey : TAccountKey; var errortxt : String) : Boolean;
   var ansistr : AnsiString;
   var ansistr : AnsiString;
     auxpubkey : TAccountKey;
     auxpubkey : TAccountKey;
   begin
   begin
     pubkey := CT_Account_NUL.accountInfo.accountKey;
     pubkey := CT_Account_NUL.accountInfo.accountKey;
     errortxt := '';
     errortxt := '';
     Result := false;
     Result := false;
-    if (params.IndexOfName(prefix+'b58_pubkey')>=0) then begin
-      If Not TAccountComp.AccountPublicKeyImport(params.AsString(prefix+'b58_pubkey',''),pubkey,ansistr) then begin
+    if (jsonObjparams.IndexOfName(prefix+'b58_pubkey')>=0) then begin
+      If Not TAccountComp.AccountPublicKeyImport(jsonObjparams.AsString(prefix+'b58_pubkey',''),pubkey,ansistr) then begin
         errortxt:= 'Invalid value of param "'+prefix+'b58_pubkey": '+ansistr;
         errortxt:= 'Invalid value of param "'+prefix+'b58_pubkey": '+ansistr;
         exit;
         exit;
       end;
       end;
-      if (params.IndexOfName(prefix+'enc_pubkey')>=0) then begin
-        auxpubkey := TAccountComp.RawString2Accountkey(TCrypto.HexaToRaw(params.AsString(prefix+'enc_pubkey','')));
+      if (jsonObjparams.IndexOfName(prefix+'enc_pubkey')>=0) then begin
+        auxpubkey := TAccountComp.RawString2Accountkey(TCrypto.HexaToRaw(jsonObjparams.AsString(prefix+'enc_pubkey','')));
         if (Not TAccountComp.EqualAccountKeys(auxpubkey,pubkey)) then begin
         if (Not TAccountComp.EqualAccountKeys(auxpubkey,pubkey)) then begin
           errortxt := 'Params "'+prefix+'b58_pubkey" and "'+prefix+'enc_pubkey" public keys are not the same public key';
           errortxt := 'Params "'+prefix+'b58_pubkey" and "'+prefix+'enc_pubkey" public keys are not the same public key';
           exit;
           exit;
         end;
         end;
       end;
       end;
     end else begin
     end else begin
-      if (params.IndexOfName(prefix+'enc_pubkey')<0) then begin
+      if (jsonObjparams.IndexOfName(prefix+'enc_pubkey')<0) then begin
         errortxt := 'Need param "'+prefix+'enc_pubkey" or "'+prefix+'b58_pubkey"';
         errortxt := 'Need param "'+prefix+'enc_pubkey" or "'+prefix+'b58_pubkey"';
         exit;
         exit;
       end;
       end;
-      pubkey := TAccountComp.RawString2Accountkey(TCrypto.HexaToRaw(params.AsString(prefix+'enc_pubkey','')));
+      pubkey := TAccountComp.RawString2Accountkey(TCrypto.HexaToRaw(jsonObjparams.AsString(prefix+'enc_pubkey','')));
     end;
     end;
     If Not TAccountComp.IsValidAccountKey(pubkey,ansistr) then begin
     If Not TAccountComp.IsValidAccountKey(pubkey,ansistr) then begin
       errortxt := 'Invalid public key: '+ansistr;
       errortxt := 'Invalid public key: '+ansistr;
     end else Result := true;
     end else Result := true;
   end;
   end;
 
 
+  Function CapturePubKey(const prefix : String; var pubkey : TAccountKey; var errortxt : String) : Boolean;
+  begin
+    Result := CapturePubKeyExt(params,prefix,pubkey,errortxt);
+  end;
+
   function SignListAccountForSaleEx(params : TPCJSONObject; OperationsHashTree : TOperationsHashTree; const actualAccounKey : TAccountKey; last_n_operation : Cardinal) : boolean;
   function SignListAccountForSaleEx(params : TPCJSONObject; OperationsHashTree : TOperationsHashTree; const actualAccounKey : TAccountKey; last_n_operation : Cardinal) : boolean;
     // params:
     // params:
     // "account_signer" is the account that signs operations and pays the fee
     // "account_signer" is the account that signs operations and pays the fee
@@ -1422,7 +1524,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     // "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)
     // "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
     // "price" is the price
     // "seller_account" is the account to pay (seller account)
     // "seller_account" is the account to pay (seller account)
-    // "new_b58_pubkey" or "new_enc_pubke" is the future public key for this sale (private sale), otherwise is open and everybody can buy
+    // "new_b58_pubkey" or "new_enc_pubkey" is the future public key for this sale (private sale), otherwise is open and everybody can buy
   var
   var
     opSale: TOpListAccountForSale;
     opSale: TOpListAccountForSale;
     account_signer, account_target, seller_account : Cardinal;
     account_signer, account_target, seller_account : Cardinal;
@@ -1520,7 +1622,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     // "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)
     // "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
     // "price" is the price
     // "seller_account" is the account to pay
     // "seller_account" is the account to pay
-    // "new_b58_pubkey" or "new_enc_pubke" is the future public key for this sale (private sale), otherwise is open and everybody can buy
+    // "new_b58_pubkey" or "new_enc_pubkey" is the future public key for this sale (private sale), otherwise is open and everybody can buy
   var
   var
     opDelist: TOpDelistAccountForSale;
     opDelist: TOpDelistAccountForSale;
     account_signer, account_target : Cardinal;
     account_signer, account_target : Cardinal;
@@ -1617,7 +1719,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     // params:
     // params:
     // "account_signer" is the account that signs operations and pays the fee
     // "account_signer" is the account that signs operations and pays the fee
     // "account_target" is the target to change info
     // "account_target" is the target to change info
-    // "new_b58_pubkey" or "new_enc_pubke" is the new public key for this account
+    // "new_b58_pubkey" or "new_enc_pubkey" is the new public key for this account
     // "new_name" is the new account name
     // "new_name" is the new account name
     // "new_type" is the new account type
     // "new_type" is the new account type
   var
   var
@@ -2166,6 +2268,341 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     end;
     end;
   end;
   end;
 
 
+  function MultiOperationAddOperation(Const HexaStringOperationsHashTree : TRawBytes; params : TPCJSONObject) : boolean;
+  var errors : AnsiString;
+    OperationsHashTree : TOperationsHashTree;
+    jsonArr : TPCJSONArray;
+    i,j : Integer;
+    sender : TMultiOpSender;
+    receiver : TMultiOpReceiver;
+    changeinfo : TMultiOpChangeInfo;
+    mop : TOpMultiOperation;
+  begin
+    { This will ADD or UPDATE a MultiOperation with NEW field/s
+      - UPDATE: If LAST operation in HexaStringOperationsHashTree RAW value contains a MultiOperation
+      - ADD: Otherwise
+
+      NOTE: This function will not check if provided info is valid (enough balance, valid n_operation...), and can work for COLD STORAGE
+      - "senders" : ARRAY of OBJECT
+        - "account" :
+        - "n_operation" : New n_operation value for account (remember, current + 1)
+        - "amount" : PASCURRENCY
+        - "payload" : HEXASTRING (optional)
+      - "receivers" : ARRAY of OBJECT
+        - "account"
+        - "amount" : PASCURRENCY
+        - "payload" : HEXASTRING (optional)
+      - "changesinfo" : ARRAY of OBJECT
+        - "account"
+        - "n_operation" : New n_operation value for account (remember, current + 1)
+        - "new_b58_pubkey" or "new_enc_pubkey" : (optional) The new public key for this account
+        - "new_name" : (optional) The new account name
+        - "new_type" : (optional) The new account type
+        }
+    Result := false;
+    if Not HexaStringToOperationsHashTreeAndGetMultioperation(HexaStringOperationsHashTree,True,OperationsHashTree,mop,errors) then begin
+      ErrorNum:=CT_RPC_ErrNum_InvalidData;
+      ErrorDesc:= 'Error decoding param previous operations hash tree raw value: '+errors;
+      Exit;
+    end;
+    Try
+      // "senders"
+      jsonArr := params.GetAsArray('senders');
+      for i:=0 to jsonArr.Count-1 do begin
+        sender := CT_TMultiOpSender_NUL;
+        j := jsonArr.GetAsObject(i).AsInteger('account',-1);
+        if j<0 then begin
+          ErrorNum := CT_RPC_ErrNum_InvalidData;
+          ErrorDesc := 'Field "account" for "senders" array not found';
+          Exit;
+        end;
+        sender.Account := j;
+        sender.Amount:= ToPascalCoins(jsonArr.GetAsObject(i).AsDouble('amount',0));
+        sender.N_Operation:=jsonArr.GetAsObject(i).AsInteger('n_operation',0);
+        sender.Payload:=TCrypto.HexaToRaw(jsonArr.GetAsObject(i).AsString('payload',''));
+        if Not mop.AddTxSender(sender) then begin
+          ErrorNum := CT_RPC_ErrNum_InvalidData;
+          ErrorDesc := 'Cannot add sender '+inttostr(sender.Account)+' duplicated or invalid data';
+          Exit;
+        end;
+      end;
+      // "receivers"
+      jsonArr := params.GetAsArray('receivers');
+      for i:=0 to jsonArr.Count-1 do begin
+        receiver := CT_TMultiOpReceiver_NUL;
+        j := jsonArr.GetAsObject(i).AsInteger('account',-1);
+        if j<0 then begin
+          ErrorNum := CT_RPC_ErrNum_InvalidData;
+          ErrorDesc := 'Field "account" for "receivers" array not found';
+          Exit;
+        end;
+        receiver.Account := j;
+        receiver.Amount:= ToPascalCoins(jsonArr.GetAsObject(i).AsDouble('amount',0));
+        receiver.Payload:=TCrypto.HexaToRaw(jsonArr.GetAsObject(i).AsString('payload',''));
+        if Not mop.AddTxReceiver(receiver) then begin
+          ErrorNum := CT_RPC_ErrNum_InvalidData;
+          ErrorDesc := 'Cannot add receiver '+inttostr(receiver.Account)+' invalid data';
+          Exit;
+        end;
+      end;
+      // "changesinfo"
+      jsonArr := params.GetAsArray('changesinfo');
+      for i:=0 to jsonArr.Count-1 do begin
+        changeinfo := CT_TMultiOpChangeInfo_NUL;
+        j := jsonArr.GetAsObject(i).AsInteger('account',-1);
+        if j<0 then begin
+          ErrorNum := CT_RPC_ErrNum_InvalidData;
+          ErrorDesc := 'Field "account" for "changesinfo" array not found';
+          Exit;
+        end;
+        changeinfo.Account := j;
+        changeinfo.N_Operation:=jsonArr.GetAsObject(i).AsInteger('n_operation',0);
+        if (jsonArr.GetAsObject(i).IndexOfName('new_b58_pubkey')>=0) or (jsonArr.GetAsObject(i).IndexOfName('new_enc_pubkey')>=0) then begin
+          changeinfo.Changes_type:=changeinfo.Changes_type + [public_key];
+          If Not CapturePubKeyExt(jsonArr.GetAsObject(i),'new_',changeinfo.New_Accountkey,ErrorDesc) then begin
+            ErrorNum := CT_RPC_ErrNum_InvalidPubKey;
+            Exit;
+          end;
+        end;
+        if (jsonArr.GetAsObject(i).IndexOfName('new_name')>=0) then begin
+          changeinfo.Changes_type:=changeinfo.Changes_type + [account_name];
+          changeinfo.New_Name:=jsonArr.GetAsObject(i).AsString('new_name','');
+        end;
+        if (jsonArr.GetAsObject(i).IndexOfName('new_type')>=0) then begin
+          changeinfo.Changes_type:=changeinfo.Changes_type + [account_type];
+          changeinfo.New_Type:=jsonArr.GetAsObject(i).AsInteger('new_type',0);
+        end;
+        if (changeinfo.Changes_type = []) then begin
+          ErrorNum:=CT_RPC_ErrNum_InvalidData;
+          ErrorDesc:='Need change something for account '+inttostr(changeinfo.Account);
+          Exit;
+        end;
+        if Not mop.AddChangeInfo(changeinfo) then begin
+          ErrorNum := CT_RPC_ErrNum_InvalidData;
+          ErrorDesc := 'Cannot add receiver '+inttostr(receiver.Account)+' duplicated or invalid data';
+          Exit;
+        end;
+      end;
+      // Return multioperation object:
+      TPascalCoinJSONComp.FillMultiOperationObject(mop,GetResultObject);
+    finally
+      OperationsHashTree.Free;
+    end;
+    Result := True;
+  end;
+
+  function DoSignOrVerifyMessage(params : TPCJSONObject) : boolean;
+    { Will sign data or verify a signature
+      In params:
+        - "digest" : HEXASTRING containing data to sign
+        - "b58_pubkey" or "enc_pubkey" : The public key that must sign "digest" data
+        - "signature" : (optional) HEXASTRING If provided, will check if "digest" data is signed by "_pubkey" provided
+      Out object:
+        - "digest" : HEXASTRING containing data
+        - "b58_pubkey" or "enc_pubkey" : The public key that have signed
+        - "signature" : HEXASTRING with
+      If validation is incorrect or errors, will return an error object }
+  var digest : TRawBytes;
+    pubKey : TAccountKey;
+    signature : TECDSA_SIG;
+    iKey : Integer;
+  begin
+    Result := False;
+    if Not TCrypto.HexaToRaw( params.AsString('digest',''),digest ) then begin
+      ErrorNum := CT_RPC_ErrNum_InvalidData;
+      ErrorDesc:= 'Param digest with invalid hexadecimal data';
+      Exit;
+    end;
+    If Not CapturePubKeyExt(params,'',pubKey,ErrorDesc) then begin
+      ErrorNum := CT_RPC_ErrNum_InvalidPubKey;
+      Exit;
+    end;
+    if (params.IndexOfName('signature')>=0) then begin
+      // Verify
+      If Not TCrypto.DecodeSignature( TCrypto.HexaToRaw(params.AsString('signature','')),signature) then begin
+        ErrorNum := CT_RPC_ErrNum_InvalidData;
+        ErrorDesc:= 'Param signature with invalid data';
+        Exit;
+      end;
+      //
+      If TCrypto.ECDSAVerify(pubKey,digest,signature) then begin
+        GetResultObject.GetAsVariant('digest').Value:=TCrypto.ToHexaString(digest);
+        GetResultObject.GetAsVariant('enc_pubkey').Value:=TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(pubKey));
+        GetResultObject.GetAsVariant('signature').Value:=TCrypto.ToHexaString(TCrypto.EncodeSignature(signature));
+        Result := True;
+      end else begin
+        // Invalid signature
+        ErrorNum := CT_RPC_ErrNum_InvalidSignature;
+        ErrorDesc := 'Signature does not match';
+        Exit;
+      end;
+    end else begin
+      // Sign process
+      if (Not (_RPCServer.FWalletKeys.IsValidPassword)) then begin
+        // Wallet is password protected
+        ErrorDesc := 'Wallet is password protected';
+        ErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
+        Exit;
+      end;
+      iKey := _RPCServer.FWalletKeys.IndexOfAccountKey(pubKey);
+      if (iKey<0) then begin
+        ErrorDesc:= 'Public Key not found in wallet: '+TAccountComp.AccountPublicKeyExport(pubKey);
+        ErrorNum := CT_RPC_ErrNum_InvalidPubKey;
+        Exit;
+      end;
+      if (Not Assigned(_RPCServer.FWalletKeys.Key[iKey].PrivateKey)) then begin
+        ErrorDesc:= 'Private key from public Key not found in wallet: '+TAccountComp.AccountPublicKeyExport(pubKey);
+        ErrorNum := CT_RPC_ErrNum_InvalidPubKey;
+        Exit;
+      end;
+      signature := TCrypto.ECDSASign( _RPCServer.FWalletKeys.Key[iKey].PrivateKey.PrivateKey ,digest );
+      //
+      GetResultObject.GetAsVariant('digest').Value:=TCrypto.ToHexaString(digest);
+      GetResultObject.GetAsVariant('enc_pubkey').Value:=TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(pubKey));
+      GetResultObject.GetAsVariant('signature').Value:=TCrypto.ToHexaString(TCrypto.EncodeSignature(signature));
+      Result := True;
+    end;
+  end;
+
+  procedure InternalMultiOperationSignCold(multiOperation : TOpMultiOperation; accounts_and_keys : TPCJSONArray; var signedAccounts : Integer);
+    { Signs a multioperation in a Cold storage, so cannot check if current signatures are valid because public keys of accounts are unknown
+      accounts_and_keys is a JSON ARRAY with Objects:
+      - "account"
+      - "b58_pubkey" or "enc_pubkey" : The public key of the "account"
+    }
+  var i,iKey : Integer;
+    pubKey : TAccountKey;
+    nAccount : Cardinal;
+    _error_desc : AnsiString;
+  begin
+    signedAccounts := 0;
+    if (Not (_RPCServer.FWalletKeys.IsValidPassword)) then Exit;
+    for i := 0 to accounts_and_keys.Count-1 do begin
+      nAccount := accounts_and_keys.GetAsObject(i).AsCardinal('account',CT_MaxAccount);
+      If CapturePubKeyExt(accounts_and_keys.GetAsObject(i),'',pubKey,_error_desc) then begin
+        iKey := _RPCServer.FWalletKeys.IndexOfAccountKey(pubKey);
+        if (iKey>=0) then begin
+          if (Assigned(_RPCServer.FWalletKeys.Key[iKey].PrivateKey)) then begin
+            inc(signedAccounts,multiOperation.DoSignMultiOperationSigner(nAccount,_RPCServer.FWalletKeys.Key[iKey].PrivateKey));
+          end;
+        end;
+      end;
+    end;
+  end;
+
+  function MultiOperationSignCold(Const HexaStringOperationsHashTree : TRawBytes; params : TPCJSONObject) : boolean;
+  var errors : AnsiString;
+    senderOperationsHashTree : TOperationsHashTree;
+    mop : TOpMultiOperation;
+    i,j : Integer;
+  begin
+    { This will SIGN a MultiOperation on a HexaStringOperationsHashTree in COLD mode (without knowledge of current public keys)
+      Must provide param "accounts_and_keys"
+      - "accounts_and_keys" is a JSON ARRAY with Objects:
+        - "account"
+        - "b58_pubkey" or "enc_pubkey" : The public key of the "account"
+      Will Return an OperationsHashTree Object
+    }
+    Result := false;
+    if (Not (_RPCServer.FWalletKeys.IsValidPassword)) then begin
+      // Wallet is password protected
+      ErrorDesc := 'Wallet is password protected';
+      ErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
+      Exit;
+    end;
+    if Not HexaStringToOperationsHashTreeAndGetMultioperation(HexaStringOperationsHashTree,False,senderOperationsHashTree,mop,errors) then begin
+      ErrorNum:=CT_RPC_ErrNum_InvalidData;
+      ErrorDesc:= 'Error decoding param previous operations hash tree raw value: '+errors;
+      Exit;
+    end;
+    Try
+      InternalMultiOperationSignCold(mop,params.GetAsArray('accounts_and_keys'),j);
+      // Return multioperation object:
+      TPascalCoinJSONComp.FillMultiOperationObject(mop,GetResultObject);
+      Result := True;
+    finally
+      senderOperationsHashTree.Free;
+    end;
+  end;
+  function MultiOperationSignOnline(Const HexaStringOperationsHashTree : TRawBytes) : boolean;
+  var errors : AnsiString;
+    senderOperationsHashTree : TOperationsHashTree;
+    j,iKey,nSignedAccounts : Integer;
+    mop : TOpMultiOperation;
+    lSigners : TList;
+    nAccount : Integer;
+    pubKey : TAccountKey;
+  begin
+    Result := false;
+    if (Not (_RPCServer.FWalletKeys.IsValidPassword)) then begin
+      // Wallet is password protected
+      ErrorDesc := 'Wallet is password protected';
+      ErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
+      Exit;
+    end;
+    if Not HexaStringToOperationsHashTreeAndGetMultioperation(HexaStringOperationsHashTree,False,senderOperationsHashTree,mop,errors) then begin
+      ErrorNum:=CT_RPC_ErrNum_InvalidData;
+      ErrorDesc:= 'Error decoding param previous operations hash tree raw value: '+errors;
+      Exit;
+    end;
+    Try
+      nSignedAccounts := 0;
+      lSigners := TList.Create;
+      Try
+        mop.SignerAccounts(lSigners);
+        for j:=0 to lSigners.Count-1 do begin
+          nAccount := PtrInt(lSigners[j]);
+          if (nAccount>=0) And (nAccount<FNode.Bank.AccountsCount) then begin
+            // Try to
+            pubKey := FNode.Operations.SafeBoxTransaction.Account(nAccount).accountInfo.accountKey;
+            // Is mine?
+            iKey := _RPCServer.FWalletKeys.IndexOfAccountKey(pubKey);
+            if (iKey>=0) then begin
+              if (assigned(_RPCServer.FWalletKeys.Key[iKey].PrivateKey)) then begin
+                // Can sign
+                inc(nSignedAccounts, mop.DoSignMultiOperationSigner(nAccount,_RPCServer.FWalletKeys.Key[iKey].PrivateKey) );
+              end;
+            end;
+          end;
+        end;
+      finally
+        lSigners.Free;
+      end;
+      // Return multioperation object:
+      TPascalCoinJSONComp.FillMultiOperationObject(mop,GetResultObject);
+      Result := True;
+    finally
+      senderOperationsHashTree.Free;
+    end;
+  end;
+
+  function RawOperations_Delete(Const HexaStringOperationsHashTree : TRawBytes; index : Integer) : boolean;
+  var senderOperationsHashTree : TOperationsHashTree;
+    errors : AnsiString;
+  begin
+    Result := False;
+    if Not HexaStringToOperationsHashTree(HexaStringOperationsHashTree,senderOperationsHashTree,errors) then begin
+      ErrorNum:=CT_RPC_ErrNum_InvalidData;
+      ErrorDesc:= 'Error decoding param previous operations hash tree raw value: '+errors;
+      Exit;
+    end;
+    Try
+      // Obtain mop from last OperationsHashTree operation, otherwise create a new one
+      if (index>=0) And (index<senderOperationsHashTree.OperationsCount) then begin
+        senderOperationsHashTree.Delete(index);
+      end else begin
+        ErrorNum := CT_RPC_ErrNum_InvalidData;
+        ErrorDesc:='Cannot delete index '+IntToStr(index)+' from Raw operations length '+IntToStr(senderOperationsHashTree.OperationsCount);
+        Exit;
+      end;
+      // Return objects:
+      TPascalCoinJSONComp.FillOperationsHashTreeObject(senderOperationsHashTree,GetResultObject);
+      Result := True;
+    finally
+      senderOperationsHashTree.Free;
+    end;
+  end;
+
 Var c,c2,c3 : Cardinal;
 Var c,c2,c3 : Cardinal;
   i,j,k,l : Integer;
   i,j,k,l : Integer;
   account : TAccount;
   account : TAccount;
@@ -2724,6 +3161,24 @@ begin
     Result := ChangeAccountInfo(params);
     Result := ChangeAccountInfo(params);
   end else if (method='signchangeaccountinfo') then begin
   end else if (method='signchangeaccountinfo') then begin
     Result := SignChangeAccountInfoColdWallet(params.AsString('rawoperations',''),params);
     Result := SignChangeAccountInfoColdWallet(params.AsString('rawoperations',''),params);
+  // V3 new  XXXXXXXXXXXXXXXX
+  end else if (method='signmessage') then begin
+    params.DeleteName('signature');
+    Result := DoSignOrVerifyMessage(params);
+  end else if (method='verifysign') then begin
+    if (params.IndexOfName('signature')<0) then params.GetAsVariant('signature').Value:=''; // Init signature value to force verify
+    Result := DoSignOrVerifyMessage(params);
+  // V3 Multioperation XXXXXXXXXXXXXXX
+  end else if (method='multioperationaddoperation') then begin
+    Result := MultiOperationAddOperation(params.AsString('rawoperations',''),params);
+  end else if (method='multioperationsignoffline') then begin
+    Result := MultiOperationSignCold(params.AsString('rawoperations',''),params);
+  end else if (method='multioperationsignonline') then begin
+    Result := MultiOperationSignOnline(params.AsString('rawoperations',''));
+  // XXXXXXXXXXX
+  end else if (method='operationsdelete') then begin
+    // Will remove an operation from rawoperations
+    Result := RawOperations_Delete(params.AsString('rawoperations',''),params.AsInteger('index',-1));
   //
   //
   end else if (method='operationsinfo') then begin
   end else if (method='operationsinfo') then begin
     Result := OperationsInfo(params.AsString('rawoperations',''),GetResultArray);
     Result := OperationsInfo(params.AsString('rawoperations',''),GetResultArray);

+ 1 - 1
src/core/UTxMultiOperation.pas

@@ -121,7 +121,7 @@ Type
     function OperationFee : UInt64; override;
     function OperationFee : UInt64; override;
     function OperationPayload : TRawBytes; override;
     function OperationPayload : TRawBytes; override;
     function SignerAccount : Cardinal; override;
     function SignerAccount : Cardinal; override;
-    procedure SignerAccounts(list : TList);
+    procedure SignerAccounts(list : TList); override;
     function IsSignerAccount(account : Cardinal) : Boolean; override;
     function IsSignerAccount(account : Cardinal) : Boolean; override;
     function DestinationAccount : Int64; override;
     function DestinationAccount : Int64; override;
     function SellerAccount : Int64; override;
     function SellerAccount : Int64; override;