// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/SignatureChecker.sol) pragma solidity ^0.8.24; import {ECDSA} from "./ECDSA.sol"; import {IERC1271} from "../../interfaces/IERC1271.sol"; import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol"; import {Bytes} from "../../utils/Bytes.sol"; /** * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support: * * * ECDSA signatures from externally owned accounts (EOAs) * * ERC-1271 signatures from smart contract wallets like Argent and Safe Wallet (previously Gnosis Safe) * * ERC-7913 signatures from keys that do not have an Ethereum address of their own * * See https://eips.ethereum.org/EIPS/eip-1271[ERC-1271] and https://eips.ethereum.org/EIPS/eip-7913[ERC-7913]. */ library SignatureChecker { using Bytes for bytes; /** * @dev Checks if a signature is valid for a given signer and data hash. If the signer has code, the * signature is validated against it using ERC-1271, otherwise it's validated using `ECDSA.recover`. * * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus * change through time. It could return true at block N and false at block N+1 (or the opposite). * * NOTE: For an extended version of this function that supports ERC-7913 signatures, see {isValidSignatureNow-bytes-bytes32-bytes-}. */ function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) { if (signer.code.length == 0) { (address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature); return err == ECDSA.RecoverError.NoError && recovered == signer; } else { return isValidERC1271SignatureNow(signer, hash, signature); } } /** * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated * against the signer smart contract using ERC-1271. * * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus * change through time. It could return true at block N and false at block N+1 (or the opposite). */ function isValidERC1271SignatureNow( address signer, bytes32 hash, bytes memory signature ) internal view returns (bool) { (bool success, bytes memory result) = signer.staticcall( abi.encodeCall(IERC1271.isValidSignature, (hash, signature)) ); return (success && result.length >= 32 && abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector)); } /** * @dev Verifies a signature for a given ERC-7913 signer and hash. * * The signer is a `bytes` object that is the concatenation of an address and optionally a key: * `verifier || key`. A signer must be at least 20 bytes long. * * Verification is done as follows: * * * If `signer.length < 20`: verification fails * * If `signer.length == 20`: verification is done using {isValidSignatureNow} * * Otherwise: verification is done using {IERC7913SignatureVerifier} * * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus * change through time. It could return true at block N and false at block N+1 (or the opposite). */ function isValidSignatureNow( bytes memory signer, bytes32 hash, bytes memory signature ) internal view returns (bool) { if (signer.length < 20) { return false; } else if (signer.length == 20) { return isValidSignatureNow(address(bytes20(signer)), hash, signature); } else { (bool success, bytes memory result) = address(bytes20(signer)).staticcall( abi.encodeCall(IERC7913SignatureVerifier.verify, (signer.slice(20), hash, signature)) ); return (success && result.length >= 32 && abi.decode(result, (bytes32)) == bytes32(IERC7913SignatureVerifier.verify.selector)); } } /** * @dev Verifies multiple ERC-7913 `signatures` for a given `hash` using a set of `signers`. * Returns `false` if the number of signers and signatures is not the same. * * The signers should be ordered by their `keccak256` hash to ensure efficient duplication check. Unordered * signers are supported, but the uniqueness check will be more expensive. * * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus * change through time. It could return true at block N and false at block N+1 (or the opposite). */ function areValidSignaturesNow( bytes32 hash, bytes[] memory signers, bytes[] memory signatures ) internal view returns (bool) { if (signers.length != signatures.length) return false; bytes32 lastId = bytes32(0); for (uint256 i = 0; i < signers.length; ++i) { bytes memory signer = signers[i]; // If one of the signatures is invalid, reject the batch if (!isValidSignatureNow(signer, hash, signatures[i])) return false; bytes32 id = keccak256(signer); // If the current signer ID is greater than all previous IDs, then this is a new signer. if (lastId < id) { lastId = id; } else { // If this signer id is not greater than all the previous ones, verify that it is not a duplicate of a previous one // This loop is never executed if the signers are ordered by id. for (uint256 j = 0; j < i; ++j) { if (id == keccak256(signers[j])) return false; } } } return true; } }