draft-ERC7739.sol 4.1 KB

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