Files
openzeppelin-contracts/contracts/utils/cryptography/signers/ERC7739.sol
Hadrien Croubois f45e9158b7 Reorder cryptography folder (#5711)
Co-authored-by: ernestognw <ernestognw@gmail.com>
2025-06-03 21:40:34 -06:00

99 lines
4.1 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {AbstractSigner} from "./AbstractSigner.sol";
import {EIP712} from "../EIP712.sol";
import {ERC7739Utils} from "../ERC7739Utils.sol";
import {IERC1271} from "../../../interfaces/IERC1271.sol";
import {MessageHashUtils} from "../MessageHashUtils.sol";
import {ShortStrings} from "../../ShortStrings.sol";
/**
* @dev Validates signatures wrapping the message hash in a nested EIP712 type. See {ERC7739Utils}.
*
* Linking the signature to the EIP-712 domain separator is a security measure to prevent signature replay across different
* EIP-712 domains (e.g. a single offchain owner of multiple contracts).
*
* This contract requires implementing the {_rawSignatureValidation} function, which passes the wrapped message hash,
* which may be either an typed data or a personal sign nested type.
*
* NOTE: xref:api:utils#EIP712[EIP-712] uses xref:api:utils#ShortStrings[ShortStrings] to optimize gas
* costs for short strings (up to 31 characters). Consider that strings longer than that will use storage,
* which may limit the ability of the signer to be used within the ERC-4337 validation phase (due to
* https://eips.ethereum.org/EIPS/eip-7562#storage-rules[ERC-7562 storage access rules]).
*/
abstract contract ERC7739 is AbstractSigner, EIP712, IERC1271 {
using ERC7739Utils for *;
using MessageHashUtils for bytes32;
/**
* @dev Attempts validating the signature in a nested EIP-712 type.
*
* A nested EIP-712 type might be presented in 2 different ways:
*
* - As a nested EIP-712 typed data
* - As a _personal_ signature (an EIP-712 mimic of the `eth_personalSign` for a smart contract)
*/
function isValidSignature(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4 result) {
// For the hash `0x7739773977397739773977397739773977397739773977397739773977397739` and an empty signature,
// we return the magic value `0x77390001` as it's assumed impossible to find a preimage for it that can be used
// maliciously. Useful for simulation purposes and to validate whether the contract supports ERC-7739.
return
(_isValidNestedTypedDataSignature(hash, signature) || _isValidNestedPersonalSignSignature(hash, signature))
? IERC1271.isValidSignature.selector
: (hash == 0x7739773977397739773977397739773977397739773977397739773977397739 && signature.length == 0)
? bytes4(0x77390001)
: bytes4(0xffffffff);
}
/**
* @dev Nested personal signature verification.
*/
function _isValidNestedPersonalSignSignature(bytes32 hash, bytes calldata signature) private view returns (bool) {
return _rawSignatureValidation(_domainSeparatorV4().toTypedDataHash(hash.personalSignStructHash()), signature);
}
/**
* @dev Nested EIP-712 typed data verification.
*/
function _isValidNestedTypedDataSignature(
bytes32 hash,
bytes calldata encodedSignature
) private view returns (bool) {
// decode signature
(
bytes calldata signature,
bytes32 appSeparator,
bytes32 contentsHash,
string calldata contentsDescr
) = encodedSignature.decodeTypedDataSig();
(
,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
) = eip712Domain();
// Check that contentHash and separator are correct
// Rebuild nested hash
return
hash == appSeparator.toTypedDataHash(contentsHash) &&
bytes(contentsDescr).length != 0 &&
_rawSignatureValidation(
appSeparator.toTypedDataHash(
ERC7739Utils.typedDataSignStructHash(
contentsDescr,
contentsHash,
abi.encode(keccak256(bytes(name)), keccak256(bytes(version)), chainId, verifyingContract, salt)
)
),
signature
);
}
}