EIP.tools

EIP.tools

⚠️ DraftStandards Track: ERC

ERC-8213: Wallet Signature and Calldata Digest Display

Defines digest terminology and display requirements for ERC-191 and EIP-712 signed data and transaction calldata in wallets.

Authors
Created2026-03-30
Discussion Linkhttps://ethereum-magicians.org/t/erc-8213-wallet-signature-and-calldata-digest-display/24295
Requires

Markdown

https://raw.githubusercontent.com/ethereum/ERCs/re...
Pull Request#1639PR open

EIP-GPT summary

Contents
AbstractMotivationSpecificationDefinitionsERC-191 DigestEIP-712 DigestDomain HashMessage HashCalldata DigestWallet Display RequirementsERC-191 SigningEIP-712 SigningTransaction SigningRationaleWhy digests instead of human-readable descriptionsWhy exclude chainId from Calldata DigestWhy include length in Calldata DigestWhy prefer EIP-712 Digest over Domain Hash plus Message HashTerminology standardizationBackwards CompatibilityTest CasesERC-191 DigestCalldata DigestReference ImplementationERC-191 Digest (Python)Calldata Digest (Python)Calldata Digest (Solidity)Security ConsiderationsHash collision resistanceDigest verification is not intent verificationTrust model shiftPartial digest comparisonVisually ambiguous message contentDigest does not cover the destination address, or value sentSoftware wallet considerationsQuantum computingCopyright

Abstract

This ERC standardizes the terminology and display of cryptographic digests in wallets for ERC-191 and EIP-712 signed data and for transaction calldata. It defines five terms:

  • ERC-191 Digest
  • EIP-712 Digest
  • Domain Hash
  • Message Hash
  • Calldata Digest

It further specifies that wallets SHOULD display these values to enable independent verification by signers.

Motivation

Recent exchange hacks (Bybit: 1.4B,WazirX:1.4B, WazirX: 200M, Radiant Capital: $50M) demonstrate the need for wallet-based verification independent of potentially compromised websites. Hardware wallets currently display ERC-191 messages or EIP-712 struct data across numerous screens, transaction calldata across numerous screens with hundreds of hex characters, forcing users to either compare raw data character-by-character or blindly trust what they are signing. Since many hardware wallets are air-gapped or intentionally isolated by design, extracting the calldata off the hardware device can be especially challenging, meaning the only way to verify a large transaction is to compare every character - which is infeasible in practice. Many users do not even bother to check calldata, however, security conscious users or users in charge of large treasuries or applications must have a way to verify all the data without getting security fatigue.

There are two distinct problems:

  1. Inconsistent terminology. The digests produced by the ERC-191 and EIP-712 encoding functions have no canonical names. Different wallets and smart contract systems (e.g., Safe's safeTxHash) use ad hoc terms, making cross-wallet verification confusing.

  2. No standard calldata digest. There is no standard way for a wallet to display a compact, verifiable summary of calldata so that a signer can confirm the full payload matches their intent.

By displaying standardized digests, signers gain a verification path: compute the expected digest independently (via a separate device, script, or tool) and compare it against what the wallet displays.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Definitions

ERC-191 Digest

The ERC-191 Digest is the 32-byte keccak256 hash of the ERC-191 signed_data for version 0x45 (personal_sign):

ERC-191 Digest = keccak256("\x19Ethereum Signed Message:\n" || len(message) || message)

where:

  • message is the raw byte sequence being signed; it need not be valid UTF-8. A text message is represented by its UTF-8 encoding, and a message transported as a 0x-prefixed hexadecimal string (as in personal_sign JSON-RPC requests) denotes the decoded bytes, not the hexadecimal characters.
  • len(message) is the number of bytes in message (not the number of characters), expressed as its ASCII base-10 decimal representation with no leading zeros or padding.
  • || denotes byte concatenation.

This is the value that is ultimately signed. Wallets and documentation MUST refer to this value as the "ERC-191 Digest" when displaying it to the user.

Note that EIP-712 typed data is itself signed as ERC-191 version 0x01; its digest is the EIP-712 Digest defined below and MUST NOT be labeled "ERC-191 Digest". This ERC defines no term for ERC-191 version 0x00.

EIP-712 Digest

The EIP-712 Digest is the 32-byte keccak256 hash of the output of the EIP-712 encode function:

EIP-712 Digest = keccak256("\x19\x01" || domainSeparator || hashStruct(message))

This is the value that is ultimately signed. Wallets and documentation MUST refer to this value as the "EIP-712 Digest" when displaying it to the user.

Domain Hash

The Domain Hash is the domainSeparator value, computed as:

Domain Hash = hashStruct(eip712Domain)

where eip712Domain is the EIP-712 domain struct.

Message Hash

The Message Hash is the result of hashing the structured message:

Message Hash = hashStruct(message)

where message is the typed data struct being signed.

Calldata Digest

The Calldata Digest is a 32-byte digest of transaction calldata, computed as:

Calldata Digest = keccak256(uint256(len(calldata)) || calldata)

where:

  • len(calldata) is the byte length of the calldata, encoded as a big-endian uint256 (32 bytes).
  • || denotes byte concatenation.
  • calldata is the raw calldata bytes of the transaction.

The length prefix prevents ambiguity between calldata of different lengths that share a common prefix.

The Calldata Digest intentionally excludes chainId and other transaction envelope fields so that the same calldata produces the same digest regardless of which chain or when the transaction is submitted.

Wallet Display Requirements

ERC-191 Signing

When a wallet prompts a user to sign an ERC-191 version 0x45 (personal_sign) message, the wallet SHOULD display:

  1. The message itself.
  2. Additionally, the ERC-191 Digest (labeled exactly as "ERC-191 Digest") if the wallet does not render the complete message on a single screen (including when the message is truncated, elided, or requires scrolling), or if the message contains bytes or characters that the wallet cannot render faithfully.

The digest MUST be computed over the exact bytes being signed and MUST be displayed as a 0x-prefixed hexadecimal string.

EIP-712 Signing

When a wallet prompts a user to sign EIP-712 typed data, the wallet SHOULD display one of the following combinations:

  1. The EIP-712 Digest alone (RECOMMENDED, as it requires the signer to verify only one hash).
  2. The Domain Hash and Message Hash together.
  3. All three: EIP-712 Digest, Domain Hash, and Message Hash.

A wallet MUST NOT display only the Domain Hash or only the Message Hash in isolation, as neither alone is sufficient to uniquely identify the signed data.

All displayed values MUST be rendered as 0x-prefixed hexadecimal strings and labeled using the exact terms defined in this ERC.

Transaction Signing

When a wallet prompts a user to sign a transaction containing non-empty calldata, the wallet SHOULD display:

  1. The Calldata Digest, labeled exactly as "Calldata Digest".

The value MUST be displayed as a 0x-prefixed hexadecimal string.

Rationale

Why digests instead of human-readable descriptions

Human-readable transaction descriptions (e.g., clear-signing formats like ERC-7730) serve a complementary but different purpose. They target average users who need plain-language explanations. This ERC targets security-conscious signers who want a compact, independently verifiable fingerprint of what they are signing.

Digest-based verification has a simpler trust model: the signer only needs to trust their hash function and their independent computation, not a third-party description registry or internet-fetched metadata.

These approaches are not mutually exclusive. A wallet MAY implement both human-readable descriptions and digest display.

Why exclude chainId from Calldata Digest

The same contract call (e.g., a token transfer) should produce the same Calldata Digest on any chain. This enables users to verify calldata using tools that are not chain-aware, and allows documentation and verification guides to reference a single digest for a given operation regardless of deployment chain.

Chain-specific replay protection is already handled by the transaction signature scheme (EIP-155) and is orthogonal to calldata verification.

Why include length in Calldata Digest

Prepending the length as a fixed-width uint256 ensures that different calldata inputs that share a prefix cannot produce the same digest. Without the length prefix, truncated or extended calldata could create ambiguity.

Why prefer EIP-712 Digest over Domain Hash plus Message Hash

Verifying one hash is less error-prone than verifying two. The EIP-712 Digest uniquely identifies the combination of domain and message, so a signer who confirms the EIP-712 Digest has fully verified what they are signing. Displaying the Domain Hash and Message Hash separately is acceptable when tooling makes it easier to compute these intermediate values, but the EIP-712 Digest alone is sufficient and preferred.

Terminology standardization

The terms "ERC-191 Digest" and "EIP-712 Digest" are chosen because:

  • Each directly references the standard that defines the encoding.
  • "Digest" is the conventional cryptographic term for the output of a hash function.
  • They avoid application-specific names (e.g., safeTxHash) that obscure the underlying operation.

Backwards Compatibility

This ERC introduces no changes to Ethereum protocol, transaction format, or smart contract interfaces. It is purely additive guidance for wallet implementations. Wallets that do not implement this ERC continue to function without issue.

Test Cases

ERC-191 Digest

Given the message "hello" (5 bytes), the signed_data preimage is:

"\x19Ethereum Signed Message:\n5hello"
= 0x19457468657265756d205369676e6564204d6573736167653a0a3568656c6c6f

The ERC-191 Digest is:

ERC-191 Digest = 0x50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750

Given the message "héllo" (5 characters but 6 bytes in UTF-8: 0x68c3a96c6c6f), the length field is the ASCII byte "6", not "5", and:

ERC-191 Digest = 0x42fd95dd8ed22d7d934d6ffff71ed0b730835b0de4bb3532a271568c8760c8d2

Calldata Digest

Given calldata for an ERC-20 transfer(address,uint256) call:

calldata = 0xa9059cbb
  0000000000000000000000004675c7e5baafbffbca748158becba61ef3b0a263
  0000000000000000000000000000000000000000000000000de0b6b3a7640000

The calldata is 68 bytes. The length prefix is:

uint256(68) = 0x0000000000000000000000000000000000000000000000000000000000000044

The Calldata Digest is:

Calldata Digest = 0x812cee5d9cc7461c04bbcd7b70af9c28b243ac5d74d3453b008b93b7dac69985

Reference Implementation

ERC-191 Digest (Python)

from eth_hash.auto import keccak

def compute_erc191_digest(message: bytes) -> str:
    """Compute the ERC-191 Digest (version 0x45) per this ERC."""
    prefix = b"\x19Ethereum Signed Message:\n" + str(len(message)).encode("ascii")
    return "0x" + keccak(prefix + message).hex()

Calldata Digest (Python)

from eth_hash.auto import keccak

def compute_calldata_digest(calldata: bytes) -> str:
    """Compute the Calldata Digest per this ERC."""
    if isinstance(calldata, str) and calldata.startswith("0x"):
        calldata = bytes.fromhex(calldata[2:])
    length_bytes = len(calldata).to_bytes(32, byteorder="big")
    return "0x" + keccak(length_bytes + calldata).hex()

Calldata Digest (Solidity)

function calldataDigest(bytes memory data) public pure returns (bytes32) {
    return keccak256(abi.encodePacked(uint256(data.length), data));
}

Security Considerations

Hash collision resistance

This ERC relies on keccak256 being collision-resistant. If a preimage or collision attack against keccak256 becomes practical, the security guarantees of digest-based verification are void. This is the same assumption underlying EIP-712 and Ethereum's account and storage model.

Digest verification is not intent verification

Displaying a digest confirms what is being signed, not why. A user who verifies a Calldata Digest confirms that the bytes match their expectation, but must still understand what those bytes do. This ERC is complementary to, not a replacement for, human-readable signing approaches like ERC-7730.

Trust model shift

Without digest display, users trust the website or dApp interface to show them correct information. With digest display, users shift trust to their independent computation tool and their ability to compare hex strings. This is a net improvement for users who can perform independent verification, but does not help users who cannot.

Partial digest comparison

Signers may be tempted to compare only the first or last few characters of a displayed digest rather than the full string. An attacker who can manipulate calldata could grind for inputs that produce a matching prefix or suffix. Signers must compare the entire digest string to achieve the security guarantees of this ERC.

Visually ambiguous message content

ERC-191 messages may contain confusable characters (e.g., Cyrillic "а" U+0430 in place of Latin "a" U+0061), zero-width characters, or bidirectional override characters that render identically to, or misleadingly differently from, the bytes actually signed. Because such characters are displayable, a wallet that shows the ERC-191 Digest only for unrenderable content will omit it in exactly the cases where visual comparison of the message text is least reliable. Wallets SHOULD display the ERC-191 Digest whenever a message contains such characters, and SHOULD indicate their presence.

Digest does not cover the destination address, or value sent

The Calldata Digest covers only the calldata bytes, not the to address or value of the transaction. Identical calldata sent to a different (potentially malicious) contract produces the same Calldata Digest, and the same calldata with an unexpected ETH value attached could also be exploited. Signers must independently verify the destination address and transaction value in addition to the Calldata Digest.

Software wallet considerations

Software wallets run on the same device as the potentially compromised dApp. Digest display in a software wallet provides weaker guarantees than in a hardware wallet, since malware could manipulate both the dApp and the wallet display. Users should prefer hardware wallets for high-value signing operations.

Quantum computing

This ERC's digest construction is hash-function-agnostic in principle. The keccak256 hash function retains approximately 128-bit security against quantum preimage attacks (via Grover's algorithm), which is considered adequate by current cryptographic standards. Should Ethereum migrate to a post-quantum hash function, the Calldata Digest definition can be updated by substituting the hash, while the ERC-191 Digest and EIP-712 Digest definitions would follow whatever their defining standards adopt. The display requirements and terminology defined here remain applicable regardless of the underlying hash or signature scheme. The greater quantum risk lies in the ECDSA signature scheme used by Ethereum, which is outside the scope of this ERC.

Copyright and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth