EIP-712 signs a typed message within a domain. The wallet receives field names, types, and values, then encodes two structures: the message and its domain. Their hashes become the inputs to one final signing digest.
Give bytes a type
Start with a message small enough to inspect:
SOLIDITY
struct Mail { address from; address to; string contents;}
The schema becomes a canonical string. Field order is preserved; there are no spaces around commas.
Mail(address from,address to,string contents)
Hashing this string produces typeHash. The type name, field names, and field types all participate. Renaming contents changes the type hash even if the value stays the same.
For a structure s, EIP-712 defines:
hashStruct(s)=keccak256(typeHash∥encodeData(s))
Every encoded field occupies 32 bytes. An address is left-padded with zeroes. Integers use a 256-bit word, with sign extension for signed values. A string contributes the Keccak-256 hash of its UTF-8 bytes.
For this Mail, the preimage is four words:
Word
Source
Encoding · 32 bytes each
0
Mail schema
typeHash
1
from
12 zero bytes + 20-byte address
2
to
12 zero bytes + 20-byte address
3
contents
Keccak-256 of UTF-8 bytes
This encoding does not use RLP. In Solidity, the four fixed-size values can be assembled with abi.encode.
Nested values
A nested struct contributes its own hashStruct. A dynamic bytes field contributes a hash of its bytes. An array contributes a hash of its encoded elements concatenated in order; each element follows the same field-encoding rules. An empty array therefore contributes the hash of empty bytes.
Referenced struct definitions are appended to the primary type definition, sorted by type name. For a Mail containing Person values, the canonical type might be:
Order
Type definition
Primary
Mail(Person from,Person to,string contents)
Referenced
Person(string name,address wallet)
Concatenate these definitions with no separator before hashing.
This is why hashing a JSON string is not equivalent. JSON is the transport used by many wallet APIs; the digest is defined by the typed encoding, not by JSON whitespace or object-key order.
Bind the domain
Two contracts can use exactly the same Mail schema. Their signatures should remain distinguishable.
The domain supplies that context. Common fields are name, version, chainId, and verifyingContract. The optional salt field has type bytes32. A protocol includes the fields it needs, then hashes the resulting EIP712Domain struct using the same rules.
Its hashStruct is the domain separator.
This domain is a protocol namespace. It is not a browser origin and does not automatically prove that the requesting website is trustworthy.
Hash that preimage once to obtain the signing digest. 0x19 and version 0x01 connect the format to EIP-191 .
The lab shows every 32-byte encoded word and the complete 66-byte preimage. Purple tracks the domain; cyan tracks the message. Changing chainId changes the domain hash while leaving the message hash intact. Changing the contents does the reverse. Either change produces a different final digest.
Sign the structure
This ethers 6.17.0 example uses the lab's schema and domain. All addresses are illustrative. The random wallet supplies from, so this executable example also checks the claimed sender.
With ethers, pass the application types to signTypedData; it builds EIP712Domain from domain. A raw eth_signTypedData_v4 request also needs primaryType and the domain type in its JSON payload. TypedDataEncoder.getPayload(domain, types, message) produces that representation.
Do not pass the typed-data digest to signMessage. That adds the personal-message envelope and produces a different signature.
Verification has state
The verifier must derive the expected domain from its own context. Accepting an arbitrary domain supplied with a signature would let the requester choose the boundary being checked.
Domain separation keeps distinct contexts apart. It does not stop the same message from being used twice in the same context. A spending protocol usually adds a nonce or order identifier, an expiry, and state that records consumption or cancellation.
Nor does a typed message guarantee a safe action. A wallet can display spender and value accurately while the requested spender remains malicious. Field visibility makes review possible; application semantics decide what the signature can do.
EIP-2612 makes this concrete: an EIP-712 message changes token allowance only after the contract checks the signer, nonce, and deadline.