For educational purposes only; not investment advice. Investing may result in loss.
Direct answer
EIP-712 standardizes how Ethereum applications describe, hash, and request signatures over typed structured data. A request contains types, primaryType, domain, and message; its digest is keccak256("\x19\x01" || domainSeparator || hashStruct(message)). This makes encoding deterministic and lets a capable wallet present fields more clearly than an opaque hash.
It does not make the message truthful, harmless, revocable, or replay-safe. The application must bind the authority to the correct chain and verifier, define every field unambiguously, enforce nonces and time limits, validate the right signer, and constrain what execution can do. A valid signature proves approval of an exact digest under a verification rule; it does not prove the signer’s identity, informed intent, or the safety of the website or contract.
Completing this review does not prove an asset, transaction, or system is safe.
How it works
1. Identify the action and verification path
Determine whether the request authorizes login, an order, a vote, a token allowance, a transfer, a relayed call, or another action. Find the code that reconstructs the digest and consumes the signature. For an externally owned account, verification commonly recovers an address from an ECDSA signature; for a contract account, the application may need ERC-1271 isValidSignature(hash, signature) and its 0x1626ba7e success value.
2. Pin the domain
Inspect the exact EIP712Domain type and values. Standard fields are name, version, chainId, verifyingContract, and salt, but only included fields are hashed. Confirm the active chain, deployed code and intended verifier independently; a familiar name, token symbol, proxy label, or checksum address is not enough. ERC-5267 eip712Domain() can expose a contract’s domain, but support is optional and proxy or upgrade behavior still needs review.
3. Reconstruct the type graph
Start from primaryType, preserve member order, and recursively collect referenced structs. encodeType appends referenced struct definitions sorted by type name. EIP-712 supports fixed-width integers, address, bool, bytes1 through bytes32, dynamic bytes and string, arrays, and structs; aliases such as uint and int, fixed-point types, and cyclic values are not defined by the standard.
4. Decode every value and unit
Match each message value to its declared type and application meaning. Verify full addresses, raw integer units, signs, array order, recipients, spenders, assets, amounts, fees, limits, destinations, calldata hashes, and human-readable strings. Dynamic bytes and string values are represented in encodeData by the Keccak-256 hash of their contents; arrays hash the concatenated element encodings, and nested structs use their own hashStruct.
5. Recompute the digest independently
Compute typeHash = keccak256(encodeType(primaryType)), then hashStruct(message) = keccak256(typeHash || encodeData(message)). Compute the domain separator the same way and combine it with the ERC-191 version bytes 0x19 0x01. Compare the resulting digest across the frontend, signing library, verifier contract, and an independent implementation; JSON appearance alone does not prove identical typed encoding.
6. Audit replay, timing, and execution controls
EIP-712 itself includes no replay protection. Confirm that the verifier checks the intended signer, consumes or invalidates the correct nonce, enforces deadline or validity windows, binds all security-critical execution parameters, and produces the same intended result if a relayer or front-runner submits first. Domain separation prevents only collisions across the domains actually encoded; missing or wrong domain fields can leave cross-contract or cross-chain reuse paths.
7. Sign minimally and reconcile the result
Reject hidden fields, unexplained types, unlimited values, distant deadlines, unknown contracts, mismatched chain IDs, blind-signing screens, or incomplete execution context. Preserve the exact typed-data JSON and digest, use a limited-purpose account when possible, and inspect the submitted transaction, receipt, events, balances, allowances, nonces, order state, and finality. Disconnecting a site does not revoke a usable signature or authority already created.
Worked examples
Example 1: Type and digest construction
For Order(address maker,address token,uint256 amount,uint256 nonce,uint256 deadline), typeHash is the Keccak-256 hash of that exact string, including field order. The message hash is keccak256(typeHash || maker || token || amount || nonce || deadline), with each encoded member occupying 32 bytes. The final digest adds 0x1901, the domain separator, and this message hash; changing amount from 250000000 to 250000001 changes the digest and invalidates the old signature.
Example 2: Units and deadline
A six-decimal token amount of 250 USDC is encoded as raw 250000000, not 250. If the current timestamp is 1727000000 and the deadline is 1727000900, the signing window is 900 seconds = 15 minutes. The wallet’s decimal rendering and local clock are aids only; the verifier uses the raw integer and its chosen on-chain time rule.
Example 3: Replay control
An order carries nonce 41 and maximum fill 5 ETH. After a verifier marks nonce 41 consumed, a second submission must fail even if the signature is otherwise valid. If the contract neither consumes the nonce nor makes execution idempotent, the same signature can authorize another 5 ETH; the domain separator alone does not stop that replay.
Example 4: Contract-wallet validity
A 2-of-3 contract wallet approves a digest while signers A, B, and C are configured. Signatures from A and B may make ERC-1271 return 0x1626ba7e today. After a module upgrade replaces B with D, the same signature bytes may become invalid because ERC-1271 validity can depend on current contract state, policy, time, and external calls; address recovery alone cannot decide contract-account validity.
Risks
- Wrong or omitted
chainId - Counterfeit or unexpected
verifyingContract - Misleading domain
nameorversion - Proxy implementation or domain changing after an upgrade
- Wrong
primaryTypeor shadow type with similar labels - Member-order, dependency-order, or encoder mismatch
- Address truncation, substitution, or deceptive labeling
- Token decimal or signed-versus-unsigned integer error
- Hidden array entry, nested struct, or arbitrary
bytespayload - Unlimited amount, broad scope, or attacker-controlled recipient
- Missing, stale, shared, or incorrectly consumed nonce
- Missing, distant, overflowed, or ambiguously interpreted deadline
- Cross-chain, cross-contract, cross-account, or cross-action replay
- Relayer withholding, censorship, front-running, or execution redirection
- Signature malleability or permissive ECDSA recovery
- ERC-1271 signer, module, threshold, state, or code change
- Wallet rendering, blind signing, or unsupported-type failure
- Frontend JSON differing from the verifier’s digest
- Revocation or cancellation losing an ordering race
- Receipt, state change, or finality mistaken for a signing prompt outcome
Common misconceptions
Myth 1: EIP-712 signatures are transactions
They are off-chain signed messages. A relayer or another actor may later submit them to a contract, and the resulting transaction can consume gas and change state without being sent by the signer.
Myth 2: Structured display means the request is safe
Typed fields improve inspectability, but malicious schemas, values, contracts, labels, hidden nesting, and incomplete wallet rendering can still mislead the signer.
Myth 3: The domain separator prevents every replay
It separates only the encoded domain. Same-domain replay still needs nonce, deadline, cancellation, fill accounting, or idempotence, and omitted domain fields provide no boundary.
Myth 4: Recovering the expected address proves authorization
Recovery proves an EOA signature over a digest. It does not validate application semantics, and contract accounts require their ERC-1271 policy rather than ordinary address recovery.
Myth 5: Closing the page or disconnecting the wallet cancels the signature
A copied signature remains usable until the verifier’s nonce, deadline, cancellation, state, or policy makes it invalid. Confirm the relevant on-chain state instead of relying on session status.
Related topics
Sources
- EIP-712: Typed structured data hashing and signing - Ethereum Improvement Proposals (accessed: 2026-08-19)
- ERC-191: Signed Data Standard - Ethereum Improvement Proposals (accessed: 2026-08-19)
- ERC-1271: Standard Signature Validation Method for Contracts - Ethereum Improvement Proposals (accessed: 2026-08-19)
- ERC-2612: Permit Extension for EIP-20 Signed Approvals - Ethereum Improvement Proposals (accessed: 2026-08-19)
- ERC-5267: Retrieval of EIP-712 domain - Ethereum Improvement Proposals (accessed: 2026-08-19)
- EIP-2: Homestead Hard-fork Changes - Ethereum Improvement Proposals (accessed: 2026-08-19)
- Contract ABI Specification - Solidity Documentation (accessed: 2026-08-19)
- ERC-7730: Structured Data Clear Signing Format - Ethereum Improvement Proposals (accessed: 2026-08-19)