Update: main domain fields are now derived by the verifier
Following a suggestion from @Amxx, we opened Update ERC-7964: Derive main domain fields on the verifier by ernestognw · Pull Request #2112 · ethereum/ERCs · GitHub to remove the fields byte from the signature header. The main domain fields are now computed by the verifier.
Problem
Previously, the header carried a fields byte (ERC-5267 bitmap) telling the verifier which domain fields to use when rebuilding the main EIP712Domain. A mismatched value can’t forge anything, since it rebuilds a different domain separator and verification fails. But it gives the signer control over the domain shape, and the verifier has no way to enforce its own binding requirements.
Every verifier already hardcodes which domain fields its chain-specific structs include, since they’re part of the struct hash of the current chain’s operation. For example, an operation nesting EIP712ChainDomain(uint256 chainId,address verifyingContract) includes chainId and verifyingContract. The main domain should contain exactly what’s left, so the fields byte never carried information the verifier didn’t already have.
It also allowed signatures the verifier wouldn’t expect. Consider a verifier whose operations only include chainId and that relies on verifyingContract being in the main domain. A signer could be asked to sign a main domain with only name and version, and the verifier would accept it. That signature is then valid on any contract on that chain sharing the same name, version and typehash.
Change
Verifiers declare the domain fields their chain-specific structs include as a constant ERC-5267 bitmap (chainFields), and the main domain fields are derived as:
bytes1 mainFields = applicationFields & ~chainFields;
Where applicationFields is the fields value returned by eip712Domain() on the application. chainFields MUST include the chainId bit (0x04).
This keeps each domain field in exactly one place (the main domain or each chain-specific struct) and guarantees chainId never ends up in the main domain.
For a standard OpenZeppelin EIP712 application (0x0f) and operations nesting EIP712ChainDomain(chainId, verifyingContract) (0x0c), the main domain is 0x03 (name and version), same as in all the examples.
Breaking changes
- The header is now
magic (10 bytes) | structIndex (2 bytes) | application (20 bytes).
- The magic value is extended to 10 bytes (
0x79647964796479647964), so signatures in the old format are no longer detected rather than misparsed.
isValidCrossChainSignatureNow takes an extra bytes1 chainFields argument.
The OpenZeppelin implementation (Add ERC-7964 crosschain signatures to SignatureChecker by ernestognw · Pull Request #6800 · OpenZeppelin/openzeppelin-contracts · GitHub) will be updated to match.