Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7920: Composite EIP-712 Signatures

A scheme for signing multiple typed-data messages with a single signature

Authors Sola Ogunsakin (@sola92)
Created 2025-03-20
Discussion Link https://ethereum-magicians.org/t/composite-eip-712-signatures/23266
Requires EIP-20, EIP-712

Abstract

This ERC provides a standard for signing multiple typed-data messages with a single signature by encoding them into a Merkle tree. This allows components to independently verify messages, without requiring full knowledge of the others. It provides a significant UX improvement by reducing the number of signature prompts to one, while preserving the security and flexibility of the EIP-712 standard.

This ERC also gives applications the flexibility to verify messages in isolation, or in aggregate. This opens up new verification modalities: for e.g, an application can require that message (x) is only valid when signed in combination message (y).

Motivation

As the ecosystem moves towards ETH-less transactions, users are often required to sign multiple off-chain messages in quick succession. Typically, a first signature is needed for a precise spend allowance (via Permit2, ERC-2612, etc.), followed by subsequent messages to direct the use of funds. This creates a frictional user experience as each signature requires a separate wallet interaction and creates confusion about what, in aggregate, is being approved.

Current solutions have significant drawbacks:

  • Pre-approving ERC-20 allowance: spend creates security vulnerabilities
  • Merging multiple messages into a single message: prevents independent verifiability. Each message cannot be verified without knowledge of the entire batch
  • Separate signature requests: creates friction in the user experience

This ERC has the following objectives:

Single Signature

A single signature should cover multiple messages

Isolated Verification

Messages should be independently verifiable without knowledge of others

Human-readable

Readability benefits of EIP-712 should be preserved. Giving wallets and users insight into what is being signed.

Specification

Overview

The composite signature scheme uses a Merkle tree to hash multiple typed-data data messages together under a single root. The user signs only the Merkle root. The process is described below.

Generating a Composite Signature

  1. For a set of messages [m₁, m₂, ..., mₙ], encode each using EIP-712’s encode and compute its hash:

    hashₙ = keccak256(encode(mₙ))
    
  2. Use these message hashes as leaf nodes in a Merkle tree and compute a merkleRoot

  3. Sign the merkle root.

    signature = sign(merkleRoot)
    

Verification Process

To verify that an individual message mₓ was included in a composite signature:

  1. Verify the signature on the merkleRoot:

    recoveredSigner = ecrecover(merkleRoot, signature)
    isValidSignature = (recoveredSigner == expectedSigner)
    
  2. Compute the leaf node for message mₓ and verify its path to the Merkle root, using the proof:

    leaf = keccak256(encode(mₓ))
    isValidProof = _verifyMerkleProof(leaf, merkleProof, merkleRoot)
    

Where _verifyMerkleProof() is defined as:

function _verifyMerkleProof(
   bytes32 leaf,
   bytes32[] calldata proof,
   bytes32 merkleRoot
) internal pure returns (bool) {
   bytes32 computedRoot = leaf;
   for (uint256 i = 0; i < proof.length; ++i) {
       if (computedRoot < proof[i]) {
           computedRoot = keccak256(abi.encode(computedRoot, proof[i]));
       } else {
           computedRoot = keccak256(abi.encode(proof[i], computedRoot));
       }
   }

   return computedRoot == merkleRoot;
}

The message is verified if and only if (1) and (2) succeed.

isVerified = isValidSignature && isValidProof

Specification of eth_signTypedData_v5 JSON RPC method.

This ERC adds a new method eth_signTypedData_v5 to Ethereum JSON-RPC. This method allows signing multiple typed data messages with a single signature using the specification described above. The signing account must be prior unlocked.

This method returns: the signature, merkle root, and an array of proofs (each corresponding to an input message).

Parameters

  1. Address - Signing account
  2. TypedData | TypedDataArray - A single TypedData object or Array of TypedData objects from EIP-712.
Returns
{
  signature: `0x${string}`; // Hex encoded 65 byte signature (same format as eth_sign)
  merkleRoot: `0x${string}`; // 32 byte Merkle root as hex string
  proofs: Array<Array<`0x${string}`>>; // Array of Merkle proofs (one for each input message)
}
Example

Request:

{
  "jsonrpc": "2.0",
  "method": "eth_signTypedData_v5",
  "params": [
    "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
    [
      {
        "types": {
          "EIP712Domain": [
            {
              "name": "name",
              "type": "string"
            },
            {
              "name": "version",
              "type": "string"
            },
            {
              "name": "chainId",
              "type": "uint256"
            },
            {
              "name": "verifyingContract",
              "type": "address"
            }
          ],
          "Person": [
            {
              "name": "name",
              "type": "string"
            },
            {
              "name": "wallet",
              "type": "address"
            }
          ],
          "Mail": [
            {
              "name": "from",
              "type": "Person"
            },
            {
              "name": "to",
              "type": "Person"
            },
            {
              "name": "contents",
              "type": "string"
            }
          ]
        },
        "primaryType": "Mail",
        "domain": {
          "name": "Ether Mail",
          "version": "1",
          "chainId": 1,
          "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
        },
        "message": {
          "from": {
            "name": "Cow",
            "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
          },
          "to": {
            "name": "Bob",
            "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
          },
          "contents": "Hello, Bob!"
        }
      },
      {
        "types": {
          "EIP712Domain": [
            {
              "name": "name",
              "type": "string"
            },
            {
              "name": "version",
              "type": "string"
            },
            {
              "name": "chainId",
              "type": "uint256"
            },
            {
              "name": "verifyingContract",
              "type": "address"
            }
          ],
          "Transfer": [
            {
              "name": "amount",
              "type": "uint256"
            },
            {
              "name": "recipient",
              "type": "address"
            }
          ]
        },
        "primaryType": "Transfer",
        "domain": {
          "name": "Ether Mail",
          "version": "1",
          "chainId": 1,
          "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
        },
        "message": {
          "amount": "1000000000000000000",
          "recipient": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
        }
      }
    ]
  ],
  "id": 1
}

Result:

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "signature": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c",
    "merkleRoot": "0x7de103665e21d6c9d9f82ae59675443bd895ed42b571c7f952c2fdc1a5b6e8d2",
    "proofs": [
      ["0x4bdbac3830d492ac3f4b0ef674786940fb33481b32392e88edafd45d507429f2"],
      ["0x95be87f8abefcddc8116061a06b18906f32298a4644882d06baff852164858c6"]
    ]
  }
}

Rationale

The choice of using a Merkle tree to bundle messages provides the following additional benefits:

Efficient verification on-chain

_verifyMerkleProof has a runtime of O(log2(N)) where N is the number of messages that were signed.

Flexible Verification Modes

Applications can require combination of messages be signed together to enhance security.

N=1 backwards compatibility

Merkle signature for single message bundles are equal to eth_signTypedData_v4. Requiring no onchain changes.

Backwards Compatibility

When the number of message is one, eth_signTypedData_v5 produces the same signature as eth_signTypedData_v4 since merkleRoot == keccak256(encode(message)). This allows eth_signTypedData_v5 to be a drop-in replacement for eth_signTypedData_v4 with no changes to on-chain verification.

Reference Implementation

eth_signTypedData_v5

Reference implementation of eth_signTypedData_v5 can be found the assets directory.

Verifier

Solidity implementation of a onchain verifier can be found the assets directory.

Merkle

Reference Merkle tree can be found in the assets directory.

Security Considerations

Replay Protection

This ERC focuses on generating composite messages and verifying their signatures. It does not contain mechanisms to prevent replays. Developers must ensure their applications can handle receiving the same message twice.

Partial Message Verification

During verification, care must be taken to ensure that both of these checks pass:

  1. EIP-712 signature on the Merkle root is valid
  2. Merkle proof is valid against the root

User Understanding

Wallets must communicate to users that they are signing multiple messages at once. Wallets must display of all message types before signing.

To ensure batch signature requests are digestible, it is recommended to limit the maximum number of messages to 10.

Merkle Tree Construction

Merkle tree should be constructed in a consistent manner.

  1. The hashing function must be keccak256
  2. To ensure predictable/consistent proof sizes, implementations must pad leaves with zero hashes to reach next power of two to ensure balance. Let n be the number of messages. Before constructing the tree, compute the smallest k such that 2^(k-1) < n ≤ 2^k. Insert zero hashes into the list of messages until list of messages is equal to 2^k.
  3. To ensure an implicit verification path, pairs must be sorted lexicographically before constructing parent hash.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Sola Ogunsakin (@sola92), "ERC-7920: Composite EIP-712 Signatures [DRAFT]," Ethereum Improvement Proposals, no. 7920, March 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7920.