Browse Source

Merge pull request #44 from PascalCoinDev/master

Finalize E-PASA implementation on JSON-RPC calls and document it
Albert Molina 4 years ago
parent
commit
d90c188e67

+ 10 - 0
CHANGELOG.md

@@ -13,6 +13,16 @@
   - Added "ABSTRACTMEM_USE_CACHE_ON_LISTS","ABSTRACTMEM_CACHE_MAX_ACCOUNTS","ABSTRACTMEM_CACHE_MAX_PUBKEYS" in order to customize cache values
 - Improved performance when downloading Safebox (Fresh installation)
 - JSON-RPC changes:  
+  - Updated "Operation Object" and "Multi Operation Object" return values:
+    **(IF THE WALLET IS UNLOCKED Will automatically try to decrypt encoded payloads and also return E-PASA used)**
+    - "senders" or "receivers" : ARRAY
+      - "account_epasa" : (String) If operation was using valid E-PASA format and can be decoded, will return E-PASA format used
+      - "unenc_payload" : (String) If payload can be decoded returns unencoded value in readable format (no HEXASTRING)
+      - "unenc_hexpayload" : (HEXASTRING) Unencoded value in hexastring
+      - "payload_method" : (String) Can be "key" or "pwd"
+      - "enc_pubkey" : HexaString with public key (if "payload_method"="key")
+      - "pwd" : String with password used (if "payload_method"="pwd")
+  - Payload encoding will automatically set "payload_type" value based on encoding params in order to store E-PASA standard
   - Updated "findaccounts": 
     -New param "end" (integer, -1 for default): Will search from "start" to "end" (if "end"=-1 will search to the end)
   - New method "findblocks": Will search and return an array of "Block objects"

+ 1 - 102
src/core/UAccounts.pas

@@ -26,7 +26,7 @@ interface
 
 uses
   Classes, SysUtils, UConst, UCrypto, SyncObjs, UThread, UBaseTypes,
-  UPCOrderedLists, UPCDataTypes, UPCSafeBoxRootHash, UEPasa,
+  UPCOrderedLists, UPCDataTypes, UPCSafeBoxRootHash,
   UPCHardcodedRandomHashTable, UJSONFunctions,
   {$IFDEF USE_ABSTRACTMEM}
   UPCAbstractMem, UPCAbstractMemAccountKeys,
@@ -118,8 +118,6 @@ Type
     Class procedure SaveTOperationBlockToStream(const stream : TStream; const operationBlock:TOperationBlock);
     Class Function LoadTOperationBlockFromStream(const stream : TStream; var operationBlock:TOperationBlock) : Boolean;
     Class Function AccountToTxt(const Account : TAccount) : String;
-    Class Function DecodeEPASAPartial(AAccount : Cardinal; const APayload : TBytes; APayloadType : Byte; ADefault : TEPasa) : TEPasa;
-    Class Function TryDecodeEPASAPartial(AAccount : Cardinal; const APayload : TBytes; APayloadType : Byte; out AEPasa : TEPasa) : Boolean;
   End;
 
   TPCSafeBox = Class;
@@ -1900,105 +1898,6 @@ begin
   end;
 end;
 
-class Function TAccountComp.DecodeEPASAPartial(AAccount : Cardinal; const APayload : TBytes; APayloadType : Byte; ADefault : TEPasa) : TEPasa;
-begin
-  if NOT TryDecodeEPASAPartial(AAccount, APayload, APayloadType, Result) then
-    Result := ADefault;
-end;
-
-class Function TAccountComp.TryDecodeEPASAPartial(AAccount : Cardinal; const APayload : TBytes; APayloadType : Byte; out AEPasa : TEPasa) : Boolean;
-var
-  LPayloadType : TPayloadType;
-  LUnencryptedPayloadBytes : TBytes;
-  LEPasaStr : String;
-  LPublic, LRecipientKeyEncrypted, LSenderKeyEncrypted,
-  LPasswordEncrypted, LAsciiFormatted, LHexFormatted, LBase58Formatted, LAddressedByName : Boolean;
-begin
-  LPayloadType := TEPasaComp.FromProtocolValue(APayloadType);
-  LPublic := LPayloadType.HasTrait(ptPublic);
-  LRecipientKeyEncrypted := LPayloadType.HasTrait(ptRecipientKeyEncrypted);
-  LSenderKeyEncrypted := LPayloadType.HasTrait(ptSenderKeyEncrypted);
-  LPasswordEncrypted := LPayloadType.HasTrait(ptPasswordEncrypted);
-  LAsciiFormatted := LPayloadType.HasTrait(ptAsciiFormatted);
-  LHexFormatted := LPayloadType.HasTrait(ptHexFormatted);
-  LBase58Formatted := LPayloadType.HasTrait(ptBase58Formatted);
-  LAddressedByName := LPayloadType.HasTrait(ptAddressedByName);
-
-  if LPayloadType.HasTrait(ptAddressedByName) then begin
-     (*LEPasaStr := Self.GetMempoolAccount(AAccount).name.ToPrintable;
-     if LEPasaStr.IsEmpty then
-       Exit(False); *)
-     // Note: since doing a name resolution for every encountered addressed-by-name EPASA,
-     // would overwhelm the SafeBox with lookups, names are not resolved.
-     // So in V5 addressed-by-name EPASA's auto-resolved as addressed-by-number
-     LEPasaStr := TAccountComp.AccountNumberToAccountTxtNumber(AAccount);
-  end else LEPasaStr := TAccountComp.AccountNumberToAccountTxtNumber(AAccount);
-
-
-  // payload opening char
-  if LPublic then begin
-     LEPasaStr := LEPasaStr + '[';
-  end;
-
-  if LSenderKeyEncrypted then begin
-     LEPasaStr := LEPasaStr + '<';
-  end;
-
-  if LRecipientKeyEncrypted then begin
-     LEPasaStr := LEPasaStr + '(';
-  end;
-
-  if LPasswordEncrypted then begin
-     LEPasaStr := LEPasaStr + '{';
-  end;
-
-  // payload data
-  if LPublic OR LSenderKeyEncrypted OR LRecipientKeyEncrypted OR LPasswordEncrypted then begin
-
-    if LPublic then
-      LUnencryptedPayloadBytes := APayload
-    else if LSenderKeyEncrypted then
-      Exit(False) // Todo: Partial decoding does not decrypt due to performance penalty if always decrypting. This needs to be implemented as a IFuture<TBytes> value with lazy evaluation.
-    else if LRecipientKeyEncrypted then
-      Exit(False) // Todo: Partial decoding does not decrypt due to performance penalty if always decrypting. This needs to be implemented as a IFuture<TBytes> value with lazy evaluation.
-    else if LPasswordEncrypted then
-      Exit(False) // Todo: Partial decoding does not decrypt due to performance penalty if always decrypting. This needs to be implemented as a IFuture<TBytes> value with lazy evaluation..
-    else raise Exception.Create('Internal Error a0805389-df1a-4b40-b12e-d22327a3d049');
-
-    // decrypt data
-     if LAsciiFormatted then
-       LEPasaStr := LEPasaStr + '"' + TEncoding.ASCII.GetString(LUnencryptedPayloadBytes) + '"'
-     else if LHexFormatted then
-       LEPasaStr := LEPasaStr + '0x' + THexEncoding.Encode(LUnencryptedPayloadBytes)
-     else if LBase58Formatted then
-       LEPasaStr := LEPasaStr + TPascalBase58Encoding.Encode(LUnencryptedPayloadBytes)
-     else raise Exception.Create('Internal Error 67a61d3e-eef2-40a9-8d92-45570f400c1e');
-  end;
-
-  // payload closing char
-  if LPublic then begin
-     LEPasaStr := LEPasaStr + ']';
-  end;
-
-  if LSenderKeyEncrypted then begin
-     LEPasaStr := LEPasaStr + '>';
-  end;
-
-  if LRecipientKeyEncrypted then begin
-     LEPasaStr := LEPasaStr + ')';
-  end;
-
-  if LPasswordEncrypted then begin
-     LEPasaStr := LEPasaStr + '}';
-  end;
-
-  // Parse as EPASA
-  if NOT TEPasa.TryParse(LEPasaStr, AEPasa) then
-    Exit(False);
-
-  Result := true;
-end;
-
 {$IFNDEF VER210}
 {$DEFINE DELPHIXE}
 {$ENDIF}

+ 5 - 5
src/core/UBlockChain.pas

@@ -28,7 +28,7 @@ uses
   Classes, UCrypto, UAccounts, ULog, UThread, SyncObjs, UBaseTypes, SysUtils,
   {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF},
   {$IFDEF USE_ABSTRACTMEM}UPCAbstractMem,{$ENDIF}
-  UPCDataTypes, UChunk, UEPasa;
+  UPCDataTypes, UChunk;
 
 {
 
@@ -139,7 +139,6 @@ Type
 
   TMultiOpSender = Record
     Account : Cardinal;
-    AccountEPASA : TEPasa;
     Amount : Int64;
     N_Operation : Cardinal;
     OpData : TMultiOpData; // Filled only when Operation is TOpData type
@@ -149,7 +148,6 @@ Type
   TMultiOpSenders = Array of TMultiOpSender;
   TMultiOpReceiver = Record
     Account : Cardinal;
-    AccountEPASA : TEPasa;
     Amount : Int64;
     Payload : TOperationPayload;
   end;
@@ -577,7 +575,7 @@ Type
 Const
   CT_TOperationPayload_NUL : TOperationPayload = (payload_type:0;payload_raw:Nil);
   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:Nil;y:Nil);OperationTxt:'';Amount:0;Fee:0;Balance:0;OriginalPayload:(payload_type:0;payload_raw:nil);PrintablePayload:'';OperationHash:Nil;OperationHash_OLD:Nil;errors:'';isMultiOperation:False;Senders:Nil;Receivers:Nil;changers:Nil);
-  CT_TMultiOpSender_NUL : TMultiOpSender =  (Account:0;(*AccountEPASA:TEPasa.Empty;*)Amount:0;N_Operation:0;Payload:(payload_type:0;payload_raw:Nil);Signature:(r:Nil;s:Nil));
+  CT_TMultiOpSender_NUL : TMultiOpSender =  (Account:0;Amount:0;N_Operation:0;Payload:(payload_type:0;payload_raw:Nil);Signature:(r:Nil;s:Nil));
   CT_TMultiOpReceiver_NUL : TMultiOpReceiver = (Account:0;Amount:0;Payload:(payload_type:0;payload_raw:Nil));
   CT_TMultiOpChangeInfo_NUL : TMultiOpChangeInfo = (Account:0;N_Operation:0;Changes_type:[];New_Accountkey:(EC_OpenSSL_NID:0;x:Nil;y:Nil);New_Name:Nil;New_Type:0;New_Data:Nil;Seller_Account:-1;Account_Price:-1;Locked_Until_Block:0;
     Hashed_secret:Nil;
@@ -3818,7 +3816,9 @@ begin
   l := FList.LockList;
   try
     if index<l.Count then Result := POperationResume(l[index])^
-    else Result := CT_TOperationResume_NUL;
+    else begin
+      Result := CT_TOperationResume_NUL;
+    end;
   finally
     FList.UnlockList;
   end;

+ 36 - 1
src/core/UEPasa.pas

@@ -73,6 +73,7 @@ type
   public
     function HasTrait(APayloadTrait: TPayloadTrait): Boolean; inline;
     function ToProtocolValue : byte;
+    function IsValid : Boolean;
   end;
 
   { TEPasa }
@@ -244,6 +245,33 @@ begin
   Result := APayloadTrait in Self;
 end;
 
+function TPayloadTypeHelper.IsValid: Boolean;
+var LValue, LEncryptedBits, LFormattedBits : Byte;
+begin
+  { As described on PIP-0027 E-PASA:
+    Bits 0..3 describe how payload is encrypted. 1 option (and only 1) must be selected
+    Bits 4..6 describe how is data encoded: String, Hexa or Base58. 1 option (and 1 only 1) must be selected
+
+    IsValid = 1 bit from 0..3 and 1 bit from 4..6 must be selected
+  }
+  LValue := Self.ToProtocolValue;
+  LEncryptedBits := (LValue and $0F); // 0000 1111
+  LFormattedBits := (LValue and $70); // 0111 0000
+  Result :=
+      (
+         ((LEncryptedBits and BYTE_BIT_0)=BYTE_BIT_0)
+      or ((LEncryptedBits and BYTE_BIT_1)=BYTE_BIT_1)
+      or ((LEncryptedBits and BYTE_BIT_2)=BYTE_BIT_2)
+      or ((LEncryptedBits and BYTE_BIT_3)=BYTE_BIT_3)
+      )
+      and
+      (
+         ((LFormattedBits and BYTE_BIT_4)=BYTE_BIT_4)
+      or ((LFormattedBits and BYTE_BIT_5)=BYTE_BIT_5)
+      or ((LFormattedBits and BYTE_BIT_6)=BYTE_BIT_6)
+      );
+end;
+
 function TPayloadTypeHelper.ToProtocolValue : Byte;
 begin
   Result := TEPasaComp.GetPayloadTypeProtocolByte(Self);
@@ -390,9 +418,12 @@ var
   LPayloadContent: String;
 begin
   Result := string.Empty;
+  if PayloadType.HasTrait(ptNonDeterministic) then Exit;
+
   if (PayloadType.HasTrait(ptAddressedByName)) then begin
     Result := Result + TPascal64Encoding.Escape(AccountName);
   end else begin
+    if (Not Account.HasValue) then Exit;
     Result := Result + Account.Value.ToString();
     if (AccountChecksum.HasValue) then begin
       Result := Result + String.Format('-%u', [AccountChecksum.Value]);
@@ -423,6 +454,10 @@ begin
   end;
 
   if (not AOmitExtendedChecksum) then begin
+    if (ExtendedChecksum='') then begin
+      // Need to compute:
+      ExtendedChecksum := TEPasaComp.ComputeExtendedChecksum(Result);
+    end;
     Result := Result + string.Format(':%s', [ExtendedChecksum]);
   end;
 end;
@@ -494,7 +529,7 @@ var
   LActualAccountChecksum: Byte;
 begin
   AErrorCode := EPasaErrorCode.Success;
-  AEPasa := TEPasa.Empty;
+  AEPasa.Clear;
   if (string.IsNullOrEmpty(AEPasaText)) then begin
     AErrorCode := EPasaErrorCode.BadFormat;
     Exit(False);

+ 179 - 0
src/core/UEPasaDecoder.pas

@@ -0,0 +1,179 @@
+unit UEPasaDecoder;
+
+{ Copyright (c) PascalCoin Developers - Herman Schoenfeld - Albert Molina
+
+  PIP-0027: E-PASA Reference Implementation
+  See: https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0027.md
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  This unit is a part of the PascalCoin Project, an infinitely scalable
+  cryptocurrency. Find us here:
+  Source: https://github.com/PascalCoin/PascalCoin
+
+  THIS LICENSE HEADER MUST NOT BE REMOVED.
+}
+
+{$IFDEF FPC}
+  {$MODE DELPHI}
+  {$ZEROBASEDSTRINGS OFF}
+{$ENDIF FPC}
+
+interface
+
+{$I ./../config.inc}
+
+uses
+  SysUtils,
+  TypInfo,
+  {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF},
+  UBlockChain, UNode, UBaseTypes,
+  UAccounts,
+  UEncoding,
+  UEPasa,
+  UWallet;
+
+type
+  TDecodeEPasaResult = (der_Decoded, der_Undefined, der_NonDeterministic, der_InvalidPayloadType, der_AccountNameNotFound, der_NotEnoughData, der_PrivateKeyNotFound, der_PasswordNotFound);
+
+  TEPasaDecoder = Class
+  public
+    class Function TryDecodeEPASA(AAccount : Cardinal; const APayload : TOperationPayload; const ANode : TNode; const AWalletKeys : TWalletKeys; const APasswords : TList<String>;
+      out ADecodeEPasaResult : TDecodeEPasaResult; out AEPasa : TEPasa) : Boolean; overload;
+    class Function TryDecodeEPASA(AAccount : Cardinal; const APayload : TOperationPayload; const ANode : TNode; const AWalletKeys : TWalletKeys; const APasswords : TList<String>;
+      out AEPasa : TEPasa) : Boolean; overload;
+    class Function DecodeEPASA(AAccount : Cardinal; const APayload : TOperationPayload; const ANode : TNode; const AWalletKeys : TWalletKeys; const APasswords : TList<String>) : String; overload;
+  End;
+
+implementation
+
+uses UPCEncryption, UCommon;
+
+{ TEPasaDecoder }
+
+class function TEPasaDecoder.TryDecodeEPASA(AAccount: Cardinal;
+  const APayload: TOperationPayload; const ANode : TNode; const AWalletKeys: TWalletKeys;
+  const APasswords: TList<String>; out ADecodeEPasaResult: TDecodeEPasaResult;
+  out AEPasa: TEPasa): Boolean;
+var
+  LUnencryptedPayloadBytes, LPwd : TBytes;
+  LDone : Boolean;
+  i : Integer;
+begin
+  LUnencryptedPayloadBytes := Nil;
+  AEPasa.Clear;
+  Result := False;
+  ADecodeEPasaResult := der_Decoded;
+  AEPasa.Account := AAccount;
+  AEPasa.AccountChecksum := TEPasa.CalculateAccountChecksum(AAccount);
+  AEPasa.PayloadType := TEPasaComp.FromProtocolValue(APayload.payload_type);
+  if AEPasa.PayloadType.HasTrait(ptNonDeterministic) then begin
+    ADecodeEPasaResult := der_NonDeterministic;
+    Exit(False);
+  end;
+  if Not AEPasa.PayloadType.IsValid then begin
+    ADecodeEPasaResult := der_InvalidPayloadType;
+    Exit(False);
+  end;
+
+  if AEPasa.PayloadType.HasTrait(ptAddressedByName) then begin
+    if (AEPasa.PayloadType.HasTrait(ptPublic) and
+       AEPasa.PayloadType.HasTrait(ptBase58Formatted)) then begin
+      // PayToKey candidate...
+      AEPasa.AccountName := '@';
+    end else begin
+      if Assigned(ANode) then begin
+        AEPasa.AccountName := ANode.GetMempoolAccount(AAccount).name.ToPrintable;
+      end;
+      if AEPasa.AccountName='' then begin
+        ADecodeEPasaResult := der_AccountNameNotFound; // Will continue processing
+      end;
+    end;
+  end;
+
+  // payload data
+  if (Length(APayload.payload_raw)=0) then begin
+    // Nothing to decode...
+  end else if (AEPasa.PayloadType.HasTrait(ptSenderKeyEncrypted)) or (AEPasa.PayloadType.HasTrait(ptRecipientKeyEncrypted)) then begin
+    if Assigned(AWalletKeys) then begin
+      LDone := False;
+      i := 0;
+      while (Not LDone) and (i < AWalletKeys.Count) do begin
+        if Assigned(AWalletKeys.Key[i].PrivateKey) then begin
+          if TPCEncryption.DoPascalCoinECIESDecrypt(AWalletKeys.Key[i].PrivateKey.PrivateKey,APayload.payload_raw,LUnencryptedPayloadBytes) then begin
+            LDone := True;
+          end;
+        end;
+        inc(i);
+      end;
+      if Not LDone then begin
+        ADecodeEPasaResult := der_PrivateKeyNotFound;
+        Exit(False);
+      end;
+    end else begin
+      ADecodeEPasaResult := der_NotEnoughData;
+      Exit(False);
+    end;
+  end else if (AEPasa.PayloadType.HasTrait(ptPasswordEncrypted)) then begin
+    if Assigned(APasswords) then begin
+      LDone := False;
+      i := 0;
+      while (Not LDone) and (i < APasswords.Count) do begin
+        LPwd.FromString(APasswords[i]);
+        if TPCEncryption.DoPascalCoinAESDecrypt(APayload.payload_raw,LPwd,LUnencryptedPayloadBytes) then begin
+          AEPasa.Password := APasswords[i];
+          LDone := True;
+        end;
+        inc(i);
+      end;
+      if Not LDone then begin
+        ADecodeEPasaResult := der_PasswordNotFound;
+        Exit(False);
+      end;
+    end else begin
+      ADecodeEPasaResult := der_NotEnoughData;
+      Exit(False);
+    end;
+  end else begin
+    if (Not AEPasa.PayloadType.HasTrait(ptPublic)) then begin
+      // Internal Error
+      ADecodeEPasaResult := der_Undefined;
+      Exit(False);
+    end;
+    LUnencryptedPayloadBytes := APayload.payload_raw;
+  end;
+  // LUnencryptedPayloadBytes Has Value in RAW
+  if (AEPasa.PayloadType.HasTrait(ptAsciiFormatted)) then begin
+    AEPasa.Payload := LUnencryptedPayloadBytes.ToString;
+  end else if (AEPasa.PayloadType.HasTrait(ptHexFormatted)) then begin
+    AEPasa.Payload := THexEncoding.Encode(LUnencryptedPayloadBytes,True);
+  end else if (AEPasa.PayloadType.HasTrait(ptBase58Formatted)) then begin
+    AEPasa.Payload := TPascalBase58Encoding.Encode(LUnencryptedPayloadBytes);
+  end else begin
+    // Internal error
+    ADecodeEPasaResult := der_Undefined;
+    Exit(False);
+  end;
+  Result := true;
+end;
+
+class function TEPasaDecoder.DecodeEPASA(AAccount: Cardinal;
+  const APayload: TOperationPayload; const ANode: TNode;
+  const AWalletKeys: TWalletKeys; const APasswords: TList<String>): String;
+var LEPasa : TEPasa;
+begin
+  if TryDecodeEPASA(AAccount,APayload,ANode,AWalletKeys,APasswords,LEPasa) then begin
+    Result := LEPasa.ToClassicPASAString;
+  end else Result := '';
+end;
+
+class function TEPasaDecoder.TryDecodeEPASA(AAccount: Cardinal;
+  const APayload: TOperationPayload; const ANode : TNode; const AWalletKeys: TWalletKeys;
+  const APasswords: TList<String>; out AEPasa: TEPasa): Boolean;
+var LDecodeEPasaResult: TDecodeEPasaResult;
+begin
+  Result := TryDecodeEPASA(AAccount,APayload,ANode,AWalletKeys,APasswords,LDecodeEPasaResult,AEPasa);
+end;
+
+end.

+ 0 - 2
src/core/UOpTransaction.pas

@@ -1189,7 +1189,6 @@ begin
   SetLength(OperationResume.Senders,1);
   OperationResume.Senders[0] := CT_TMultiOpSender_NUL;
   OperationResume.Senders[0].Account:=FData.sender;
-  OperationResume.Senders[0].AccountEPASA := TAccountComp.DecodeEPASAPartial(FData.sender, FData.payload.payload_raw, FData.payload.payload_type, TEPasa.Empty);
   OperationResume.Senders[0].Amount:=Int64(FData.amount + FData.fee);
   OperationResume.Senders[0].N_Operation:=FData.n_operation;
   OperationResume.Senders[0].Payload:=FData.payload;
@@ -1199,7 +1198,6 @@ begin
       SetLength(OperationResume.Receivers,1);
       OperationResume.Receivers[0] := CT_TMultiOpReceiver_NUL;
       OperationResume.Receivers[0].Account:=FData.target;
-      OperationResume.Receivers[0].AccountEPASA:=TAccountComp.DecodeEPASAPartial(FData.target, FData.payload.payload_raw, FData.payload.payload_type, TEPasa.Empty);
       OperationResume.Receivers[0].Amount:=FData.amount;
       OperationResume.Receivers[0].Payload:=FData.payload;
     end;

+ 6 - 2
src/core/UPCRPCOpData.pas

@@ -297,7 +297,9 @@ begin
     LResultArray := AJSONResponse.GetAsArray('result');
 
     for i := 0 to LOperationsResumeList.Count-1 do begin
-      TPascalCoinJSONComp.FillOperationObject(LOperationsResumeList.OperationResume[i],ASender.Node.Bank.BlocksCount,LResultArray.GetAsObject( LResultArray.Count ));
+      TPascalCoinJSONComp.FillOperationObject(LOperationsResumeList.OperationResume[i],ASender.Node.Bank.BlocksCount,
+        ASender.Node,ASender.RPCServer.WalletKeys,ASender.RPCServer.PayloadPasswords,
+        LResultArray.GetAsObject( LResultArray.Count ));
     end;
     Result := True;
   finally
@@ -362,7 +364,9 @@ begin
       Exit;
     end;
     TPCOperation.OperationToOperationResume(0,LOpData,False,LSender.account,LOPR);
-    TPascalCoinJSONComp.FillOperationObject(LOPR,ASender.Node.Bank.BlocksCount,AJSONResponse.GetAsObject('result'));
+    TPascalCoinJSONComp.FillOperationObject(LOPR,ASender.Node.Bank.BlocksCount,
+      ASender.Node,ASender.RPCServer.WalletKeys,ASender.RPCServer.PayloadPasswords,
+      AJSONResponse.GetAsObject('result'));
     Result := True;
   finally
     LOpData.free;

+ 3 - 1
src/core/UPCRPCSend.pas

@@ -188,7 +188,9 @@ begin
         Exit;
       end;
       TPCOperation.OperationToOperationResume(0,LOpt,False,LSender.account,LOpr);
-      TPascalCoinJSONComp.FillOperationObject(LOpr,ASender.Node.Bank.BlocksCount,AJSONResponse.GetAsObject('result'));
+      TPascalCoinJSONComp.FillOperationObject(LOpr,ASender.Node.Bank.BlocksCount,
+        ASender.Node,ASender.RPCServer.WalletKeys,ASender.RPCServer.PayloadPasswords,
+        AJSONResponse.GetAsObject('result'));
       Result := true;
     finally
       LOpt.free;

+ 132 - 64
src/core/URPC.pas

@@ -67,10 +67,11 @@ Type
   public
     class procedure FillAccountObject(Const account : TAccount; jsonObj : 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; const ANode : TNode; const AWalletKeys : TWalletKeys; const APasswords : TList<String>; jsonObject : TPCJSONObject); overload;
     class procedure FillOperationsHashTreeObject(Const OperationsHashTree : TOperationsHashTree; jsonObject : TPCJSONObject);
-    class procedure FillMultiOperationObject(current_protocol : Word; Const multiOperation : TOpMultiOperation; jsonObject : TPCJSONObject);
+    class procedure FillMultiOperationObject(current_protocol : Word; Const multiOperation : TOpMultiOperation; const ANode : TNode; const AWalletKeys : TWalletKeys; const APasswords : TList<String>; jsonObject : TPCJSONObject);
     class procedure FillPublicKeyObject(const PubKey : TAccountKey; jsonObj : TPCJSONObject);
+    class function FillEPasaOrDecrypt(const AAccount : Int64; Const APayload : TOperationPayload; const ANode : TNode; const AWalletKeys : TWalletKeys; const APasswords : TList<String>; jsonObject : TPCJSONObject) : Boolean;
     class function ToPascalCoins(jsonCurr : Real) : Int64;
     //
     class Function HexaStringToOperationsHashTree(Const AHexaStringOperationsHashTree : String; ACurrentProtocol : Word; out AOperationsHashTree : TOperationsHashTree; var AErrors : String) : Boolean;
@@ -97,6 +98,7 @@ Type
     FValidIPs: String;
     FAllowUsePrivateKeys: Boolean;
     FNode : TNode;
+    FPayloadPasswords: TList<String>;
     procedure SetActive(AValue: Boolean);
     procedure SetIniFileName(const Value: String);
     procedure SetLogFileName(const Value: String);
@@ -111,6 +113,7 @@ Type
     Property Port : Word read FPort Write FPort;
     Property Active : Boolean read FActive write SetActive;
     Property WalletKeys : TWalletKeysExt read FWalletKeys write FWalletKeys;
+    Property PayloadPasswords: TList<String> read FPayloadPasswords;
     //
     Property JSON20Strict : Boolean read FJSON20Strict write FJSON20Strict;
     Property IniFileName : String read FIniFileName write SetIniFileName;
@@ -163,6 +166,7 @@ implementation
 Uses
   {$IFNDEF FPC}windows,{$ENDIF}
   SysUtils, Synautil,
+  UEPasaDecoder,
   UPCRPCSend,
   UPCRPCOpData, UPCRPCFindAccounts, UPCRPCFindBlocks, UPCRPCFileUtils;
 
@@ -214,12 +218,60 @@ begin
   end;
 end;
 
-class procedure TPascalCoinJSONComp.FillOperationObject(const OPR: TOperationResume; currentNodeBlocksCount : Cardinal; jsonObject: TPCJSONObject);
+class function TPascalCoinJSONComp.FillEPasaOrDecrypt(const AAccount: Int64;
+  const APayload: TOperationPayload; const ANode: TNode;
+  const AWalletKeys: TWalletKeys; const APasswords: TList<String>;
+  jsonObject: TPCJSONObject) : Boolean;
+var LEPasa : TEPasa;
+  i : Integer;
+  pkey : TECPrivateKey;
+  decrypted_payload : TRawBytes;
+  LDecodeEPasaResult : TDecodeEPasaResult;
+begin
+  Result := False;
+  if AAccount>=0 then begin
+    if TEPasaDecoder.TryDecodeEPASA(AAccount,APayload,ANode,AWalletKeys,APasswords,LDecodeEPasaResult,LEPasa) then begin
+      jsonObject.GetAsVariant('account_epasa').Value := LEPasa.ToClassicPASAString;
+      jsonObject.GetAsVariant('unenc_payload').Value := LEPasa.Payload;
+      jsonObject.GetAsVariant('unenc_hexpayload').Value := LEPasa.GetRawPayloadBytes.ToHexaString;
+      Result := True;
+    end;
+  end;
+  if (Not Result) and (Assigned(AWalletKeys)) and (LDecodeEPasaResult<>der_PrivateKeyNotFound) then begin
+    for i := 0 to AWalletKeys.Count - 1 do begin
+      pkey := AWalletKeys.Key[i].PrivateKey;
+      if (assigned(pkey)) then begin
+        if TPCEncryption.DoPascalCoinECIESDecrypt(pkey.PrivateKey,APayload.payload_raw,decrypted_payload) then begin
+          jsonObject.GetAsVariant('unenc_payload').Value:= decrypted_payload.ToPrintable;
+          jsonObject.GetAsVariant('unenc_hexpayload').Value:= TCrypto.ToHexaString(decrypted_payload);
+          jsonObject.GetAsVariant('payload_method').Value:= 'key';
+          jsonObject.GetAsVariant('enc_pubkey').Value:= TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(pkey.PublicKey));
+          Result := true;
+        end;
+      end;
+    end;
+  end;
+  if (Not Result) And Assigned(APasswords) and (LDecodeEPasaResult<>der_PasswordNotFound) then begin
+    for i := 0 to APasswords.Count - 1 do begin
+      if TPCEncryption.DoPascalCoinAESDecrypt(APayload.payload_raw,TEncoding.ANSI.GetBytes(APasswords[i]),decrypted_payload) then begin
+        jsonObject.GetAsVariant('unenc_payload').Value:= decrypted_payload.ToPrintable;
+        jsonObject.GetAsVariant('unenc_hexpayload').Value:= TCrypto.ToHexaString(decrypted_payload);
+        jsonObject.GetAsVariant('payload_method').Value:= 'pwd';
+        jsonObject.GetAsVariant('pwd').Value:=APasswords[i];
+        Result := true;
+      end;
+    end;
+  end;
+end;
+
+class procedure TPascalCoinJSONComp.FillOperationObject(const OPR: TOperationResume; currentNodeBlocksCount : Cardinal;
+  const ANode : TNode; const AWalletKeys : TWalletKeys; const APasswords : TList<String>; jsonObject: TPCJSONObject);
 Var i : Integer;
   LOpChangeAccountInfoType : TOpChangeAccountInfoType;
   LString : String;
   jsonArr : TPCJSONArray;
   auxObj : TPCJSONObject;
+  LEPasa : TEPasa;
 
   procedure FillOpDataObject(AParentObj : TPCJSONObject; const AOpData : TMultiOpData);
   var LDataObj : TPCJSONObject;
@@ -258,7 +310,7 @@ Begin
       LString := TCrypto.ToHexaString(OPR.Senders[i].Payload.payload_raw);
       auxObj := jsonArr.GetAsObject(jsonArr.Count);
       auxObj.GetAsVariant('account').Value := OPR.Senders[i].Account;
-      auxObj.GetAsVariant('account_epasa').Value := OPR.Senders[i].AccountEPASA.ToString();
+      FillEPasaOrDecrypt(OPR.Senders[i].Account,OPR.Senders[i].Payload,ANode,AWalletKeys,APasswords,auxObj);
       if (OPR.Senders[i].N_Operation>0) then auxObj.GetAsVariant('n_operation').Value := OPR.Senders[i].N_Operation;
       auxObj.GetAsVariant('amount').Value := TAccountComp.FormatMoneyDecimal(OPR.Senders[i].Amount * (-1));
       auxObj.GetAsVariant('amount_s').Value := TAccountComp.FormatMoney (OPR.Senders[i].Amount * (-1));
@@ -273,6 +325,7 @@ Begin
     for i:=Low(OPR.Receivers) to High(OPR.Receivers) do begin
       auxObj := jsonArr.GetAsObject(jsonArr.Count);
       auxObj.GetAsVariant('account').Value := OPR.Receivers[i].Account;
+      FillEPasaOrDecrypt(OPR.Receivers[i].Account,OPR.Receivers[i].Payload,ANode,AWalletKeys,APasswords,auxObj);
       auxObj.GetAsVariant('amount').Value :=  TAccountComp.FormatMoneyDecimal(OPR.Receivers[i].Amount);
       auxObj.GetAsVariant('amount_s').Value :=  TAccountComp.FormatMoney(OPR.Receivers[i].Amount);
       auxObj.GetAsVariant('payload').Value := TCrypto.ToHexaString(OPR.Receivers[i].Payload.payload_raw);
@@ -424,18 +477,18 @@ begin
      Exit(False);
 
    if AEPASA.PayloadType.HasTrait(ptPublic) then begin
-     AInputParams.SetAs('payload_method', TPCJSONVariantValue.CreateFromVariant('none'));
-     AInputParams.SetAs('payload', TPCJSONVariantValue.CreateFromVariant(AEPASA.GetRawPayloadBytes.ToHexaString));
+     AInputParams.GetAsVariant('payload_method').Value := 'none';
+     AInputParams.GetAsVariant('payload').Value := AEPASA.GetRawPayloadBytes.ToHexaString;
    end else if AEPASA.PayloadType.HasTrait(ptSenderKeyEncrypted) then begin
-     AInputParams.SetAs('payload_method', TPCJSONVariantValue.CreateFromVariant('sender'));
-     AInputParams.SetAs('payload', TPCJSONVariantValue.CreateFromVariant(AEPASA.GetRawPayloadBytes().ToHexaString()));
+     AInputParams.GetAsVariant('payload_method').Value := 'sender';
+     AInputParams.GetAsVariant('payload').Value := AEPASA.GetRawPayloadBytes.ToHexaString;
    end else if AEPASA.PayloadType.HasTrait(ptRecipientKeyEncrypted) then begin
-     AInputParams.SetAs('payload_method', TPCJSONVariantValue.CreateFromVariant('dest'));
-     AInputParams.SetAs('payload', TPCJSONVariantValue.CreateFromVariant(AEPASA.GetRawPayloadBytes().ToHexaString()));
+     AInputParams.GetAsVariant('payload_method').Value := 'dest';
+     AInputParams.GetAsVariant('payload').Value := AEPASA.GetRawPayloadBytes.ToHexaString;
    end else if AEPASA.PayloadType.HasTrait(ptPasswordEncrypted) then begin
-     AInputParams.SetAs('payload_method', TPCJSONVariantValue.CreateFromVariant('aes'));
-     AInputParams.SetAs('payload', TPCJSONVariantValue.CreateFromVariant(AEPASA.GetRawPayloadBytes().ToHexaString()));
-     AInputParams.SetAs('pwd', TPCJSONVariantValue.CreateFromVariant(AEPASA.Password));
+     AInputParams.GetAsVariant('payload_method').Value := 'aes';
+     AInputParams.GetAsVariant('payload').Value := AEPASA.GetRawPayloadBytes.ToHexaString;
+     AInputParams.GetAsVariant('pwd').Value := AEPASA.Password;
      Result := True;
    end;
    Result := True;
@@ -484,20 +537,39 @@ class function TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(
   out AOperationPayload : TOperationPayload;
   var AErrorNum: Integer;
   var AErrorDesc: String): Boolean;
+var LNewPayloadType : TPayloadType;
 begin
   AOperationPayload := CT_TOperationPayload_NUL;
   AOperationPayload.payload_type := APayloadType.ToProtocolValue;
   if (Length(ARawPayload)>0) then begin
+    if ARawPayload.ToPrintable.CompareTo(ARawPayload.ToString)=0 then LNewPayloadType := [ptAsciiFormatted]
+    else LNewPayloadType := [];
     if (APayload_method='none') then begin
       AOperationPayload.payload_raw:=ARawPayload;
+      if (AOperationPayload.payload_type=0) then begin
+        LNewPayloadType := LNewPayloadType + [ptPublic];
+        AOperationPayload.payload_type := LNewPayloadType.ToProtocolValue;
+      end;
       Result := True;
     end else if (APayload_method='dest') then begin
       Result := TPCEncryption.DoPascalCoinECIESEncrypt(ATargetAccountKey,ARawPayload,AOperationPayload.payload_raw);
+      if (AOperationPayload.payload_type=0) then begin
+        LNewPayloadType := LNewPayloadType + [ptRecipientKeyEncrypted];
+        AOperationPayload.payload_type := LNewPayloadType.ToProtocolValue;
+      end;
     end else if (APayload_method='sender') then begin
       Result := TPCEncryption.DoPascalCoinECIESEncrypt(ASenderAccounKey,ARawPayload,AOperationPayload.payload_raw);
+      if (AOperationPayload.payload_type=0) then begin
+        LNewPayloadType := LNewPayloadType + [ptSenderKeyEncrypted];
+        AOperationPayload.payload_type := LNewPayloadType.ToProtocolValue;
+      end;
     end else if (APayload_method='aes') then begin
       AOperationPayload.payload_raw := TPCEncryption.DoPascalCoinAESEncrypt(ARawPayload,TEncoding.ANSI.GetBytes(AEncodePwdForAES));
       Result := True;
+      if (AOperationPayload.payload_type=0) then begin
+        LNewPayloadType := LNewPayloadType + [ptPasswordEncrypted];
+        AOperationPayload.payload_type := LNewPayloadType.ToProtocolValue;
+      end;
     end else begin
       Result := False;
       AErrorNum:=CT_RPC_ErrNum_InvalidOperation;
@@ -564,7 +636,8 @@ begin
   jsonObject.GetAsVariant('rawoperations').Value:=OperationsHashTreeToHexaString(OperationsHashTree);
 end;
 
-class procedure TPascalCoinJSONComp.FillMultiOperationObject(current_protocol : Word; const multiOperation: TOpMultiOperation; jsonObject: TPCJSONObject);
+class procedure TPascalCoinJSONComp.FillMultiOperationObject(current_protocol : Word; const multiOperation: TOpMultiOperation;
+  const ANode : TNode; const AWalletKeys : TWalletKeys; const APasswords : TList<String>; jsonObject: TPCJSONObject);
 Var i, nSigned, nNotSigned : Integer;
   opht : TOperationsHashTree;
   jsonArr : TPCJSONArray;
@@ -593,7 +666,7 @@ begin
     LStr := TCrypto.ToHexaString(multiOperation.Data.txSenders[i].Payload.payload_raw);
     auxObj := jsonArr.GetAsObject(jsonArr.Count);
     auxObj.GetAsVariant('account').Value := multiOperation.Data.txSenders[i].Account;
-    auxObj.GetAsVariant('account_epasa').Value := multiOperation.Data.txSenders[i].AccountEPASA.ToString();
+    FillEPasaOrDecrypt(multiOperation.Data.txSenders[i].Account,multiOperation.Data.txSenders[i].Payload,ANode,AWalletKeys,APasswords,auxObj);
     auxObj.GetAsVariant('n_operation').Value := multiOperation.Data.txSenders[i].N_Operation;
     auxObj.GetAsVariant('amount').Value := TAccountComp.FormatMoneyDecimal(multiOperation.Data.txSenders[i].Amount * (-1));
     auxObj.GetAsVariant('payload').Value := LStr;
@@ -605,7 +678,7 @@ begin
     LStr := TCrypto.ToHexaString(multiOperation.Data.txSenders[i].Payload.payload_raw);
     auxObj := jsonArr.GetAsObject(jsonArr.Count);
     auxObj.GetAsVariant('account').Value := multiOperation.Data.txReceivers[i].Account;
-    auxObj.GetAsVariant('account_epasa').Value := multiOperation.Data.txReceivers[i].AccountEPASA.ToString();
+    FillEPasaOrDecrypt(multiOperation.Data.txReceivers[i].Account,multiOperation.Data.txReceivers[i].Payload,ANode,AWalletKeys,APasswords,auxObj);
     auxObj.GetAsVariant('amount').Value := TAccountComp.FormatMoneyDecimal(multiOperation.Data.txReceivers[i].Amount);
     auxObj.GetAsVariant('payload').Value := TCrypto.ToHexaString(multiOperation.Data.txReceivers[i].Payload.payload_raw);
     auxObj.GetAsVariant('payload_type').Value := multiOperation.Data.txReceivers[i].Payload.payload_type;
@@ -840,6 +913,7 @@ end;
 
 constructor TRPCServer.Create;
 begin
+  FPayloadPasswords := TList<String>.Create;
   FActive := false;
   FRPCLog := Nil;
   FIniFile := Nil;
@@ -858,6 +932,7 @@ end;
 destructor TRPCServer.Destroy;
 begin
   FreeAndNil(FRPCLog);
+  FreeAndNil(FPayloadPasswords);
   active := false;
   if _RPCServer=Self then _RPCServer:=Nil;
   inherited Destroy;
@@ -1132,7 +1207,9 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
 
   Procedure FillOperationResumeToJSONObject(Const OPR : TOperationResume; jsonObject : TPCJSONObject);
   Begin
-    TPascalCoinJSONComp.FillOperationObject(OPR,FNode.Bank.BlocksCount,jsonObject);
+    TPascalCoinJSONComp.FillOperationObject(OPR,FNode.Bank.BlocksCount,
+      Node,RPCServer.WalletKeys,RPCServer.PayloadPasswords,
+      jsonObject);
   end;
 
   Function GetAccountOperations(accountNumber : Cardinal; jsonArray : TPCJSONArray; maxBlocksDepth, startReg, maxReg: Integer; forceStartBlock : Cardinal) : Boolean;
@@ -1611,7 +1688,9 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
         If TPCOperation.OperationToOperationResume(0,Op,True,Op.SignerAccount,OPR) then begin
           OPR.NOpInsideBlock := i;
           OPR.Balance := -1;
-        end else OPR := CT_TOperationResume_NUL;
+        end else begin
+          OPR := CT_TOperationResume_NUL;
+        end;
         FillOperationResumeToJSONObject(OPR,Obj);
       end;
       Result := true;
@@ -1675,48 +1754,44 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     Result := true;
   end;
 
-  Function DoDecrypt(RawEncryptedPayload : TRawBytes; jsonArrayPwds : TPCJSONArray) : Boolean;
+  Function DoDecrypt(jsonArrayPwds : TPCJSONArray) : Boolean;
   var i : Integer;
     pkey : TECPrivateKey;
     decrypted_payload : TRawBytes;
+    LPayload : TOperationPayload;
+    s : String;
+    Lpasswords : TList<String>;
   Begin
-    Result := false;
-    if Length(RawEncryptedPayload)=0 then begin
-      GetResultObject.GetAsVariant('result').Value:= False;
-      GetResultObject.GetAsVariant('enc_payload').Value:= '';
-      Result := true;
-      exit;
+    if Length(params.AsString('payload',''))=0 then begin
+      ErrorNum:= CT_RPC_ErrNum_InvalidData;
+      ErrorDesc := 'Need param "payload"';
+      Exit(False);
     end;
-    for i := 0 to _RPCServer.WalletKeys.Count - 1 do begin
-      pkey := _RPCServer.WalletKeys.Key[i].PrivateKey;
-      if (assigned(pkey)) then begin
-        if TPCEncryption.DoPascalCoinECIESDecrypt(pkey.PrivateKey,RawEncryptedPayload,decrypted_payload) then begin
-          GetResultObject.GetAsVariant('result').Value:= true;
-          GetResultObject.GetAsVariant('enc_payload').Value:= TCrypto.ToHexaString(RawEncryptedPayload);
-          GetResultObject.GetAsVariant('unenc_payload').Value:= decrypted_payload.ToPrintable;
-          GetResultObject.GetAsVariant('unenc_hexpayload').Value:= TCrypto.ToHexaString(decrypted_payload);
-          GetResultObject.GetAsVariant('payload_method').Value:= 'key';
-          GetResultObject.GetAsVariant('enc_pubkey').Value:= TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(pkey.PublicKey));
-          Result := true;
-          Exit;
-        end;
-      end;
+    LPayload.payload_raw := TCrypto.HexaToRaw(params.AsString('payload',''));
+    LPayload.payload_type := params.AsInteger('payload_type',0);
+    if Length(LPayload.payload_raw)=0 then begin
+      ErrorNum:= CT_RPC_ErrNum_InvalidData;
+      ErrorDesc := '"payload" param is not an HEXASTRING';
+      Exit(False);
     end;
-    for i := 0 to jsonArrayPwds.Count - 1 do begin
-      if TPCEncryption.DoPascalCoinAESDecrypt(RawEncryptedPayload,TEncoding.ANSI.GetBytes(jsonArrayPwds.GetAsVariant(i).AsString('')),decrypted_payload) then begin
-        GetResultObject.GetAsVariant('result').Value:= true;
-        GetResultObject.GetAsVariant('enc_payload').Value:= TCrypto.ToHexaString(RawEncryptedPayload);
-        GetResultObject.GetAsVariant('unenc_payload').Value:= decrypted_payload.ToPrintable;
-        GetResultObject.GetAsVariant('unenc_hexpayload').Value:= TCrypto.ToHexaString(decrypted_payload);
-        GetResultObject.GetAsVariant('payload_method').Value:= 'pwd';
-        GetResultObject.GetAsVariant('pwd').Value:= jsonArrayPwds.GetAsVariant(i).AsString('');
-        Result := true;
-        exit;
+    Lpasswords := TList<String>.Create;
+    Try
+      for i := 0 to jsonArrayPwds.Count-1 do begin
+        s := jsonArrayPwds.GetAsVariant(i).AsString('');
+        if Lpasswords.IndexOf(s)<0 then Lpasswords.Add(s);
       end;
-    end;
-    // Not found
-    GetResultObject.GetAsVariant('result').Value:= False;
-    GetResultObject.GetAsVariant('enc_payload').Value:= TCrypto.ToHexaString(RawEncryptedPayload);
+
+      if TPascalCoinJSONComp.FillEPasaOrDecrypt(-1,LPayload,FNode,FRPCServer.WalletKeys,Lpasswords,GetResultObject) then begin
+        GetResultObject.GetAsVariant('result').Value:= True;
+      end else begin
+        GetResultObject.GetAsVariant('result').Value:= False;
+      end;
+      GetResultObject.GetAsVariant('enc_payload').Value:= TCrypto.ToHexaString(LPayload.payload_raw);
+
+    Finally
+      Lpasswords.Free;
+    End;
+
     Result := true;
   End;
 
@@ -2610,7 +2685,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
         end;
       end;
       // Return multioperation object:
-      TPascalCoinJSONComp.FillMultiOperationObject(FNode.Bank.SafeBox.CurrentProtocol,mop,GetResultObject);
+      TPascalCoinJSONComp.FillMultiOperationObject(FNode.Bank.SafeBox.CurrentProtocol,mop,FNode,FRPCServer.WalletKeys,FRPCServer.PayloadPasswords, GetResultObject);
     finally
       OperationsHashTree.Free;
     end;
@@ -2747,7 +2822,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     Try
       InternalMultiOperationSignCold(mop,protocol,params.GetAsArray('accounts_and_keys'),j);
       // Return multioperation object:
-      TPascalCoinJSONComp.FillMultiOperationObject(protocol,mop,GetResultObject);
+      TPascalCoinJSONComp.FillMultiOperationObject(protocol,mop,FNode,FRPCServer.WalletKeys,FRPCServer.PayloadPasswords,GetResultObject);
       Result := True;
     finally
       senderOperationsHashTree.Free;
@@ -2798,7 +2873,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
         lSigners.Free;
       end;
       // Return multioperation object:
-      TPascalCoinJSONComp.FillMultiOperationObject(FNode.Bank.SafeBox.CurrentProtocol,mop,GetResultObject);
+      TPascalCoinJSONComp.FillMultiOperationObject(FNode.Bank.SafeBox.CurrentProtocol,mop,FNode,FRPCServer.WalletKeys,FRPCServer.PayloadPasswords,GetResultObject);
       Result := True;
     finally
       senderOperationsHashTree.Free;
@@ -3641,19 +3716,12 @@ begin
       ErrorNum := CT_RPC_ErrNum_NotAllowedCall;
       Exit;
     end;
-    // Decrypts a "payload" searching for wallet private keys and for array of strings in "pwds" param
-    // Returns an JSON Object with "result" (Boolean) and
-    if (params.AsString('payload','')='') then begin
-      ErrorNum:= CT_RPC_ErrNum_InvalidData;
-      ErrorDesc := 'Need param "payload"';
-      exit;
-    end;
     If Not _RPCServer.WalletKeys.IsValidPassword then begin
       ErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
       ErrorDesc := 'Wallet is password protected. Unlock first';
       exit;
     end;
-    Result := DoDecrypt(TCrypto.HexaToRaw(params.AsString('payload','')),params.GetAsArray('pwds'));
+    Result := DoDecrypt(params.GetAsArray('pwds'));
   end else if (method='getconnections') then begin
     if (Not _RPCServer.AllowUsePrivateKeys) then begin
       // Protection when server is locked to avoid private keys call

+ 1 - 1
src/core/UTxMultiOperation.pas

@@ -23,7 +23,7 @@ unit UTxMultiOperation;
 interface
 
 uses
-  Classes, SysUtils, UCrypto, UBlockChain, UAccounts, UBaseTypes,
+  Classes, SysUtils, UCrypto, UBlockChain, UAccounts, UBaseTypes, UEPasa,
   {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF},
   UPCDataTypes;
 

+ 32 - 15
src/gui-classic/UFRMOperation.dfm

@@ -90,7 +90,7 @@ object FRMOperation: TFRMOperation
   object bbCancel: TBitBtn
     Left = 461
     Top = 476
-    Width = 116
+    Width = 120
     Height = 31
     Kind = bkCancel
     NumGlyphs = 2
@@ -185,7 +185,7 @@ object FRMOperation: TFRMOperation
         object lblEncryptionErrors: TLabel
           Left = 255
           Top = 96
-          Width = 187
+          Width = 152
           Height = 33
           AutoSize = False
           Caption = 'Errors detected'
@@ -211,7 +211,7 @@ object FRMOperation: TFRMOperation
         object rbEncryptedWithEC: TRadioButton
           Left = 15
           Top = 35
-          Width = 207
+          Width = 234
           Height = 19
           Caption = 'Encrypted with dest account public key'
           Checked = True
@@ -222,7 +222,7 @@ object FRMOperation: TFRMOperation
         object rbEncrptedWithPassword: TRadioButton
           Left = 15
           Top = 53
-          Width = 141
+          Width = 234
           Height = 19
           Caption = 'Encrypted with password'
           TabOrder = 2
@@ -232,7 +232,7 @@ object FRMOperation: TFRMOperation
         object rbNotEncrypted: TRadioButton
           Left = 15
           Top = 93
-          Width = 162
+          Width = 234
           Height = 19
           Caption = 'Dont encrypt (Public payload)'
           TabOrder = 4
@@ -266,21 +266,25 @@ object FRMOperation: TFRMOperation
         end
         object rbEncryptedWithOldEC: TRadioButton
           Left = 15
-          Top = 16
-          Width = 159
+          Top = 12
+          Width = 234
           Height = 19
           Caption = 'Encrypted with old public key'
           TabOrder = 0
           TabStop = True
           OnClick = memoPayloadClick
         end
-        object cbPayloadAsHex: TCheckBox
-          Left = 448
+        object cbPayloadDataInputType: TComboBox
+          Left = 413
           Top = 96
-          Width = 97
-          Height = 17
-          Caption = 'As Hex'
+          Width = 89
+          Height = 21
+          Style = csDropDownList
           TabOrder = 6
+          Items.Strings = (
+            'As String'#11
+            'As Hexadecimal'#11
+            'As Base58')
         end
       end
       object ebFee: TEdit
@@ -373,20 +377,33 @@ object FRMOperation: TFRMOperation
             OnClick = sbSearchDestinationAccountClick
           end
           object ebDestAccount: TEdit
-            Left = 114
+            Left = 115
             Top = 29
-            Width = 370
+            Width = 369
             Height = 21
             Anchors = [akLeft, akTop, akRight]
             TabOrder = 0
           end
           object ebAmount: TEdit
             Left = 115
-            Top = 58
+            Top = 56
             Width = 87
             Height = 21
             TabOrder = 1
           end
+          object memoEPASA: TMemo
+            Left = 13
+            Top = 96
+            Width = 492
+            Height = 65
+            TabStop = False
+            BorderStyle = bsNone
+            ReadOnly = True
+            TabOrder = 2
+            WantReturns = False
+            OnChange = memoPayloadClick
+            OnClick = memoPayloadClick
+          end
         end
         object tsChangePrivateKey: TTabSheet
           Caption = 'Change Key'

+ 39 - 16
src/gui-classic/UFRMOperation.lfm

@@ -15,7 +15,7 @@ object FRMOperation: TFRMOperation
   OnCreate = FormCreate
   OnDestroy = FormDestroy
   Position = poOwnerFormCenter
-  LCLVersion = '2.0.2.0'
+  LCLVersion = '2.0.10.0'
   object lblAccountCaption: TLabel
     Left = 25
     Height = 13
@@ -142,7 +142,7 @@ object FRMOperation: TFRMOperation
           Left = 255
           Height = 33
           Top = 87
-          Width = 182
+          Width = 153
           AutoSize = False
           Caption = 'Errors detected'
           Font.Color = clRed
@@ -223,12 +223,18 @@ object FRMOperation: TFRMOperation
           OnClick = memoPayloadClick
           TabOrder = 0
         end
-        object cbPayloadAsHex: TCheckBox
-          Left = 444
-          Height = 19
-          Top = 92
-          Width = 54
-          Caption = 'As Hex'
+        object cbPayloadDataInputType: TComboBox
+          Left = 412
+          Height = 21
+          Top = 88
+          Width = 89
+          ItemHeight = 13
+          Items.Strings = (
+            'As String'#11
+            'As Hexadecimal'#11
+            'As Base58'
+          )
+          Style = csDropDownList
           TabOrder = 6
         end
       end
@@ -244,8 +250,8 @@ object FRMOperation: TFRMOperation
         Height = 167
         Top = 11
         Width = 521
-        ActivePage = tsListAccount
-        TabIndex = 2
+        ActivePage = tsTransaction
+        TabIndex = 0
         TabOrder = 0
         OnChange = PageControlOpTypeChange
         object tsTransaction: TTabSheet
@@ -285,7 +291,7 @@ object FRMOperation: TFRMOperation
             Left = 115
             Height = 21
             Top = 29
-            Width = 85
+            Width = 357
             TabOrder = 0
           end
           object ebAmount: TEdit
@@ -296,9 +302,9 @@ object FRMOperation: TFRMOperation
             TabOrder = 1
           end
           object sbSearchDestinationAccount: TSpeedButton
-            Left = 208
+            Left = 480
             Height = 22
-            Top = 29
+            Top = 28
             Width = 23
             Glyph.Data = {
               36030000424D3803000000000000360000002800000010000000100000000100
@@ -330,6 +336,23 @@ object FRMOperation: TFRMOperation
             }
             OnClick = sbSearchDestinationAccountClick
           end
+          object memoEPASA: TMemo
+            Left = 13
+            Height = 48
+            Top = 88
+            Width = 492
+            BorderStyle = bsNone
+            Font.Color = clBlack
+            Font.Height = -16
+            Font.Name = 'Tahoma'
+            OnChange = memoPayloadClick
+            OnClick = memoPayloadClick
+            ParentFont = False
+            ReadOnly = True
+            TabOrder = 2
+            TabStop = False
+            WantReturns = False
+          end
         end
         object tsChangePrivateKey: TTabSheet
           Caption = 'Change key'
@@ -1024,7 +1047,7 @@ object FRMOperation: TFRMOperation
     end
     object tsGlobalError: TTabSheet
       Caption = 'Notification'
-      ClientHeight = 325
+      ClientHeight = 357
       ClientWidth = 548
       ImageIndex = 1
       TabVisible = False
@@ -1148,8 +1171,8 @@ object FRMOperation: TFRMOperation
     Text = 'ebSenderAccount'
   end
   object ActionList: TActionList
-    left = 140
-    top = 350
+    Left = 140
+    Top = 350
     object actExecute: TAction
       Caption = 'Execute (F12)'
       OnExecute = actExecuteExecute

+ 151 - 75
src/gui-classic/UFRMOperation.pas

@@ -32,7 +32,7 @@ uses
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls, UNode, UWallet, UCrypto, Buttons, UBlockChain,
   UAccounts, UFRMAccountSelect, ActnList, ComCtrls, Types, UFRMMemoText,
-  UPCEncryption, UBaseTypes, UPCDataTypes, UPCOrderedLists, UEPasa;
+  UPCEncryption, UBaseTypes, UPCDataTypes, UPCOrderedLists, UEPasa, UEncoding;
 
 Const
   CM_PC_WalletKeysChanged = WM_USER + 1;
@@ -129,9 +129,10 @@ type
     ebHashLock: TEdit;
     btnHashLock: TSpeedButton;
     sbTimeLock: TSpeedButton;
-    cbPayloadAsHex: TCheckBox;
     lblChangeAccountData: TLabel;
     ebChangeAccountData: TEdit;
+    cbPayloadDataInputType: TComboBox;
+    memoEPASA: TMemo;
     procedure ebNewPublicKeyExit(Sender: TObject);
     procedure FormCreate(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
@@ -159,6 +160,7 @@ type
     FDefaultFee: Int64;
     FEncodedPayload : TOperationPayload;
     FDisabled : Boolean;
+    FUpdating : Boolean;
     FSenderAccounts: TOrderedCardinalList; // TODO: TOrderedCardinalList should be replaced with a "TCardinalList" since signer account should be processed last
     procedure SetWalletKeys(const Value: TWalletKeys);
     Procedure UpdateWalletKeys;
@@ -167,7 +169,7 @@ type
     Function UpdateFee(var Fee : Int64; errors : String) : Boolean;
     Function UpdateOperationOptions(var errors : String) : Boolean;
     Function UpdatePayload(Const ASenderAccount : TAccount; var AErrors : String) : Boolean;
-    Function UpdateOpTransaction(const ASenderAccount: TAccount; out LTargetEPASA : TEPasa;  out ATargetAccount : TAccount; out AResolvedTargetKey : TAccountKey; out ATargetRequiresPurchase : Boolean; out AAmount: Int64; out AErrors: String) : Boolean;
+    Function UpdateOpTransaction(const ASenderAccount: TAccount; out ATargetEPASA : TEPasa;  out ATargetAccount : TAccount; out AResolvedTargetKey : TAccountKey; out ATargetRequiresPurchase : Boolean; out AAmount: Int64; out AErrors: String) : Boolean;
     Function UpdateOpChangeKey(Const TargetAccount : TAccount; var SignerAccount : TAccount; var NewPublicKey : TAccountKey; var errors : String) : Boolean;
     Function UpdateOpListAccount(Const TargetAccount : TAccount; var SalePrice : Int64; var SellerAccount,SignerAccount : TAccount; var NewOwnerPublicKey : TAccountKey; var LockedUntilBlock : Cardinal; var HashLock : T32Bytes; var errors : String) : Boolean;
     Function UpdateOpDelist(Const TargetAccount : TAccount; var SignerAccount : TAccount; var errors : String) : Boolean;
@@ -179,6 +181,7 @@ type
     procedure CM_WalletChanged(var Msg: TMessage); message CM_PC_WalletKeysChanged;
     Function GetDefaultSenderAccount : TAccount;
     procedure ebAccountKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
+    function CaptureEPasa(const AEPasaTxt : String; out AEPasa : TEPasa) : Boolean;
   protected
     procedure searchAccount(editBox : TCustomEdit);
   public
@@ -470,6 +473,41 @@ begin
 end;
 
 
+function TFRMOperation.CaptureEPasa(const AEPasaTxt: String; out AEPasa: TEPasa): Boolean;
+begin
+  Result := TEPasa.TryParse(AEPasaTxt,AEPasa);
+  //
+  if ((FUpdating) or (Not Result)) then Exit;
+  FUpdating := True;
+  try
+    if AEPasa.PayloadType.HasTrait(ptPublic) then rbNotEncrypted.Checked := True
+    else if AEPasa.PayloadType.HasTrait(ptSenderKeyEncrypted) then rbEncryptedWithOldEC.Checked := True
+    else if AEPasa.PayloadType.HasTrait(ptRecipientKeyEncrypted) then rbEncryptedWithEC.Checked := True
+    else if AEPasa.PayloadType.HasTrait(ptPasswordEncrypted) then begin
+      rbEncrptedWithPassword.Checked := True;
+      ebEncryptPassword.Text := AEPasa.Password;
+    end;
+    if AEPasa.PayloadType.HasTrait(ptAsciiFormatted) then cbPayloadDataInputType.ItemIndex := 0
+    else if AEPasa.PayloadType.HasTrait(ptHexFormatted) then cbPayloadDataInputType.ItemIndex := 1
+    else if AEPasa.PayloadType.HasTrait(ptBase58Formatted) then cbPayloadDataInputType.ItemIndex := 2;
+    if (AEPasa.PayloadType.HasTrait(ptAsciiFormatted) or AEPasa.PayloadType.HasTrait(ptHexFormatted) or AEPasa.PayloadType.HasTrait(ptBase58Formatted)) then begin
+      memoPayload.Lines.Text := AEPasa.Payload;
+    end;
+
+    memoEPASA.Lines.Clear;
+    if AEPasa.IsPayToKey then memoEPASA.Lines.Add('PayToKey EPASA');
+    if AEPasa.IsAddressedByName then memoEPASA.Lines.Add('Addressed by name: '+AEPasa.AccountName);
+    memoEPASA.Lines.Add( AEPasa.ToString );
+    if AEPasa.PayloadType.HasTrait(ptAsciiFormatted) or AEPasa.PayloadType.HasTrait(ptBase58Formatted) or AEPasa.PayloadType.HasTrait(ptHexFormatted) then begin
+      memoEPasa.Lines.Add( 'AS BYTES: 0x'+AEPasa.GetRawPayloadBytes.ToHexaString);
+      memoEPasa.Lines.Add( 'AS PRINTABLE: '+AEPasa.GetRawPayloadBytes.ToPrintable);
+    end;
+
+  finally
+    FUpdating := False;
+  end;
+end;
+
 procedure TFRMOperation.CM_WalletChanged(var Msg: TMessage);
 begin
    UpdateWalletKeys;
@@ -479,14 +517,14 @@ procedure TFRMOperation.ebAccountNumberExit(Sender: TObject);
 Var LEPasa : TEPASA;
   eb : TEdit;
 begin
-  if (Not assigned(Sender)) then exit;
+  if (Not assigned(Sender)) or (FUpdating) then exit;
   if (Not (Sender is TEdit)) then exit;
   eb := TEdit(Sender);
-  If TEPasa.TryParse(eb.Text,LEPasa) then begin
+  if CaptureEPasa(eb.Text,LEPasa) then begin
     if LEPasa.IsClassicPASA then
       eb.Text := LEPasa.ToClassicPASAString()
     else
-      eb.Text := LEPasa.ToString();
+      eb.Text := LEPasa.ToString(True);
   end else begin
     eb.Text := '';
   end;
@@ -497,7 +535,7 @@ procedure TFRMOperation.ebCurrencyExit(Sender: TObject);
 Var m : Int64;
   eb : TEdit;
 begin
-  if (Not assigned(Sender)) then exit;
+  if (Not assigned(Sender)) or (FUpdating) then exit;
   if (Not (Sender is TEdit)) then exit;
   eb := TEdit(Sender);
   If Not (eb.ReadOnly) then begin
@@ -544,10 +582,10 @@ procedure TFRMOperation.FormCreate(Sender: TObject);
 begin
   {$IFDEF USE_GNUGETTEXT}TranslateComponent(self);{$ENDIF}
   FDisabled := false;
+  FUpdating := False;
   FWalletKeys := Nil;
   FSenderAccounts := TOrderedCardinalList.Create;
   FSenderAccounts.OnListChanged := OnSenderAccountsChanged;
-  FDisabled := true;
   FNode := TNode.Node;
   ebSenderAccount.OnKeyDown:=ebAccountKeyDown;
   ebSenderAccount.Tag:=CT_AS_MyAccounts;
@@ -620,6 +658,24 @@ begin
   ebFee.OnExit:= ebCurrencyExit;
   memoAccounts.Lines.Clear;
   PageControlOpType.ActivePage := tsTransaction;
+  cbPayloadDataInputType.Items.Clear;
+  cbPayloadDataInputType.Items.Add('As String');
+  cbPayloadDataInputType.Items.Add('As Hexadecimal');
+  cbPayloadDataInputType.Items.Add('As Base58');
+  cbPayloadDataInputType.ItemIndex := 0;
+  cbPayloadDataInputType.OnChange := memoPayloadClick;
+  memoPayload.OnChange := memoPayloadClick;
+  memoPayload.OnClick := memoPayloadClick;
+  memoEPASA.ScrollBars := ssBoth;
+  memoEPASA.ReadOnly := False;
+  memoEPASA.TabStop := False;
+  memoEPASA.ParentFont := True;
+  {$IFDEF TESTNET}
+  memoEPASA.Visible := True;
+  {$ELSE}
+  memoEPASA.Visible := False;
+  {$ENDIF}
+  memoEPASA.Enabled := memoEPASA.Visible;
 end;
 
 procedure TFRMOperation.ebNewPublicKeyExit(Sender: TObject);
@@ -852,17 +908,15 @@ begin
   end;
 end;
 
-var GInUpdateInfoClick : boolean;
 procedure TFRMOperation.updateInfoClick(Sender: TObject);
 Var errors : String;
 begin
-  if NOT GInUpdateInfoClick then begin
-    GInUpdateInfoClick := true;
-    try
-      UpdateOperationOptions(errors);
-    finally
-    GInUpdateInfoClick := false;
-    end;
+  if FDisabled or FUpdating then Exit;
+  FDisabled := True;
+  try
+    UpdateOperationOptions(errors);
+  finally
+    FDisabled := False;
   end;
 end;
 
@@ -1261,8 +1315,6 @@ begin
   lblSignerAccount.Enabled := ebSignerAccount.Enabled;
   lblChangeName.Enabled:= (PageControlOpType.ActivePage=tsChangeInfo) And (SenderAccounts.Count=1);
   ebChangeName.Enabled:= lblChangeName.Enabled;
-  //
-  UpdatePayload(sender_account, e);
 end;
 
 function TFRMOperation.UpdateOpListAccount(const TargetAccount: TAccount;
@@ -1522,25 +1574,44 @@ begin
   End;
 end;
 
-function TFRMOperation.UpdateOpTransaction(const ASenderAccount: TAccount; out LTargetEPASA : TEPasa; out ATargetAccount : TAccount; out AResolvedTargetKey : TECDSA_Public; out ATargetRequiresPurchase : Boolean; out AAmount: Int64; out AErrors: String): Boolean;
+function TFRMOperation.UpdateOpTransaction(const ASenderAccount: TAccount; out ATargetEPASA : TEPasa; out ATargetAccount : TAccount; out AResolvedTargetKey : TECDSA_Public; out ATargetRequiresPurchase : Boolean; out AAmount: Int64; out AErrors: String): Boolean;
 Var
   LResolvedAccountNo : Cardinal;
+  LPublicKey : TAccountKey;
 begin
   AErrors := '';
   lblTransactionErrors.Caption := '';
   if PageControlOpType.ActivePage<>tsTransaction then exit;
-  if not TEPasa.TryParse(ebDestAccount.Text, LTargetEPASA) then begin
-    AErrors := 'Invalid dest. EPASA ('+ebDestAccount.Text+')';
+
+  if (Trim(ebDestAccount.Text)='') then begin
+    AErrors := 'Need a destintation or EPASA';
+    lblTransactionErrors.Caption := AErrors;
+    Exit(False);
+  end;
+
+
+  if Not CaptureEPasa(ebDestAccount.Text,ATargetEPasa) then begin
+    AErrors := 'Invalid EPASA value: '+ebDestAccount.Text;
     lblTransactionErrors.Caption := AErrors;
     Exit(False);
   end;
 
-  Result := TNode.Node.TryResolveEPASA(LTargetEPASA, LResolvedAccountNo, AResolvedTargetKey, ATargetRequiresPurchase, AErrors);
+  Result := TNode.Node.TryResolveEPASA(ATargetEPASA, LResolvedAccountNo, AResolvedTargetKey, ATargetRequiresPurchase, AErrors);
   if NOT Result then begin
     lblTransactionErrors.Caption := AErrors;
     Exit(False);
   end;
 
+  // GUI Base58 protection: In order to prevent manual mistake, Base58 is only allowed when introducing
+  // a Public key, otherwise will need to use String ("") or Hexadecimal (0x..) input
+  if (ATargetEPASA.PayloadType.HasTrait(ptBase58Formatted)) then begin
+     if Not TAccountComp.AccountPublicKeyImport(ATargetEPASA.Payload,LPublicKey,AErrors) then begin
+       AErrors := 'Not a Base58 Public key: '+AErrors;
+       lblTransactionErrors.Caption := AErrors;
+       Exit(False);
+     end;
+  end;
+
   if LResolvedAccountNo <> CT_AccountNo_NUL then begin
     ATargetAccount := TNode.Node.GetMempoolAccount(LResolvedAccountNo);
     if ATargetAccount.account=ASenderAccount.account then begin
@@ -1583,6 +1654,7 @@ Var
   LPayloadBytes : TRawBytes;
 begin
   LValid := false;
+  LPayloadBytes := Nil;
   LEncryptedPayloadBytes := Nil;
   FEncodedPayload := CT_TOperationPayload_NUL;
   LUserPayloadString := memoPayload.Lines.Text;
@@ -1590,55 +1662,60 @@ begin
   AErrors := 'Unknown error';
   try
     LTargetEPASA := TEPasa.Empty;
-    If (PageControlOpType.ActivePage=tsTransaction) then begin
-      if NOT TEPasa.TryParse(ebDestAccount.Text, LTargetEPASA) then begin
-        AErrors := 'Indeterminable target';
-        Exit(False);
-      end;
 
-      if LTargetEPASA.IsPayToKey then begin
-        LValid := true;
-        Exit;
-      end;
-
-      if LTargetEPASA.HasPayload then begin
-        if LUserPayloadString <> '' then begin
-          AErrors := 'Ambiguous payload. Payload already specified by target EPASA.';
-          LValid := False;
-          Exit;
-        end;
-
-        if ebEncryptPassword.Text <> '' then begin
-          AErrors := 'Ambiguous payload. Password cannot be specified.';
-          LValid := False;
-          Exit;
-        end;
-      end;
-      FEncodedPayload.payload_type := LTargetEPASA.PayloadType.ToProtocolValue;
+    if NOT TEPasa.TryParse(ebDestAccount.Text, LTargetEPASA) then begin
+      AErrors := 'Indeterminable target';
+      Exit(False);
     end;
 
-    if (LUserPayloadString='') AND (LTargetEPASA.Payload.IsEmpty) then begin
-      LValid := true;
-      exit;
+    if (LUserPayloadString='') then begin
+      LValid := True;
+      Exit(True);
     end;
-    if LTargetEPASA.HasPayload then begin
-      LPayloadBytes := LTargetEPASA.GetRawPayloadBytes;
-    end else if cbPayloadAsHex.Checked then begin
-      if NOT TCrypto.HexaToRaw(LUserPayloadString, LPayloadBytes) then begin
-        LValid := false;
-        AErrors := 'Payload not hex-formatted';
-        exit;
+
+    LTargetEPASA.PayloadType := LTargetEPASA.PayloadType - [ptPublic] - [ptRecipientKeyEncrypted] - [ptSenderKeyEncrypted]
+      - [ptPasswordEncrypted] - [ptAsciiFormatted] - [ptHexFormatted] - [ptBase58Formatted];
+
+    case cbPayloadDataInputType.ItemIndex of
+      0 : begin
+           LTargetEPASA.PayloadType := LTargetEPASA.PayloadType + [ptAsciiFormatted];
+           LPayloadBytes := TEncoding.ASCII.GetBytes(LUserPayloadString);
+      end;
+      1 : begin
+         LTargetEPASA.PayloadType := LTargetEPASA.PayloadType + [ptHexFormatted];
+         if Not THexEncoding.TryDecode(LUserPayloadString,LPayloadBytes) then begin
+           AErrors := 'Payload is not an Hexadecimal string';
+           Exit(False);
+         end;
       end;
-    end else LPayloadBytes := TEncoding.ANSI.GetBytes(LUserPayloadString);
+      2 : begin
+         LTargetEPASA.PayloadType := LTargetEPASA.PayloadType + [ptBase58Formatted];
+         if Not TPascalBase58Encoding.TryDecode(LUserPayloadString,LPayloadBytes) then begin
+           AErrors := 'Payload is not a Base 58 string';
+           Exit(False);
+         end;
+
+         // GUI Base58 protection: In order to prevent manual mistake, Base58 is only allowed when introducing
+         // a Public key, otherwise will need to use String ("") or Hexadecimal (0x..) input
+         if Not TAccountComp.AccountPublicKeyImport(LUserPayloadString,LPublicKey,AErrors) then begin
+           AErrors := 'Not a Public key: '+AErrors;
+           Exit(False);
+         end;
+      end
+    else
+    end;
+    LTargetEPASA.Payload := LUserPayloadString;
 
-    if (NOT LTargetEPasa.HasPayload AND rbEncryptedWithOldEC.Checked) OR LTargetEPASA.PayloadType.HasTrait(ptSenderKeyEncrypted) then begin
+    if (Length(LPayloadBytes)>0) and (rbEncryptedWithOldEC.Checked) then begin
       // Use sender
-      AErrors := 'Error encrypting';
+      LTargetEPASA.PayloadType := LTargetEPASA.PayloadType + [ptSenderKeyEncrypted];
+      AErrors := 'Error encrypting by sender';
       LAccount := FNode.GetMempoolAccount(ASenderAccount.account);
       TPCEncryption.DoPascalCoinECIESEncrypt(LAccount.accountInfo.accountKey,LPayloadBytes,LEncryptedPayloadBytes);
       LValid := Length(LEncryptedPayloadBytes)>0;
-    end else if (NOT LTargetEPasa.HasPayload AND rbEncryptedWithEC.Checked) or LTargetEPASA.PayloadType.HasTrait(ptRecipientKeyEncrypted) then begin
-      AErrors := 'Error encrypting';
+    end else if (Length(LPayloadBytes)>0) and (rbEncryptedWithEC.Checked) then begin
+      AErrors := 'Error encrypting by recipient';
+      LTargetEPASA.PayloadType := LTargetEPASA.PayloadType + [ptRecipientKeyEncrypted];
       if (PageControlOpType.ActivePage=tsTransaction) or (PageControlOpType.ActivePage=tsListAccount) or (PageControlOpType.ActivePage=tsDelistAccount)
         or (PageControlOpType.ActivePage=tsBuyAccount) then begin
 
@@ -1704,14 +1781,14 @@ begin
       end else begin
         AErrors := 'This operation does not allow this kind of payload';
       end;
-    end else if (NOT LTargetEPasa.HasPayload AND rbEncrptedWithPassword.Checked) OR LTargetEPASA.PayloadType.HasTrait(ptPasswordEncrypted) then begin
-      if LTargetEPASA.PayloadType.HasTrait(ptPasswordEncrypted) then
-        LPassword := LTargetEPASA.Password
-      else
-        LPassword := ebEncryptPassword.Text;
+    end else if (Length(LPayloadBytes)>0) AND (rbEncrptedWithPassword.Checked) then begin
+      LTargetEPASA.PayloadType := LTargetEPASA.PayloadType + [ptPasswordEncrypted];
+      LPassword := ebEncryptPassword.Text;
       LEncryptedPayloadBytes := TPCEncryption.DoPascalCoinAESEncrypt(LPayloadBytes,TEncoding.ANSI.GetBytes(LPassword));
+      LTargetEPASA.Password := LPassword;
       LValid := Length(LEncryptedPayloadBytes)>0;
-    end else if (NOT LTargetEPasa.HasPayload AND rbNotEncrypted.Checked) or LTargetEPASA.PayloadType.HasTrait(ptPublic) then begin
+    end else if (Length(LPayloadBytes)>0) AND (rbNotEncrypted.Checked) then begin
+      LTargetEPASA.PayloadType := LTargetEPASA.PayloadType + [ptPublic];
       LEncryptedPayloadBytes := LPayloadBytes;
       LValid := true;
     end else begin
@@ -1726,19 +1803,18 @@ begin
     end;
     if LValid then begin
       lblEncryptionErrors.Caption := '';
-      if LTargetEPASA.HasPayload then
-        lblPayloadLength.Caption := Format('(%db -> %db)',[length(LTargetEPASA.Payload),length(LEncryptedPayloadBytes)])
-      else
-        lblPayloadLength.Caption := Format('(%db -> %db)',[length(LUserPayloadString),length(LEncryptedPayloadBytes)]);
+      lblPayloadLength.Caption := Format('(%db -> %db)',[length(LTargetEPASA.Payload),length(LEncryptedPayloadBytes)])
     end else begin
+      if Trim(AErrors)='' then AErrors := 'Undefined';
       lblEncryptionErrors.Caption := AErrors;
-      if LTargetEPASA.HasPayload then
-        lblPayloadLength.Caption := Format('(%db -> ?)',[length(LTargetEPASA.Payload)])
-      else
-        lblPayloadLength.Caption := Format('(%db -> ?)',[length(LUserPayloadString)]);
+      lblPayloadLength.Caption := Format('(%db -> ?)',[length(LTargetEPASA.Payload)])
     end;
+    FEncodedPayload.payload_type := LTargetEPASA.PayloadType.ToProtocolValue;
     FEncodedPayload.payload_raw := LEncryptedPayloadBytes;
     Result := LValid;
+    if (LValid) And (Not FUpdating) then begin
+      ebDestAccount.Text := LTargetEPASA.ToClassicPASAString;
+    end;
   end;
 end;
 

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

@@ -715,7 +715,9 @@ begin
     If op.OperationToOperationResume(FSourceNode.Bank.BlocksCount,op,True,op.SignerAccount,opr) then begin
       jsonObj := TPCJSONObject.Create;
       Try
-        TPascalCoinJSONComp.FillOperationObject(opr,FSourceNode.Bank.BlocksCount,jsonObj);
+        TPascalCoinJSONComp.FillOperationObject(opr,FSourceNode.Bank.BlocksCount,
+          SourceNode,SourceWalletKeys,Nil,
+          jsonObj);
         mOperationInfo.Lines.Add('JSON:');
         mOperationInfo.Lines.Add(jsonObj.ToJSON(False));
       Finally

+ 20 - 19
src/gui-classic/UFRMWallet.lfm

@@ -4,7 +4,7 @@ object FRMWallet: TFRMWallet
   Top = 201
   Width = 865
   Caption = 'Pascal full node Wallet (Classic GUI)'
-  ClientHeight = 600
+  ClientHeight = 580
   ClientWidth = 865
   Color = clBtnFace
   Constraints.MinHeight = 600
@@ -16,6 +16,7 @@ object FRMWallet: TFRMWallet
   OnCreate = FormCreate
   OnDestroy = FormDestroy
   Position = poScreenCenter
+  LCLVersion = '2.0.10.0'
   object pnlTop: TPanel
     Left = 0
     Height = 91
@@ -311,7 +312,7 @@ object FRMWallet: TFRMWallet
   object StatusBar: TStatusBar
     Left = 0
     Height = 23
-    Top = 577
+    Top = 557
     Width = 865
     Panels = <    
       item
@@ -331,7 +332,7 @@ object FRMWallet: TFRMWallet
   end
   object PageControl: TPageControl
     Left = 0
-    Height = 486
+    Height = 466
     Top = 91
     Width = 865
     ActivePage = tsMyAccounts
@@ -341,11 +342,11 @@ object FRMWallet: TFRMWallet
     OnChange = PageControlChange
     object tsMyAccounts: TTabSheet
       Caption = 'Account Explorer'
-      ClientHeight = 460
+      ClientHeight = 440
       ClientWidth = 857
       object Splitter1: TSplitter
         Left = 400
-        Height = 394
+        Height = 374
         Top = 66
         Width = 5
       end
@@ -471,17 +472,17 @@ object FRMWallet: TFRMWallet
       end
       object pnlAccounts: TPanel
         Left = 0
-        Height = 394
+        Height = 374
         Top = 66
         Width = 400
         Align = alLeft
         BevelOuter = bvNone
-        ClientHeight = 394
+        ClientHeight = 374
         ClientWidth = 400
         TabOrder = 1
         object dgAccounts: TDrawGrid
           Left = 0
-          Height = 360
+          Height = 340
           Top = 0
           Width = 400
           Align = alClient
@@ -495,7 +496,7 @@ object FRMWallet: TFRMWallet
         object pnlAccountsInfo: TPanel
           Left = 0
           Height = 34
-          Top = 360
+          Top = 340
           Width = 400
           Align = alBottom
           BevelOuter = bvNone
@@ -576,7 +577,7 @@ object FRMWallet: TFRMWallet
       end
       object pcAccountsOptions: TPageControl
         Left = 405
-        Height = 394
+        Height = 374
         Top = 66
         Width = 452
         ActivePage = tsMultiSelectAccounts
@@ -603,12 +604,12 @@ object FRMWallet: TFRMWallet
         end
         object tsMultiSelectAccounts: TTabSheet
           Caption = 'Selected Accounts For Batch Operation'
-          ClientHeight = 368
+          ClientHeight = 348
           ClientWidth = 444
           ImageIndex = 1
           object dgSelectedAccounts: TDrawGrid
             Left = 41
-            Height = 311
+            Height = 291
             Top = 31
             Width = 320
             Align = alLeft
@@ -645,7 +646,7 @@ object FRMWallet: TFRMWallet
           object pnlSelectedAccountsBottom: TPanel
             Left = 0
             Height = 26
-            Top = 342
+            Top = 322
             Width = 444
             Align = alBottom
             BevelOuter = bvNone
@@ -687,12 +688,12 @@ object FRMWallet: TFRMWallet
           end
           object pnlSelectedAccountsLeft: TPanel
             Left = 0
-            Height = 311
+            Height = 291
             Top = 31
             Width = 41
             Align = alLeft
             BevelOuter = bvNone
-            ClientHeight = 311
+            ClientHeight = 291
             ClientWidth = 41
             TabOrder = 3
             object sbSelectedAccountsAdd: TSpeedButton
@@ -1191,12 +1192,12 @@ object FRMWallet: TFRMWallet
   end
   object TimerUpdateStatus: TTimer
     OnTimer = TimerUpdateStatusTimer
-    left = 32
-    top = 56
+    Left = 32
+    Top = 56
   end
   object MainMenu: TMainMenu
-    left = 165
-    top = 160
+    Left = 165
+    Top = 160
     object miProject: TMenuItem
       Caption = 'Project'
       object miPrivatekeys: TMenuItem

+ 13 - 4
src/gui-classic/UFRMWallet.pas

@@ -35,7 +35,7 @@ uses
   UNode, UGridUtils, UJSONFunctions, UAccounts, Menus, ImgList, UNetProtocol,
   UCrypto, Buttons, UPoolMining, URPC, UFRMAccountSelect, UConst,
   UAccountKeyStorage, UBaseTypes, UPCDataTypes, UOrderedList,
-  UFRMRPCCalls, UTxMultiOperation, USettings,
+  UFRMRPCCalls, UTxMultiOperation, USettings, UEPasa,
   {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF};
 
 Const
@@ -333,7 +333,7 @@ Uses UFolderHelper,{$IFDEF USE_GNUGETTEXT}gnugettext,{$ENDIF}
   {$ENDIF}
   UPCTNetDataExtraMessages,
   UFRMAskForAccount,
-  UAbstractBTree,
+  UAbstractBTree, UEPasaDecoder,
   UFRMAbout, UFRMOperation, UFRMWalletKeys, UFRMPayloadDecoder, UFRMNodesIp, UFRMMemoText,
   UCommon, UPCOrderedLists;
 
@@ -981,6 +981,7 @@ procedure TFRMWallet.FillOperationInformation(const Strings: TStrings;
   const OperationResume: TOperationResume);
 var i : Integer;
   jsonObj : TPCJSONObject;
+  LEPASA : TEPasa;
 begin
   If (not OperationResume.valid) then exit;
   If OperationResume.Block<FNode.Bank.BlocksCount then
@@ -1009,7 +1010,10 @@ begin
   If (Length(OperationResume.OperationHash_OLD)>0) then begin
     Strings.Add(Format('Old Operation Hash (old_ophash): %s',[TCrypto.ToHexaString(OperationResume.OperationHash_OLD)]));
   end;
-  Strings.Add(Format('Payload type:%d length:%d',[OperationResume.OriginalPayload.payload_type, length(OperationResume.OriginalPayload.payload_raw)]));
+  if TEPasaDecoder.TryDecodeEPASA(OperationResume.DestAccount,OperationResume.OriginalPayload,FNode,FWalletKeys,Nil,LEPASA) then begin
+    Strings.Add('EPASA: '+LEPASA.ToString);
+  end else Strings.Add('No EPASA format');
+  Strings.Add(Format('Payload type:%s length:%d',['0x'+IntToHex(OperationResume.OriginalPayload.payload_type,2), length(OperationResume.OriginalPayload.payload_raw)]));
   if (Length(OperationResume.OriginalPayload.payload_raw)>0) then begin
     If OperationResume.PrintablePayload<>'' then begin
       Strings.Add(Format('Payload (human): %s',[OperationResume.PrintablePayload]));
@@ -1021,7 +1025,9 @@ begin
   end;
   jsonObj := TPCJSONObject.Create;
   Try
-    TPascalCoinJSONComp.FillOperationObject(OperationResume,FNode.Bank.BlocksCount,jsonObj);
+    TPascalCoinJSONComp.FillOperationObject(OperationResume,FNode.Bank.BlocksCount,
+      FNode,FWalletKeys,Nil,
+      jsonObj);
     Strings.Add('OPERATION JSON:');
     Strings.Add(jsonObj.ToJSON(False));
   Finally
@@ -1348,14 +1354,17 @@ begin
   FOperationsAccountGrid := TOperationsGrid.Create(Self);
   FOperationsAccountGrid.DrawGrid := dgAccountOperations;
   FOperationsAccountGrid.MustShowAlwaysAnAccount := true;
+  FOperationsAccountGrid.WalletKeys := FWalletKeys;
   FPendingOperationsGrid := TOperationsGrid.Create(Self);
   FPendingOperationsGrid.DrawGrid := dgPendingOperations;
   FPendingOperationsGrid.AccountNumber := -1; // all
   FPendingOperationsGrid.PendingOperations := true;
+  FPendingOperationsGrid.WalletKeys := FWalletKeys;
   FOperationsExplorerGrid := TOperationsGrid.Create(Self);
   FOperationsExplorerGrid.DrawGrid := dgOperationsExplorer;
   FOperationsExplorerGrid.AccountNumber := -1;
   FOperationsExplorerGrid.PendingOperations := False;
+  FOperationsExplorerGrid.WalletKeys := FWalletKeys;
   FBlockChainGrid := TBlockChainGrid.Create(Self);
   FBlockChainGrid.DrawGrid := dgBlockChainExplorer;
   FBlockChainGrid.ShowTimeAverageColumns:={$IFDEF SHOW_AVERAGE_TIME_STATS}True;{$ELSE}False;{$ENDIF}

+ 38 - 5
src/gui-classic/UGridUtils.pas

@@ -134,7 +134,7 @@ Type
 
   TOperationsGridUpdateThread = Class(TPCThread)
     FOperationsGrid : TOperationsGrid;
-    procedure DoUpdateOperationsGrid(ANode : TNode; var AList : TList<TOperationResume>);
+    procedure DoUpdateOperationsGrid(const ANode : TNode; const AWalleTKeys : TWalletKeys; const APasswords : TList<String>; var AList : TList<TOperationResume>);
   protected
     procedure BCExecute; override;
   public
@@ -152,6 +152,8 @@ Type
     FBlockEnd: Int64;
     FMustShowAlwaysAnAccount: Boolean;
     FOperationsGridUpdateThread : TOperationsGridUpdateThread;
+    FWalletKeys: TWalletKeys;
+    FPasswords: TList<String>;
     Procedure OnNodeNewOperation(Sender : TObject);
     Procedure OnNodeNewAccount(Sender : TObject);
     Procedure InitGrid;
@@ -177,6 +179,8 @@ Type
     Property AccountNumber : Int64 read FAccountNumber write SetAccountNumber;
     Property MustShowAlwaysAnAccount : Boolean read FMustShowAlwaysAnAccount write SetMustShowAlwaysAnAccount;
     Property Node : TNode read GetNode write SetNode;
+    property WalletKeys : TWalletKeys read FWalletKeys write FWalletKeys;
+    property Passwords : TList<String> read FPasswords;
     Procedure UpdateAccountOperations; virtual;
     Procedure ShowModalDecoder(WalletKeys: TWalletKeys; AppParams : TAppParams);
     Property BlockStart : Int64 read FBlockStart write SetBlockStart;
@@ -281,6 +285,7 @@ implementation
 
 uses
   Graphics, SysUtils, UTime, UOpTransaction, UConst,
+  UEPasa, UEPasaDecoder,
   UFRMPayloadDecoder, ULog;
 
 { TAccountsGridUpdateThread }
@@ -940,7 +945,7 @@ var list : TList<TOperationResume>;
 begin
   list := TList<TOperationResume>.Create;
   try
-    DoUpdateOperationsGrid(FOperationsGrid.Node,list);
+    DoUpdateOperationsGrid(FOperationsGrid.Node,FOperationsGrid.WalletKeys,FOperationsGrid.Passwords,list);
     if (Not Terminated) then begin
       FOperationsGrid.FOperationsResume.Clear;
       for i := 0 to list.Count-1 do begin
@@ -961,7 +966,8 @@ begin
   Suspended := False;
 end;
 
-procedure TOperationsGridUpdateThread.DoUpdateOperationsGrid(ANode: TNode; var AList: TList<TOperationResume>);
+procedure TOperationsGridUpdateThread.DoUpdateOperationsGrid(const ANode : TNode; const AWalleTKeys : TWalletKeys;
+  const APasswords : TList<String>; var AList: TList<TOperationResume>);
 Var list : TList<Cardinal>;
   i,j : Integer;
   OPR : TOperationResume;
@@ -970,6 +976,7 @@ Var list : TList<Cardinal>;
   bstart,bend : int64;
   LOperationsResume : TOperationsResumeList;
   LLockedMempool : TPCOperationsComp;
+  LEPasa : TEPasa;
 begin
   if Not Assigned(ANode) then exit;
   AList.Clear;
@@ -1061,6 +1068,13 @@ begin
       end;
     end;
   Finally
+    for i := 0 to AList.Count-1 do begin
+      OPR := AList[i];
+      if TEPasaDecoder.TryDecodeEPASA(OPR.AffectedAccount,OPR.OriginalPayload,ANode,AWalleTKeys,APasswords,LEPasa) then begin
+        OPR.PrintablePayload := LEPasa.ToString(True);
+        AList[i] := OPR;
+      end;
+    end;
   End;
 end;
 
@@ -1068,6 +1082,8 @@ end;
 
 constructor TOperationsGrid.Create(AOwner: TComponent);
 begin
+  FPasswords := TList<String>.Create;
+  FWalletKeys := Nil;
   FAccountNumber := 0;
   FDrawGrid := Nil;
   MustShowAlwaysAnAccount := false;
@@ -1091,6 +1107,7 @@ begin
   end;
   FOperationsResume.Free;
   FNodeNotifyEvents.Free;
+  FPasswords.Free;
   inherited;
 end;
 
@@ -1135,8 +1152,9 @@ begin
 end;
 
 procedure TOperationsGrid.OnGridDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
-Var s : String;
+Var s, saux : String;
   opr : TOperationResume;
+  LRectLeft, LRectRight : TRect;
 begin
   DrawGrid.Canvas.Font.Color:=clWindowText;
   opr := CT_TOperationResume_NUL;
@@ -1209,7 +1227,22 @@ begin
         Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfRight,tfVerticalCenter,tfSingleLine]);
       end else if ACol=7 then begin
         s := opr.PrintablePayload;
-        Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfLeft,tfVerticalCenter,tfSingleLine]);
+        LRectRight := Rect;
+        if opr.OriginalPayload.payload_type>0 then begin
+          saux := '0x'+IntToHex(opr.OriginalPayload.payload_type,2);
+          LRectLeft := Rect;
+          LRectLeft.Width := 30;
+          Rect.Inflate(-32,0,0,0);
+          DrawGrid.Canvas.Font.Color := clBlue;
+          DrawGrid.Canvas.Font.Style := [fsBold];
+          Canvas_TextRect(DrawGrid.Canvas,LRectLeft,saux,State,[tfLeft,tfVerticalCenter,tfSingleLine]);
+          DrawGrid.Canvas.Font.Style := [];
+        end;
+        DrawGrid.Canvas.Font.Color := clBlack;
+        if opr.OriginalPayload.payload_raw.ToString=s then begin
+          DrawGrid.Canvas.Font.Style := [fsBold];
+        end;
+        Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfLeft,tfVerticalCenter,tfSingleLine])
       end else begin
         s := '(???)';
         Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfCenter,tfVerticalCenter,tfSingleLine]);