ERC-5267 EIP-712 Domain Retrieval Standard (Retrieval of EIP-712 domain)

Final · Signatures & auth · created 2022-07-14

Defines a function through which a contract discloses which fields and values make up its EIP-712 signing domain. Apps and wallets can read it to build the domain separator themselves.

Why it is needed

EIP-712 is a way of signing structured messages. To prevent replay attacks, where a signature is reused elsewhere, and to reduce phishing, it uses a domain separator that binds a signature to a domain such as a specific contract. The domain consists of a name, version, chain ID, verifying contract address, and optional fields such as a salt, but EIP-712 itself does not define how a contract can announce which fields it uses and with what values. As a result, apps had to fit the domain separately for each contract, and generic integration was impossible. The original text points out that even EIP-2612 (permit), known to greatly improve user experience, has not been widely adopted for this reason. The DOMAIN_SEPARATOR function of EIP-2612 returns only a hash value, so it cannot be used to show the user the domain contents and verify them before signing. This standard fills the gap by defining an interface through which an app can read a contract's domain definition.

What to implement

In depth

The eip712Domain function declaration and return rules

A contract that follows the standard MUST define the eip712Domain function exactly in the form declared in the original text. The return values are the seven items fields, name, version, chainId, verifyingContract, salt and extensions. Even when some fields are unused, the function must return every specified value. The shape must always be the same so that clients can decode the returned data correctly. This return value must describe the domain separator the contract actually uses when it verifies EIP-712 signatures, and it conveys both the shape of the EIP712Domain struct (which optional fields and extensions are present) and the value of each field.

How to read the fields bitmap

fields is a bitmap held in a single bytes1. If bit i is 1, domain field i is present, where i runs from 0 to 4. Bits are read from least significant to most significant, and the field order is the one EIP-712 defines, which is also the order listed in the function's return type. So bit 0 is name, 1 is version, 2 is chainId, 3 is verifyingContract, and 4 is salt. The reference implementation uses only name, chainId and verifyingContract, so fields is 01101 in binary and 0d in hexadecimal. The JavaScript reference implementation interprets it by looping over a fieldNames array and removing from the domain object every field for which fields & (1 << i) is 0.

Meaning of name, version, chainId, verifyingContract and salt

Each of these five values is the value of that field in EIP712Domain when fields says the field is present. If the field is absent, its value is treated as unspecified and ignored. The meaning of each field is exactly as EIP-712 defines it. If chainId is used, its value SHOULD change to follow the EIP-155 chain ID of the actual chain. The reference implementation puts block.chainid in chainId and address(this) in verifyingContract, so the contract reports the chain it is deployed on and its own address as they are. If this contract is at the address 0x0000000000000000000000000000000000000001 on Ethereum mainnet, the domain it describes consists of three fields: name is Example, chainId is 1, and verifyingContract is that address.

The extensions list

extensions is a list of EIP numbers held in a uint256 array. Each number MUST refer to an EIP that adds a new domain field to EIP-712, and that EIP must define how to obtain the field value and, if needed, the conditions for including it. The fields bitmap does not affect whether an extension field is included. Extensions are expressed as EIP numbers because EIP-712 specifies that new fields should be proposed through the EIP process. The reference implementation shows an example that assumes a hypothetical EIP-XYZ defines a subdomain field of type bytes32 and a getSubdomain() function; if that number is in extensions, it calls getSubdomain() and puts the result into the domain object. If an unknown number arrives, it raises an error. The EIP712Domain struct type must also gain the subdomain field in that case, but that part is outside the scope of the reference implementation.

Domain changes and the EIP712DomainChanged event

The return value of eip712Domain, that is, the contract's EIP-712 domain, MAY change during the lifetime of the contract. However, it SHOULD NOT change frequently. If the chainId field is used, it must change to reflect the actual chain's EIP-155 ID. To signal that the domain may have changed, the contract MAY emit an EIP712DomainChanged event that takes no arguments. An app that receives the event can call eip712Domain again to read the latest domain.

Why the DOMAIN_SEPARATOR hash alone is not enough

EIP-2612 (permit) specifies a DOMAIN_SEPARATOR function that returns a bytes32 value. This value is the result of hashStruct(eip712Domain), that is, the actual domain separator. However, the RPC methods defined by EIP-712 take an object that describes the domain, not a hash. This is not a flaw in the RPC design. It is part of a security premise that the domain must be verified and shown to the user during signing. A hash is an opaque value whose contents cannot be known, so it cannot do this. This standard fills that gap in both EIP-712 and EIP-2612.

Handling existing contracts that do not implement this standard

This standard is an optional extension of EIP-712, so it causes no backward compatibility problems. Upgradeable contracts that use EIP-712 signatures MAY be upgraded to implement this standard. On the other hand, some contracts are immutable and cannot be upgraded, so user agents and apps that use this standard SHOULD support such contracts as well. The simplest method is to hardcode common domains keyed by contract address and chain ID. There is also a more general method. It builds candidate domains from known information according to a few common patterns, then picks the one whose hash matches the value of the contract's DOMAIN_SEPARATOR or domainSeparator function.

Things to watch out for

Original ERC · ← ERCs