130 lines
5.3 KiB
Solidity
130 lines
5.3 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {IERC1271} from "../../interfaces/IERC1271.sol";
|
|
import {EIP712} from "../../utils/cryptography/EIP712.sol";
|
|
import {MessageHashUtils} from "../../utils/cryptography/MessageHashUtils.sol";
|
|
import {ShortStrings} from "../../utils/ShortStrings.sol";
|
|
import {ERC7739Utils} from "./draft-ERC7739Utils.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 {_validateSignature} function, which passes the wrapped message hash,
|
|
* which may be either an typed data or a personal sign nested type.
|
|
*
|
|
* NOTE: {EIP712} uses {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 ERC-7562 storage access rules).
|
|
*/
|
|
abstract contract ERC7739Signer is 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 too 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
|
|
_isValidSignature(hash, signature)
|
|
? IERC1271.isValidSignature.selector
|
|
: (hash == 0x7739773977397739773977397739773977397739773977397739773977397739 && signature.length == 0)
|
|
? bytes4(0x77390001)
|
|
: bytes4(0xffffffff);
|
|
}
|
|
|
|
/**
|
|
* @dev Internal version of {isValidSignature} that returns a boolean.
|
|
*/
|
|
function _isValidSignature(bytes32 hash, bytes calldata signature) internal view virtual returns (bool) {
|
|
return
|
|
_isValidNestedTypedDataSignature(hash, signature) || _isValidNestedPersonalSignSignature(hash, signature);
|
|
}
|
|
|
|
/**
|
|
* @dev Nested personal signature verification.
|
|
*
|
|
* NOTE: Instead of overriding this function, try with {_validateSignature}. It encapsulates
|
|
* nested EIP-712 hashes.
|
|
*/
|
|
function _isValidNestedPersonalSignSignature(
|
|
bytes32 hash,
|
|
bytes calldata signature
|
|
) internal view virtual returns (bool) {
|
|
return _validateSignature(_domainSeparatorV4().toTypedDataHash(hash.personalSignStructHash()), signature);
|
|
}
|
|
|
|
/**
|
|
* @dev Nested EIP-712 typed data verification.
|
|
*
|
|
* NOTE: Instead of overriding this function, try with {_validateSignature}. It encapsulates
|
|
* nested EIP-712 hashes.
|
|
*/
|
|
function _isValidNestedTypedDataSignature(
|
|
bytes32 hash,
|
|
bytes calldata encodedSignature
|
|
) internal view virtual 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 &&
|
|
_validateSignature(
|
|
appSeparator.toTypedDataHash(
|
|
ERC7739Utils.typedDataSignStructHash(
|
|
contentsDescr,
|
|
contentsHash,
|
|
abi.encode(keccak256(bytes(name)), keccak256(bytes(version)), chainId, verifyingContract, salt)
|
|
)
|
|
),
|
|
signature
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev Signature validation algorithm.
|
|
*
|
|
* To ensure there's no way to inherit from this ERC-7739 signer and still be able to sign raw messages,
|
|
* this function provides a nested EIP-712 hash. Developers must implement only this function to ensure
|
|
* no raw message signing is possible.
|
|
*
|
|
* WARNING: Implementing a signature validation algorithm is a security-sensitive operation as it involves
|
|
* cryptographic verification. It is important to review and test thoroughly before deployment. Consider
|
|
* using one of the signature verification libraries ({ECDSA}, {P256} or {RSA}).
|
|
*/
|
|
function _validateSignature(
|
|
bytes32 nestedEIP712Hash,
|
|
bytes calldata signature
|
|
) internal view virtual returns (bool);
|
|
}
|