Single Payment Signature Structure
To authorize payment operations the user must generate a cryptographic signature compliant with the EIP-191 standard using the Ethereum Signed Message format.
The signature verification process uses the SignatureRecover
library which implements the standard Ethereum message signing protocol. The message is constructed by concatenating the EVVM ID, action type, and parameters, then wrapped with the EIP-191 prefix: "\x19Ethereum Signed Message:\n"
+ message length + message content.
The structure uses conditional logic to determine whether to use a direct address or identity string for the recipient.
Signed Message Format
The signature verification uses the SignatureRecover.signatureVerification
function with the following structure:
SignatureRecover.signatureVerification(
Strings.toString(evvmID), // EVVM ID as string
"pay", // Action type
string.concat( // Concatenated parameters
_receiverAddress == address(0)
? _receiverIdentity
: AdvancedStrings.addressToString(_receiverAddress),
",",
AdvancedStrings.addressToString(_token),
",",
Strings.toString(_amount),
",",
Strings.toString(_priorityFee),
",",
Strings.toString(_nonce),
",",
_priorityFlag ? "true" : "false",
",",
AdvancedStrings.addressToString(_executor)
),
signature,
signer
);
Internal Message Construction
Internally, the SignatureRecover.signatureVerification
function constructs the final message by concatenating:
string.concat(evvmID, ",", functionName, ",", inputs)
This results in a message format:
"{evvmID},pay,{receiver},{token},{amount},{priorityFee},{nonce},{priorityFlag},{executor}"
EIP-191 Message Hashing
The message is then hashed according to EIP-191 standard:
bytes32 messageHash = keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n",
Strings.toString(bytes(message).length),
message
)
);
This creates the final hash that the user must sign with their private key.
Message Components
The signature verification takes three main parameters:
1. EVVM ID (String):
- The result of
Strings.toString(evvmID)
- Purpose: Identifies the specific EVVM instance
2. Action Type (String):
- Fixed value:
"pay"
- Purpose: Identifies this as a payment operation
3. Concatenated Parameters (String): The parameters are concatenated with comma separators:
3.1. Recipient Identifier (String):
- If
_receiverAddress == address(0)
: Use_receiverIdentity
string directly - If
_receiverAddress != address(0)
: UseAdvancedStrings.addressToString(_receiverAddress)
- Purpose: Specifies the intended recipient using either address or identity
3.2. Token Address (String):
- The result of
AdvancedStrings.addressToString(_token)
- Purpose: Identifies the token being transferred
3.3. Amount (String):
- The result of
Strings.toString(_amount)
- Purpose: Specifies the quantity of the token to be transferred
3.4. Priority Fee (String):
- The result of
Strings.toString(_priorityFee)
- Purpose: Specifies the fee paid to staking holders
3.5. Nonce (String):
- The result of
Strings.toString(_nonce)
- Purpose: Provides replay protection for the transaction
3.6. Priority Flag (String):
"true"
: If_priorityFlag
istrue
(asynchronous)"false"
: If_priorityFlag
isfalse
(synchronous)- Purpose: Explicitly includes the execution mode in the signed message
3.7. Executor Address (String):
- The result of
AdvancedStrings.addressToString(_executor)
- Purpose: Specifies the address authorized to submit this payment request
Example
Here's a practical example of constructing a signature message for sending 0.05 ETH:
Scenario: User wants to send 0.05 ETH to another user using synchronous processing
Parameters:
evvmID
:1
(EVVM instance ID)_priorityFlag
:false
(synchronous processing)_receiverAddress
:0x742c7b6b472c8f4bd58e6f9f6c82e8e6e7c82d8c
_token
:address(0)
(ETH)_amount
:50000000000000000
(0.05 ETH in wei)_priorityFee
:1000000000000000
(0.001 ETH in wei)_nonce
:42
_executor
:0x0000000000000000000000000000000000000000
(unrestricted)
Signature verification call:
SignatureRecover.signatureVerification(
"1", // evvmID as string
"pay", // action type
"0x742c7b6b472c8f4bd58e6f9f6c82e8e6e7c82d8c,0x0000000000000000000000000000000000000000,50000000000000000,1000000000000000,42,false,0x0000000000000000000000000000000000000000",
signature,
signer
);
Final message to be signed (after internal concatenation):
1,pay,0x742c7b6b472c8f4bd58e6f9f6c82e8e6e7c82d8c,0x0000000000000000000000000000000000000000,50000000000000000,1000000000000000,42,false,0x0000000000000000000000000000000000000000
EIP-191 formatted message hash:
keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n145",
"1,pay,0x742c7b6b472c8f4bd58e6f9f6c82e8e6e7c82d8c,0x0000000000000000000000000000000000000000,50000000000000000,1000000000000000,42,false,0x0000000000000000000000000000000000000000"
))
Concatenated parameters breakdown:
0x742c7b6b472c8f4bd58e6f9f6c82e8e6e7c82d8c
- Receiver address0x0000000000000000000000000000000000000000
- Token address (ETH)50000000000000000
- Amount in wei (0.05 ETH)1000000000000000
- Priority fee in wei (0.001 ETH)42
- Noncefalse
- Priority flag (synchronous)0x0000000000000000000000000000000000000000
- Executor (unrestricted)
Example with Username
Here's another example using a username instead of an address:
Scenario: User wants to send 0.05 ETH to username "example" using asynchronous processing
Parameters:
evvmID
:1
(EVVM instance ID)_priorityFlag
:true
(asynchronous processing)_receiverAddress
:address(0)
(using identity instead)_receiverIdentity
:"example"
_token
:address(0)
(ETH)_amount
:50000000000000000
(0.05 ETH in wei)_priorityFee
:2000000000000000
(0.002 ETH in wei)_nonce
:15
_executor
:0x0000000000000000000000000000000000000000
(unrestricted)
Signature verification call:
SignatureRecover.signatureVerification(
"1", // evvmID as string
"pay", // action type
"example,0x0000000000000000000000000000000000000000,50000000000000000,2000000000000000,15,true,0x0000000000000000000000000000000000000000",
signature,
signer
);
Final message to be signed (after internal concatenation):
1,pay,example,0x0000000000000000000000000000000000000000,50000000000000000,2000000000000000,15,true,0x0000000000000000000000000000000000000000
EIP-191 formatted message hash:
keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n134",
"1,pay,example,0x0000000000000000000000000000000000000000,50000000000000000,2000000000000000,15,true,0x0000000000000000000000000000000000000000"
))
Concatenated parameters breakdown:
example
- Receiver identity (username)0x0000000000000000000000000000000000000000
- Token address (ETH)50000000000000000
- Amount in wei (0.05 ETH)2000000000000000
- Priority fee in wei (0.002 ETH)15
- Noncetrue
- Priority flag (asynchronous)0x0000000000000000000000000000000000000000
- Executor (unrestricted)
Signature Implementation Details
The SignatureRecover
library performs signature verification in the following steps:
- Message Construction: Concatenates
evvmID
,functionName
, andinputs
with commas - EIP-191 Formatting: Prepends
"\x19Ethereum Signed Message:\n"
+ message length - Hashing: Applies
keccak256
to the formatted message - Signature Parsing: Splits the 65-byte signature into
r
,s
, andv
components - Recovery: Uses
ecrecover
to recover the signer's address - Verification: Compares recovered address with expected signer
Signature Format Requirements
- Length: Exactly 65 bytes
- Structure:
[r (32 bytes)][s (32 bytes)][v (1 byte)]
- V Value: Must be 27 or 28 (automatically adjusted if < 27)
- Message Format: The final message follows the pattern
"{evvmID},{functionName},{parameters}"
- EIP-191 Compliance: Uses
"\x19Ethereum Signed Message:\n"
prefix with message length - Hash Function:
keccak256
is used for the final message hash before signing - Signature Recovery: Uses
ecrecover
to verify the signature against the expected signer - String Conversion:
AdvancedStrings.addressToString
converts addresses to lowercase hex with "0x" prefixStrings.toString
converts numbers to decimal strings
- Priority Flag: Determines execution mode (async=
true
, sync=false
) - Recipient Logic: Uses
_receiverIdentity
if_receiverAddress == address(0)
, otherwise uses the address - EVVM ID: Identifies the specific EVVM instance for signature verification