EVVM Signature Structures Overview
All EVVM operations require EIP-191 cryptographic signatures for security. The platform uses centralized signature verification where Core.sol validates all operations using a unified signature format.
Universal Signature Format
{evvmId},{senderExecutor},{hashPayload},{originExecutor},{nonce},{isAsyncExec}
Components:
{evvmId}: Network identifier (uint256, typically1){senderExecutor}: Address that can call the function via msg.sender (0x0...0for anyone){hashPayload}: Service-specific hash of operation parameters (bytes32){originExecutor}: EOA that can initiate the transaction via tx.origin (0x0...0for anyone){nonce}: User's centralized nonce from Core.sol (uint256){isAsyncExec}: Execution mode -truefor async,falsefor sync (boolean)
Dual-Executor Transaction Model
EVVM uses a dual-executor system for flexible transaction control:
senderExecutor (msg.sender control)
Controls which address can call the contract function:
// Validated in Core.sol
if (senderExecutor != address(0) && msg.sender != senderExecutor) {
revert Core__InvalidExecutor();
}
Common Patterns:
address(0): Anyone can execute (maximum flexibility)specificAddress: Only that address can call the functionserviceAddress: Service can execute on behalf of user
Use Case: Allows users to restrict which contracts or addresses can execute their signed transactions (e.g., only through a specific relayer or service).
originExecutor (tx.origin control)
Controls which EOA can initiate the entire transaction:
// Validated in Core.sol
if (originExecutor != address(0) && tx.origin != originExecutor) {
revert Core__InvalidExecutor();
}
Common Patterns:
address(0): Any EOA can initiate (maximum flexibility)userEOA: Only that EOA can initiate the transactiontrustedEOA: Only specific EOA can initiate (delegation)
Use Case: Enables users to ensure only they (or a trusted party) can trigger the transaction, even if msg.sender is a contract.
Flexibility Mechanism
Both executors support address(0) for maximum flexibility:
// Example: Public execution (anyone can call, anyone can initiate)
senderExecutor = address(0);
originExecutor = address(0);
// Example: Personal execution only
senderExecutor = address(0); // Any contract can call
originExecutor = user; // But only user's EOA can initiate
// Example: Service-restricted
senderExecutor = serviceAddress; // Only service contract can call
originExecutor = address(0); // Any EOA can initiate
// Example: Fully restricted
senderExecutor = relayerContract; // Only relayer can call
originExecutor = userEOA; // Only user's EOA can initiate
Hash Payload Independence
Important: The hashPayload does NOT include executor addresses. It contains only operation-specific parameters:
// Example: CoreHashUtils.hashDataForPay
bytes32 hashPayload = keccak256(
abi.encode(
"pay",
to_address,
to_identity,
token,
amount,
priorityFee
)
);
// Executors are NOT in the hash - they're only in the signature payload
Two-Layer Signature Architecture
EVVM uses a two-layer hashing system for deterministic and gas-efficient signatures:
Layer 1: Hash Payload Generation
Each service uses a dedicated HashUtils library to generate hashPayload:
// Example: CoreHashUtils for payment operations
bytes32 hashPayload = CoreHashUtils.hashDataForPay(
receiver,
token,
amount,
priorityFee
);
Available HashUtils Libraries:
- CoreHashUtils: Payment operations (pay, batchPay, dispersePay)
- NameServiceHashUtils: Username operations (register, transfer, metadata)
- StakingHashUtils: Staking operations (stake, unstake, claim)
- P2PSwapHashUtils: Swap operations (makeOrder, dispatch, cancel)
- TreasuryCrossChainHashUtils: Cross-chain bridge operations
Layer 2: Signature Construction
The hashPayload is combined with execution context to create the final message:
// Using AdvancedStrings.buildSignaturePayload
string memory message = AdvancedStrings.buildSignaturePayload(
evvmId, // Network ID
senderExecutor, // msg.sender control
hashPayload, // Service-specific hash
originExecutor, // tx.origin control
nonce, // User's nonce
isAsyncExec // Execution mode
);
// Returns: "{evvmId},{senderExecutor},{hashPayload},{originExecutor},{nonce},{isAsyncExec}"
Layer 3: EIP-191 Wrapping
The message is signed using the EIP-191 standard (same as MetaMask):
bytes32 messageHash = keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n",
uintToString(bytes(message).length),
message
)
);
Centralized Verification Process
All signatures are verified by Core.sol using validateAndConsumeNonce():
// Service calls Core.sol for verification
Core(coreAddress).validateAndConsumeNonce(
user, // Signer address
senderExecutor, // Who can call via msg.sender
hashPayload, // Service-specific hash
originExecutor, // Who can initiate via tx.origin
nonce, // User's nonce
isAsyncExec, // Execution mode
signature // EIP-191 signature
);
What Core.sol Does:
- Verifies signature matches the signer (EIP-191 recovery)
- Validates nonce (checks status, consumes if valid)
- Checks senderExecutor authorization (if not
address(0), validates msg.sender) - Checks originExecutor authorization (if not
address(0), validates tx.origin) - Optionally delegates to UserValidator (if configured)
Example: Payment Signature
Scenario: Send 0.05 ETH to 0x742d...82d8c with sync execution
Step 1: Generate Hash Payload
bytes32 hashPayload = CoreHashUtils.hashDataForPay(
0x742d7b6b472c8f4bd58e6f9f6c82e8e6e7c82d8c, // receiver
0x0000000000000000000000000000000000000000, // token (ETH)
50000000000000000, // amount (0.05 ETH)
1000000000000000 // priorityFee (0.001 ETH)
);
// Result: 0xa7f3c2d8e9b4f1a6c5d8e7f9b2a3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1
Step 2: Construct Signature Message
1,0x0000000000000000000000000000000000000000,0xa7f3c2d8e9b4f1a6c5d8e7f9b2a3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1,0x0000000000000000000000000000000000000000,42,false
Components:
evvmId:1senderExecutor:0x0000...(anyone can call)hashPayload:0xa7f3...(from Step 1)originExecutor:0x0000...(anyone can initiate)nonce:42isAsyncExec:false(sync)
Step 3: Sign with Wallet User signs the message using MetaMask or another EIP-191 compatible wallet.
Parameter Formatting Rules
String Conversion
- Numbers:
"50000000000000000"(decimal string, no scientific notation) - Addresses:
"0x742c7b6b472c8f4bd58e6f9f6c82e8e6e7c82d8c"(lowercase hex with0x) - Bytes32:
"0xa7f3c2d8e9b4f1a6..."(64-character hex with0x) - Booleans:
"true"or"false"(lowercase) - Strings: As provided (e.g.,
"alice")
Formatting Utilities
Use AdvancedStrings library:
import {AdvancedStrings} from "@evvm/testnet-contracts/library/utils/AdvancedStrings.sol";
AdvancedStrings.uintToString(42); // "42"
AdvancedStrings.addressToString(addr); // "0x742c..."
AdvancedStrings.bytes32ToString(hash); // "0xa7f3..."
EVVM Services Overview
Core.sol
Payment operations:
pay(): Single payment to addresses or usernamesbatchPay(): Multiple payments in one transactiondispersePay(): Split payment to multiple recipientscaPay(): Contract-only payments
NameService
Username management:
- Registration (pre-register → register flow)
- Marketplace (offers, transfers)
- Metadata (custom fields)
- Renewals and cleanup
Staking
Token staking:
- Presale and public staking
- Unstaking with time locks
- Reward distribution
Treasury
Cross-chain operations:
- Bridge transfers (Axelar, LayerZero)
- Multi-chain asset management
P2PSwap
Decentralized exchange:
- Order creation and cancellation
- Order dispatch (fills)
- Fee management
Best Practices
Security
- Never reuse nonces: Each signature must have a unique nonce
- Validate executors carefully:
- Use
address(0)for both executors for public operations - Use specific
senderExecutorto restrict which contract can call - Use specific
originExecutorto restrict which EOA can initiate - Use both for maximum security (specific contract + specific EOA)
- Use
- Async for time-sensitive: Use
isAsyncExec=truefor operations needing specific timing - Hash independence: Executors are not part of hashPayload, only in signature message
Gas Optimization
- Batch operations: Use
batchPay()instead of multiplepay()calls - Sync when possible: Async execution costs more gas
- Reuse hash payloads: Cache
hashPayloadif making multiple signatures
Development
- Use HashUtils libraries: Don't manually construct hashes
- Test signature generation: Verify message format matches expected structure
- Handle nonce management: Track used nonces client-side
- Validate before signing: Check parameters before generating signature
Next Steps
Explore service-specific signature structures:
- Core Payment Signatures - Payment operation signatures
- NameService Signatures - Username and metadata operations
- Staking Signatures - Staking and reward operations
- Treasury Signatures - Cross-chain bridge operations
- P2PSwap Signatures - DEX order operations