99 lines
4.1 KiB
Solidity
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
|
|
);
|
|
}
|
|
}
|