EIP712.sol 4.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.0;
  3. /**
  4. * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
  5. *
  6. * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
  7. * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
  8. * they need in their contracts using a combination of `abi.encode` and `keccak256`.
  9. *
  10. * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
  11. * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
  12. * ({_hashTypedDataV4}).
  13. *
  14. * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
  15. * the chain id to protect against replay attacks on an eventual fork of the chain.
  16. *
  17. * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
  18. * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
  19. */
  20. abstract contract EIP712 {
  21. /* solhint-disable var-name-mixedcase */
  22. // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
  23. // invalidate the cached domain separator if the chain id changes.
  24. bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
  25. uint256 private immutable _CACHED_CHAIN_ID;
  26. bytes32 private immutable _HASHED_NAME;
  27. bytes32 private immutable _HASHED_VERSION;
  28. bytes32 private immutable _TYPE_HASH;
  29. /* solhint-enable var-name-mixedcase */
  30. /**
  31. * @dev Initializes the domain separator and parameter caches.
  32. *
  33. * The meaning of `name` and `version` is specified in
  34. * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
  35. *
  36. * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
  37. * - `version`: the current major version of the signing domain.
  38. *
  39. * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
  40. * contract upgrade].
  41. */
  42. constructor(string memory name, string memory version) {
  43. bytes32 hashedName = keccak256(bytes(name));
  44. bytes32 hashedVersion = keccak256(bytes(version));
  45. bytes32 typeHash = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
  46. _HASHED_NAME = hashedName;
  47. _HASHED_VERSION = hashedVersion;
  48. _CACHED_CHAIN_ID = block.chainid;
  49. _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
  50. _TYPE_HASH = typeHash;
  51. }
  52. /**
  53. * @dev Returns the domain separator for the current chain.
  54. */
  55. function _domainSeparatorV4() internal view returns (bytes32) {
  56. if (block.chainid == _CACHED_CHAIN_ID) {
  57. return _CACHED_DOMAIN_SEPARATOR;
  58. } else {
  59. return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
  60. }
  61. }
  62. function _buildDomainSeparator(bytes32 typeHash, bytes32 name, bytes32 version) private view returns (bytes32) {
  63. return keccak256(
  64. abi.encode(
  65. typeHash,
  66. name,
  67. version,
  68. block.chainid,
  69. address(this)
  70. )
  71. );
  72. }
  73. /**
  74. * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
  75. * function returns the hash of the fully encoded EIP712 message for this domain.
  76. *
  77. * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
  78. *
  79. * ```solidity
  80. * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
  81. * keccak256("Mail(address to,string contents)"),
  82. * mailTo,
  83. * keccak256(bytes(mailContents))
  84. * )));
  85. * address signer = ECDSA.recover(digest, signature);
  86. * ```
  87. */
  88. function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
  89. return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), structHash));
  90. }
  91. }