📖 This EIP is in the review stage. It is subject to changes and feedback is appreciated.

EIP-5267: Retrieval of EIP-712 domain Source

A way to describe and retrieve an EIP-712 domain to securely integrate EIP-712 signatures.

AuthorFrancisco Giordano
Discussions-Tohttps://ethereum-magicians.org/t/eip-5267-retrieval-of-eip-712-domain/9951
StatusReview
TypeStandards Track
CategoryERC
Created2022-07-14
Requires 155, 712, 2612

Abstract

This EIP complements EIP-712 by standardizing how contracts should publish the fields and values that describe their domain. This enables applications to retrieve this description and generate appropriate domain separators in a general way, and thus integrate EIP-712 signatures securely and scalably.

Motivation

EIP-712 is a signature scheme for complex structured messages. In order to avoid replay attacks and mitigate phishing, the scheme includes a “domain separator” that makes the resulting signature unique to a specific domain (e.g., a specific contract) and allows user-agents to inform end users the details of what is being signed and how it may be used. A domain is defined by a data structure with fields from a predefined set, all of which are optional, or from extensions. Notably, EIP-712 does not specify any way for contracts to publish which of these fields they use or with what values. This has likely limited adoption of EIP-712, as it is not possible to develop general integrations, and instead applications find that they need to build custom support for each EIP-712 domain. A prime example of this is EIP-2612 (permit), which has not been widely adopted by applications even though it is understood to be a valuable improvement to the user experience. The present EIP defines an interface that can be used by applications to retrieve a definition of the domain that a contract uses to verify EIP-712 signatures.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Compliant contracts MUST define eip712Domain exactly as declared below. All specified values MUST be returned even if they are not used, to ensure proper decoding on the client side.

function eip712Domain() external view returns (
    bytes1 fields,
    string name,
    string version,
    uint256 chainId,
    address verifyingContract,
    bytes32 salt,
    uint256[] extensions
);

The return values of this function MUST describe the domain separator that is used for verification of EIP-712 signatures in the contract. They describe both the form of the EIP712Domain struct (i.e., which of the optional fields and extensions are present) and the value of each field, as follows.

  • fields: A bit map where bit i is set to 1 if and only if domain field i is present (0 ≤ i ≤ 4). Bits are read from least significant to most significant, and fields are indexed in the order that is specified by EIP-712, identical to the order in which they are listed in the function type.
  • name, version, chainId, verifyingContract, salt: The value of the corresponding field in EIP712Domain, if present according to fields. If the field is not present, the value is unspecified. The semantics of each field is defined in EIP-712.
  • extensions: A list of EIP numbers that specify additional fields in the domain. The method to obtain the value for each of these additional fields and any conditions for inclusion are expected to be specified in the respective EIP. The value of fields does not affect their inclusion.

The return values of this function (equivalently, its EIP-712 domain) MAY change throughout the lifetime of a contract, but changes SHOULD NOT be frequent. The chainId field, if used, SHOULD change to mirror the EIP-155 id of the underlying chain.

Rationale

A notable application of EIP-712 signatures is found in EIP-2612 (permit), which specifies a DOMAIN_SEPARATOR function that returns a bytes32 value (the actual domain separator, i.e., the result of hashStruct(eip712Domain)). This value does not suffice for the purposes of integrating with EIP-712, as the RPC methods defined there receive an object describing the domain and not just the separator in hash form. Note that this is not a flaw of the RPC methods, it is indeed part of the security proposition that the domain should be validated and informed to the user as part of the signing process. On its own, a hash does not allow this to be implemented, given it is opaque. The present EIP fills this gap in both EIP-712 and EIP-2612.

Extensions are described by their EIP numbers because EIP-712 states: “Future extensions to this standard can add new fields […] new fields should be proposed through the EIP process.”

Backwards Compatibility

This is an optional extension to EIP-712 that does not introduce backwards compatibility issues.

Upgradeable contracts that make use of EIP-712 signatures MAY be upgraded to implement this EIP.

User-agents or applications that implement this EIP SHOULD additionally support those contracts that due to their immutability cannot be upgraded to implement this EIP, by hardcoding their domain based on contract address and chain id.

Reference Implementation

Solidity Example

pragma solidity 0.8.0;

contract EIP712VerifyingContract {
  function eip712Domain() external view returns (
      bytes1 fields,
      string memory name,
      string memory version,
      uint256 chainId,
      address verifyingContract,
      bytes32 salt,
      uint256[] memory extensions
  ) {
      return (
          hex"0d", // 01101
          "Example",
          "",
          block.chainid,
          address(this),
          bytes32(0),
          new uint256[](0)
      );
  }
}

This contract’s domain only uses the fields name, chainId, and verifyingContract, therefore the fields value is 01101, or 0d in hexadecimal.

Assuming this contract is on Ethereum mainnet and its address is 0x0000000000000000000000000000000000000001, the domain it describes is:

{
  name: "Example",
  chainId: 1,
  verifyingContract: "0x0000000000000000000000000000000000000001"
}

JavaScript

A domain object can be constructed based on the return values of an eip712Domain() invocation.

const fieldNames = ['name', 'version', 'chainId', 'verifyingContract', 'salt'];

/** Builds a domain object based on the values obtained by calling `eip712Domain()` in a contract. */
function buildDomain(fields, name, version, chainId, verifyingContract, salt, extensions) {
  if (extensions.length > 0) {
    throw Error("extensions not implemented");
  }

  const domain = { name, version, chainId, verifyingContract, salt };

  for (const [i, field] of fieldNames.entries()) {
    if (!(fields & (1 << i))) {
      delete domain[field];
    }
  }

  return domain;
}

Security Considerations

While this EIP allows a contract to specify a verifyingContract other than itself, as well as a chainId other than that of the current chain, user-agents and applications should in general validate that these do match the contract and chain before requesting any user signatures for the domain. This may not always be a valid assumption.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Francisco Giordano, "EIP-5267: Retrieval of EIP-712 domain [DRAFT]," Ethereum Improvement Proposals, no. 5267, July 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5267.