diff --git a/src/ERC6538Registry.sol b/src/ERC6538Registry.sol index 67e63ba..dd895dd 100644 --- a/src/ERC6538Registry.sol +++ b/src/ERC6538Registry.sol @@ -70,14 +70,38 @@ contract ERC6538Registry { address registrant, uint256 schemeId, bytes memory signature, - bytes memory stealthMetaAddress + bytes memory stealthMetaAddress, + uint8 v, + bytes32 r, + bytes32 s ) external { - bytes32 digest = _hashTypedDataV4( - keccak256( - abi.encode(TYPE_HASH, registrant, schemeId, stealthMetaAddress, nonceOf[registrant]++) - ) + bytes32 dataHash; + address recoveredAddress; + + unchecked { + dataHash = keccak256( + abi.encodePacked( + "\x19\x01", + domainSeparator, + keccak256( + abi.encode(TYPE_HASH, registrant, schemeId, stealthMetaAddress, nonceOf[registrant]++) + ) + ) + ); + recoveredAddress = ecrecover(dataHash, v, r, s); + } + + require( + ( + (recoveredAddress != address(0) && recoveredAddress == registrant) + || ( + IERC1271(registrant).isValidSignature(dataHash, signature) + == IERC1271.isValidSignature.selector + ) + ), + "Invalid signature" ); - require(isValidSignatureNow(registrant, digest, signature), "Invalid signature"); + stealthMetaAddressOf[registrant][schemeId] = stealthMetaAddress; emit StealthMetaAddressSet(registrant, schemeId, stealthMetaAddress); } @@ -86,134 +110,16 @@ contract ERC6538Registry { function incrementNonce() external { nonceOf[msg.sender]++; } +} - /// @notice Returns the hash of the fully encoded EIP712 message for this domain. - /// @dev The following code is modified from OpenZeppelin's `EIP712.sol` file. Permalink: - /// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e70a0118ef10773457f670671baefad2c5ea610d/contracts/utils/cryptography/EIP712.sol - /// @param structHash The hash of the struct containing the message data, as defined in - /// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct - function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { - return toTypedDataHash(domainSeparator, structHash); - } - - /// @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`). - /// @dev The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them - /// with `\x19\x01` and hashing the result. It corresponds to the hash signed by the - /// https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of - /// EIP-712. - /// @dev The following code is from OpenZeppelin's `MessageHashUtils.sol` file. Permalink: - /// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e70a0118ef10773457f670671baefad2c5ea610d/contracts/utils/cryptography/MessageHashUtils.sol - /// @param contractDomainSeparator The domain separator. - /// @param structHash The hash of the struct containing the message data, as defined in - /// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct - function toTypedDataHash(bytes32 contractDomainSeparator, bytes32 structHash) - internal - pure - returns (bytes32 digest) - { - /// @solidity memory-safe-assembly - assembly { - let ptr := mload(0x40) - mstore(ptr, hex"1901") - mstore(add(ptr, 0x02), contractDomainSeparator) - mstore(add(ptr, 0x22), structHash) - digest := keccak256(ptr, 0x42) - } - } - - /// @notice Checks if a signature is valid for a given signer and data hash. If the signer is a - /// smart contract, the signature is validated against that smart contract using ERC1271, - /// otherwise it's validated using `tryRecover`. - /// @dev The following code is from OpenZeppelin's `SignatureChecker.sol` file. Permalink: - /// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e70a0118ef10773457f670671baefad2c5ea610d/contracts/utils/cryptography/SignatureChecker.sol - /// @param signer The address that should have signed the message data. - /// @param hash The digest of message data. - /// @param signature The signature provided by the registrant. - function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) - internal - view - returns (bool) - { - (address recovered, RecoverError error,) = tryRecover(hash, signature); - return (error == RecoverError.NoError && recovered == signer) - || isValidERC1271SignatureNow(signer, hash, signature); - } - - /// @notice Checks if a signature is valid for a given signer and data hash. The signature is - /// validated against the signer smart contract using ERC1271. - /// @dev The following code is modified from OpenZeppelin's `SignatureChecker.sol` file. Permalink: - /// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e70a0118ef10773457f670671baefad2c5ea610d/contracts/utils/cryptography/SignatureChecker.sol - /// @param signer The address that should have signed the message data. - /// @param hash The digest of message data. - /// @param signature The signature provided by the registrant. - function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature) - internal +/// @notice Interface of the ERC1271 standard signature validation method for contracts as defined +/// in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. +interface IERC1271 { + /// @dev Should return whether the signature provided is valid for the provided data + /// @param hash Hash of the data to be signed + /// @param signature Signature byte array associated with _data + function isValidSignature(bytes32 hash, bytes memory signature) + external view - returns (bool) - { - (bool success, bytes memory result) = signer.staticcall( - abi.encodeWithSelector(bytes4(keccak256("isValidSignature(bytes32,bytes)")), hash, signature) - ); - return ( - success && result.length >= 32 - && abi.decode(result, (bytes32)) - == bytes32(bytes4(keccak256("isValidSignature(bytes32,bytes)"))) - ); - } - - /// @dev The following code is from OpenZeppelin's `ECDSA.sol` file. Permalink: - /// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e70a0118ef10773457f670671baefad2c5ea610d/contracts/utils/cryptography/ECDSA.sol - /// @param hash The digest of message data. - /// @param signature The signature provided by the registrant. - function tryRecover(bytes32 hash, bytes memory signature) - internal - pure - returns (address, RecoverError, bytes32) - { - if (signature.length == 65) { - bytes32 r; - bytes32 s; - uint8 v; - // ecrecover takes the signature parameters, and the only way to get them currently is to use - // assembly. - assembly ("memory-safe") { - r := mload(add(signature, 0x20)) - s := mload(add(signature, 0x40)) - v := byte(0, mload(add(signature, 0x60))) - } - return tryRecover(hash, v, r, s); - } else { - return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length)); - } - } - - /// @notice Recover the signer's address using `v`, `r` and `s` signature fields. - /// @dev The following code is from OpenZeppelin's `ECDSA.sol` file. Permalink: - /// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e70a0118ef10773457f670671baefad2c5ea610d/contracts/utils/cryptography/ECDSA.sol - function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) - internal - pure - returns (address, RecoverError, bytes32) - { - // 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 (301): - // 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): 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. - if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { - return (address(0), RecoverError.InvalidSignatureS, s); - } - - // If the signature is valid (and not malleable), return the signer address - address signer = ecrecover(hash, v, r, s); - if (signer == address(0)) return (address(0), RecoverError.InvalidSignature, bytes32(0)); - - return (signer, RecoverError.NoError, bytes32(0)); - } + returns (bytes4 magicValue); } diff --git a/test/ERC6538Registry.t.sol b/test/ERC6538Registry.t.sol index 0c9c662..3364d8a 100644 --- a/test/ERC6538Registry.t.sol +++ b/test/ERC6538Registry.t.sol @@ -69,7 +69,7 @@ contract RegisterKeysOnBehalf_Address is ERC6538RegistryTest { vm.expectEmit(true, true, true, true); emit StealthMetaAddressSet(alice, schemeId, stealthMetaAddress); - registry.registerKeysOnBehalf(alice, schemeId, signature, stealthMetaAddress); + registry.registerKeysOnBehalf(alice, schemeId, signature, stealthMetaAddress, v, r, s); } function testFuzz_ERC1271SignatureIsValid( @@ -91,7 +91,7 @@ contract RegisterKeysOnBehalf_Address is ERC6538RegistryTest { vm.expectEmit(true, true, true, true); emit StealthMetaAddressSet(registrant, schemeId, stealthMetaAddress); - registry.registerKeysOnBehalf(registrant, schemeId, signature, stealthMetaAddress); + registry.registerKeysOnBehalf(registrant, schemeId, signature, stealthMetaAddress, v, r, s); } function testFuzz_UpdateStealthMetaAddress( @@ -112,7 +112,7 @@ contract RegisterKeysOnBehalf_Address is ERC6538RegistryTest { vm.expectEmit(true, true, true, true); emit StealthMetaAddressSet(alice, schemeId, stealthMetaAddress); - registry.registerKeysOnBehalf(alice, schemeId, signature, stealthMetaAddress); + registry.registerKeysOnBehalf(alice, schemeId, signature, stealthMetaAddress, v, r, s); } } @@ -123,12 +123,14 @@ contract RegisterKeysOnBehalf_Address is ERC6538RegistryTest { bytes memory stealthMetaAddress ) external { (address alice, uint256 alicePk) = makeAddrAndKey(name); - bytes32 hash = keccak256(abi.encode(alice, schemeId, stealthMetaAddress, 0)); + SigUtils.RegistrantInfo memory registrantInfo = + SigUtils.RegistrantInfo(alice, schemeId, stealthMetaAddress, 0 /* nonce */ ); + bytes32 hash = sigUtils.getTypedDataHash(registrantInfo); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, hash); bytes memory signature = abi.encodePacked(r, s, v); - vm.expectRevert("Invalid signature"); - registry.registerKeysOnBehalf(bob, schemeId, signature, stealthMetaAddress); + vm.expectRevert(); + registry.registerKeysOnBehalf(bob, schemeId, signature, stealthMetaAddress, v, r, s); } function testFuzz_RevertIf_WrongNonce( @@ -139,17 +141,18 @@ contract RegisterKeysOnBehalf_Address is ERC6538RegistryTest { ) external { vm.assume(nonce != 0); (address alice, uint256 alicePk) = makeAddrAndKey(name); - bytes32 hash = keccak256(abi.encode(alice, schemeId, stealthMetaAddress, nonce)); + SigUtils.RegistrantInfo memory registrantInfo = + SigUtils.RegistrantInfo(alice, schemeId, stealthMetaAddress, nonce); + bytes32 hash = sigUtils.getTypedDataHash(registrantInfo); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, hash); bytes memory signature = abi.encodePacked(r, s, v); - - vm.expectRevert("Invalid signature"); - registry.registerKeysOnBehalf(alice, schemeId, signature, stealthMetaAddress); + vm.expectRevert(); + registry.registerKeysOnBehalf(alice, schemeId, signature, stealthMetaAddress, v, r, s); } function test_RevertIf_NoSignatureIsProvided() external { - vm.expectRevert("Invalid signature"); - registry.registerKeysOnBehalf(address(0), 0, "", ""); + vm.expectRevert(); + registry.registerKeysOnBehalf(address(0), 0, "", "", 0, 0, 0); } } @@ -266,3 +269,15 @@ contract SigUtils { return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, getStructHash(_info))); } } +/// @notice Interface of the ERC1271 standard signature validation method for contracts as defined +/// in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. + +interface IERC1271 { + /// @dev Should return whether the signature provided is valid for the provided data + /// @param hash Hash of the data to be signed + /// @param signature Signature byte array associated with _data + function isValidSignature(bytes32 hash, bytes memory signature) + external + view + returns (bytes4 magicValue); +}