draft-ERC7739Signer.sol 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.20;
  3. import {IERC1271} from "../../interfaces/IERC1271.sol";
  4. import {EIP712} from "../../utils/cryptography/EIP712.sol";
  5. import {MessageHashUtils} from "../../utils/cryptography/MessageHashUtils.sol";
  6. import {ShortStrings} from "../../utils/ShortStrings.sol";
  7. import {ERC7739Utils} from "./draft-ERC7739Utils.sol";
  8. /**
  9. * @dev Validates signatures wrapping the message hash in a nested EIP712 type. See {ERC7739Utils}.
  10. *
  11. * Linking the signature to the EIP-712 domain separator is a security measure to prevent signature replay across different
  12. * EIP-712 domains (e.g. a single offchain owner of multiple contracts).
  13. *
  14. * This contract requires implementing the {_validateSignature} function, which passes the wrapped message hash,
  15. * which may be either an typed data or a personal sign nested type.
  16. *
  17. * NOTE: {EIP712} uses {ShortStrings} to optimize gas costs for short strings (up to 31 characters).
  18. * Consider that strings longer than that will use storage, which may limit the ability of the signer to
  19. * be used within the ERC-4337 validation phase (due to ERC-7562 storage access rules).
  20. */
  21. abstract contract ERC7739Signer is EIP712, IERC1271 {
  22. using ERC7739Utils for *;
  23. using MessageHashUtils for bytes32;
  24. /**
  25. * @dev Attempts validating the signature in a nested EIP-712 type.
  26. *
  27. * A nested EIP-712 type might be presented in 2 different ways:
  28. *
  29. * - As a nested EIP-712 typed data
  30. * - As a _personal_ signature (an EIP-712 mimic of the `eth_personalSign` for a smart contract)
  31. */
  32. function isValidSignature(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4 result) {
  33. // For the hash `0x7739773977397739773977397739773977397739773977397739773977397739` and an empty signature,
  34. // we return the magic value too as it's assumed impossible to find a preimage for it that can be used maliciously.
  35. // Useful for simulation purposes and to validate whether the contract supports ERC-7739.
  36. return
  37. _isValidSignature(hash, signature)
  38. ? IERC1271.isValidSignature.selector
  39. : (hash == 0x7739773977397739773977397739773977397739773977397739773977397739 && signature.length == 0)
  40. ? bytes4(0x77390001)
  41. : bytes4(0xffffffff);
  42. }
  43. /**
  44. * @dev Internal version of {isValidSignature} that returns a boolean.
  45. */
  46. function _isValidSignature(bytes32 hash, bytes calldata signature) internal view virtual returns (bool) {
  47. return
  48. _isValidNestedTypedDataSignature(hash, signature) || _isValidNestedPersonalSignSignature(hash, signature);
  49. }
  50. /**
  51. * @dev Nested personal signature verification.
  52. *
  53. * NOTE: Instead of overriding this function, try with {_validateSignature}. It encapsulates
  54. * nested EIP-712 hashes.
  55. */
  56. function _isValidNestedPersonalSignSignature(
  57. bytes32 hash,
  58. bytes calldata signature
  59. ) internal view virtual returns (bool) {
  60. return _validateSignature(_domainSeparatorV4().toTypedDataHash(hash.personalSignStructHash()), signature);
  61. }
  62. /**
  63. * @dev Nested EIP-712 typed data verification.
  64. *
  65. * NOTE: Instead of overriding this function, try with {_validateSignature}. It encapsulates
  66. * nested EIP-712 hashes.
  67. */
  68. function _isValidNestedTypedDataSignature(
  69. bytes32 hash,
  70. bytes calldata encodedSignature
  71. ) internal view virtual returns (bool) {
  72. // decode signature
  73. (
  74. bytes calldata signature,
  75. bytes32 appSeparator,
  76. bytes32 contentsHash,
  77. string calldata contentsDescr
  78. ) = encodedSignature.decodeTypedDataSig();
  79. (
  80. ,
  81. string memory name,
  82. string memory version,
  83. uint256 chainId,
  84. address verifyingContract,
  85. bytes32 salt,
  86. ) = eip712Domain();
  87. // Check that contentHash and separator are correct
  88. // Rebuild nested hash
  89. return
  90. hash == appSeparator.toTypedDataHash(contentsHash) &&
  91. bytes(contentsDescr).length != 0 &&
  92. _validateSignature(
  93. appSeparator.toTypedDataHash(
  94. ERC7739Utils.typedDataSignStructHash(
  95. contentsDescr,
  96. contentsHash,
  97. abi.encode(keccak256(bytes(name)), keccak256(bytes(version)), chainId, verifyingContract, salt)
  98. )
  99. ),
  100. signature
  101. );
  102. }
  103. /**
  104. * @dev Signature validation algorithm.
  105. *
  106. * To ensure there's no way to inherit from this ERC-7739 signer and still be able to sign raw messages,
  107. * this function provides a nested EIP-712 hash. Developers must implement only this function to ensure
  108. * no raw message signing is possible.
  109. *
  110. * WARNING: Implementing a signature validation algorithm is a security-sensitive operation as it involves
  111. * cryptographic verification. It is important to review and test thoroughly before deployment. Consider
  112. * using one of the signature verification libraries ({ECDSA}, {P256} or {RSA}).
  113. */
  114. function _validateSignature(
  115. bytes32 nestedEIP712Hash,
  116. bytes calldata signature
  117. ) internal view virtual returns (bool);
  118. }