--- eip: 8213 title: Wallet Signature and Calldata Digest Display description: Defines digest terminology and display requirements for ERC-191 and EIP-712 signed data and transaction calldata in wallets. author: Patrick Collins (@PatrickAlphaC) discussions-to: https://ethereum-magicians.org/t/erc-8213-wallet-signature-and-calldata-digest-display/24295 status: Draft type: Standards Track category: ERC created: 2026-03-30 requires: 191, 712 --- ## Abstract This ERC standardizes the terminology and display of cryptographic digests in wallets for [ERC-191](./eip-191.md) and [EIP-712](./eip-712.md) 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: $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](./eip-191.md) `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](./eip-712.md) 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](./eip-712.md) `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](./eip-712.md) 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](./eip-191.md) 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](./eip-712.md) 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](./eip-7730.md)) 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](./eip-155.md)) 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](./eip-20.md) `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) ```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) ```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) ```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 Copyright and related rights waived via [CC0](../LICENSE.md).