Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a9ad9c8
Add ERC-XXXX: Composite EIP-712 Signatures
sola92 Mar 25, 2025
e2e4d3f
lints
sola92 Mar 25, 2025
0022d4d
updates
sola92 Mar 25, 2025
95ce3fc
Simplify signature by removing CompositeMessage type
sola92 Mar 27, 2025
dea21b4
fix lints
sola92 Mar 28, 2025
f5d16c3
fixes
sola92 Mar 28, 2025
0b5df96
updates
sola92 Mar 28, 2025
11d12e7
verify backwards compatibility
sola92 Mar 28, 2025
3a98549
eth_signCompositeTypedData -> eth_signTypedData_v5
sola92 Mar 28, 2025
1a095e5
fix lints
sola92 Mar 28, 2025
4ed988c
fix lints
sola92 Mar 28, 2025
6e674dd
fix lints
sola92 Mar 28, 2025
cf19e4e
fix lints
sola92 Mar 28, 2025
22828ba
fix lints
sola92 Mar 28, 2025
991df70
fix lints
sola92 Mar 28, 2025
12a62b3
fix lints
sola92 Mar 28, 2025
0294163
updates
sola92 Mar 28, 2025
7d14469
updates
sola92 Mar 28, 2025
fefafd4
updates
sola92 Mar 28, 2025
9e7b14f
updates
sola92 Mar 28, 2025
c611106
updates
sola92 Mar 28, 2025
54b260a
updates
sola92 Mar 28, 2025
3882a89
updates
sola92 Mar 28, 2025
490386c
updates
sola92 Mar 28, 2025
e125758
updates
sola92 Mar 28, 2025
b0cb002
updates
sola92 Mar 28, 2025
11b00c6
updates
sola92 Mar 28, 2025
473672a
updates
sola92 Mar 28, 2025
0705223
updates
sola92 Mar 28, 2025
47a76f8
updates
sola92 Mar 31, 2025
f23f0a4
- Fix typos
sola92 Apr 12, 2025
e306df0
updates
sola92 Apr 12, 2025
35ad9fc
updates
sola92 Apr 12, 2025
171c1cc
updates
sola92 Apr 18, 2025
bf41d3e
updates
sola92 May 4, 2025
bd08bc7
updates
sola92 May 4, 2025
5beb332
updates
sola92 May 4, 2025
54bae6a
updates
sola92 May 5, 2025
5580d02
updates
sola92 May 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
335 changes: 335 additions & 0 deletions ERCS/erc-7920.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
---
eip: 7920
title: Composite EIP-712 Signatures
description: A scheme for signing multiple typed-data messages with a single signature
author: Sola Ogunsakin (@sola92)
discussions-to: https://ethereum-magicians.org/t/composite-eip-712-signatures/23266
status: Draft
type: Standards Track
category: ERC
created: 2025-03-20
requires: 20, 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](./eip-712.md) 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](./eip-2612.md), 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](./eip-20.md) 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:

```solidity
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

```typescript
{
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:

```json
{
"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:

```JavaScript
{
"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](../assets/eip-7920/src/eth_signTypedData_v5.ts).

### Verifier

Solidity implementation of a onchain verifier can be found the [assets directory](../assets/eip-7920/contracts/ExampleVerifier.sol).

### Merkle

Reference Merkle tree can be found in the [assets directory](../assets/eip-7920/src/merkle.ts).

## 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

Copyright and related rights waived via [CC0](../LICENSE.md).
Loading
Loading