// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.18; import "../interfaces/IERC1271Wallet.sol"; import "./LibBytes.sol"; /** * @dev Contains logic for signature validation. * Signatures from wallet contracts assume ERC-1271 support (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1271.md) * Notes: Methods are strongly inspired by contracts in https://github.com/0xProject/0x-monorepo/blob/development/ */ library SignatureValidator { // Errors error InvalidSignatureLength(bytes _signature); error EmptySignature(); error InvalidSValue(bytes _signature, bytes32 _s); error InvalidVValue(bytes _signature, uint256 _v); error UnsupportedSignatureType(bytes _signature, uint256 _type, bool _recoverMode); error SignerIsAddress0(bytes _signature); using LibBytes for bytes; /***********************************| | Variables | |__________________________________*/ // bytes4(keccak256("isValidSignature(bytes,bytes)")) bytes4 constant internal ERC1271_MAGICVALUE = 0x20c13b0b; // bytes4(keccak256("isValidSignature(bytes32,bytes)")) bytes4 constant internal ERC1271_MAGICVALUE_BYTES32 = 0x1626ba7e; // Allowed signature types. uint256 private constant SIG_TYPE_EIP712 = 1; uint256 private constant SIG_TYPE_ETH_SIGN = 2; uint256 private constant SIG_TYPE_WALLET_BYTES32 = 3; /***********************************| | Signature Functions | |__________________________________*/ /** * @notice Recover the signer of hash, assuming it's an EOA account * @dev Only for SignatureType.EIP712 and SignatureType.EthSign signatures * @param _hash Hash that was signed * encoded as (bytes32 r, bytes32 s, uint8 v, ... , SignatureType sigType) */ function recoverSigner( bytes32 _hash, bytes calldata _signature ) internal pure returns (address signer) { if (_signature.length != 66) revert InvalidSignatureLength(_signature); uint256 signatureType = _signature.readUint8(_signature.length - 1); // Variables are not scoped in Solidity. uint8 v = _signature.readUint8(64); bytes32 r = _signature.readBytes32(0); bytes32 s = _signature.readBytes32(32); // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most // signatures from current libraries generate a unique signature with an s-value in the lower half order. // // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // these malleable signatures as well. // // Source OpenZeppelin // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/cryptography/ECDSA.sol if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { revert InvalidSValue(_signature, s); } if (v != 27 && v != 28) { revert InvalidVValue(_signature, v); } // Signature using EIP712 if (signatureType == SIG_TYPE_EIP712) { signer = ecrecover(_hash, v, r, s); // Signed using web3.eth_sign() or Ethers wallet.signMessage() } else if (signatureType == SIG_TYPE_ETH_SIGN) { signer = ecrecover( keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash)), v, r, s ); } else { // We cannot recover the signer for any other signature type. revert UnsupportedSignatureType(_signature, signatureType, true); } // Prevent signer from being 0x0 if (signer == address(0x0)) revert SignerIsAddress0(_signature); return signer; } /** * @notice Returns true if the provided signature is valid for the given signer. * @dev Supports SignatureType.EIP712, SignatureType.EthSign, and ERC1271 signatures * @param _hash Hash that was signed * @param _signer Address of the signer candidate * @param _signature Signature byte array */ function isValidSignature( bytes32 _hash, address _signer, bytes calldata _signature ) internal view returns (bool valid) { if (_signature.length == 0) { revert EmptySignature(); } uint256 signatureType = uint8(_signature[_signature.length - 1]); if (signatureType == SIG_TYPE_EIP712 || signatureType == SIG_TYPE_ETH_SIGN) { // Recover signer and compare with provided valid = recoverSigner(_hash, _signature) == _signer; } else if (signatureType == SIG_TYPE_WALLET_BYTES32) { // Remove signature type before calling ERC1271, restore after call valid = ERC1271_MAGICVALUE_BYTES32 == IERC1271Wallet(_signer).isValidSignature(_hash, _signature[0:_signature.length - 1]); } else { // We cannot validate any other signature type. // We revert because we can say nothing about its validity. revert UnsupportedSignatureType(_signature, signatureType, false); } } }