draft-ERC7739.sol 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. // SPDX-License-Identifier: MIT
  2. // OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/signers/draft-ERC7739.sol)
  3. pragma solidity ^0.8.24;
  4. import {AbstractSigner} from "./AbstractSigner.sol";
  5. import {EIP712} from "../EIP712.sol";
  6. import {ERC7739Utils} from "../draft-ERC7739Utils.sol";
  7. import {IERC1271} from "../../../interfaces/IERC1271.sol";
  8. import {MessageHashUtils} from "../MessageHashUtils.sol";
  9. import {ShortStrings} from "../../ShortStrings.sol";
  10. /**
  11. * @dev Validates signatures wrapping the message hash in a nested EIP712 type. See {ERC7739Utils}.
  12. *
  13. * Linking the signature to the EIP-712 domain separator is a security measure to prevent signature replay across different
  14. * EIP-712 domains (e.g. a single offchain owner of multiple contracts).
  15. *
  16. * This contract requires implementing the {_rawSignatureValidation} function, which passes the wrapped message hash,
  17. * which may be either an typed data or a personal sign nested type.
  18. *
  19. * NOTE: xref:api:utils/cryptography#EIP712[EIP-712] uses xref:api:utils/cryptography#ShortStrings[ShortStrings] to
  20. * optimize gas costs for short strings (up to 31 characters). Consider that strings longer than that will use storage,
  21. * which may limit the ability of the signer to be used within the ERC-4337 validation phase (due to
  22. * https://eips.ethereum.org/EIPS/eip-7562#storage-rules[ERC-7562 storage access rules]).
  23. */
  24. abstract contract ERC7739 is AbstractSigner, EIP712, IERC1271 {
  25. using ERC7739Utils for *;
  26. using MessageHashUtils for bytes32;
  27. /**
  28. * @dev Attempts validating the signature in a nested EIP-712 type.
  29. *
  30. * A nested EIP-712 type might be presented in 2 different ways:
  31. *
  32. * - As a nested EIP-712 typed data
  33. * - As a _personal_ signature (an EIP-712 mimic of the `eth_personalSign` for a smart contract)
  34. */
  35. function isValidSignature(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4 result) {
  36. // For the hash `0x7739773977397739773977397739773977397739773977397739773977397739` and an empty signature,
  37. // we return the magic value `0x77390001` as it's assumed impossible to find a preimage for it that can be used
  38. // maliciously. Useful for simulation purposes and to validate whether the contract supports ERC-7739.
  39. return
  40. (_isValidNestedTypedDataSignature(hash, signature) || _isValidNestedPersonalSignSignature(hash, signature))
  41. ? IERC1271.isValidSignature.selector
  42. : (hash == 0x7739773977397739773977397739773977397739773977397739773977397739 && signature.length == 0)
  43. ? bytes4(0x77390001)
  44. : bytes4(0xffffffff);
  45. }
  46. /**
  47. * @dev Nested personal signature verification.
  48. */
  49. function _isValidNestedPersonalSignSignature(bytes32 hash, bytes calldata signature) private view returns (bool) {
  50. return _rawSignatureValidation(_domainSeparatorV4().toTypedDataHash(hash.personalSignStructHash()), signature);
  51. }
  52. /**
  53. * @dev Nested EIP-712 typed data verification.
  54. */
  55. function _isValidNestedTypedDataSignature(
  56. bytes32 hash,
  57. bytes calldata encodedSignature
  58. ) private view returns (bool) {
  59. // decode signature
  60. (
  61. bytes calldata signature,
  62. bytes32 appSeparator,
  63. bytes32 contentsHash,
  64. string calldata contentsDescr
  65. ) = encodedSignature.decodeTypedDataSig();
  66. (
  67. ,
  68. string memory name,
  69. string memory version,
  70. uint256 chainId,
  71. address verifyingContract,
  72. bytes32 salt,
  73. ) = eip712Domain();
  74. // Check that contentHash and separator are correct
  75. // Rebuild nested hash
  76. return
  77. hash == appSeparator.toTypedDataHash(contentsHash) &&
  78. bytes(contentsDescr).length != 0 &&
  79. _rawSignatureValidation(
  80. appSeparator.toTypedDataHash(
  81. ERC7739Utils.typedDataSignStructHash(
  82. contentsDescr,
  83. contentsHash,
  84. abi.encode(keccak256(bytes(name)), keccak256(bytes(version)), chainId, verifyingContract, salt)
  85. )
  86. ),
  87. signature
  88. );
  89. }
  90. }