From 3e6d4fd5808397951ac7c7db2eea8b3746a3c173 Mon Sep 17 00:00:00 2001 From: RevanthGundala <93841932+RevanthGundala@users.noreply.github.com> Date: Wed, 28 Jun 2023 16:32:35 -0700 Subject: [PATCH] Added new features --- eip-registry.md | 169 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 135 insertions(+), 34 deletions(-) diff --git a/eip-registry.md b/eip-registry.md index 301db05..1e0b816 100644 --- a/eip-registry.md +++ b/eip-registry.md @@ -29,42 +29,143 @@ The contract is specified below. A one byte integer is used to identify the stea ```solidity pragma solidity ^0.8.17; +/// @notice Interface for https://eips.ethereum.org/EIPS/eip-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 + * + * MUST return the bytes4 magic value 0x1626ba7e when function passes. + * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) + * MUST allow external calls + */ + function isValidSignature(bytes32 _hash, bytes memory _signature) external view returns (bytes4 magicValue); +} + /// @notice Registry to map an address or other identifier to its stealth meta-address. contract ERC5564Registry { - /// @dev Emitted when a registrant updates their stealth meta-address. - event StealthMetaAddressSet( - bytes indexed registrant, uint256 indexed scheme, bytes stealthMetaAddress - ); - - /// @notice Maps a registrant's identifier to the scheme to the stealth meta-address. - /// @dev Registrant may be a 160 bit address or other recipient identifier, such as an ENS name. - /// @dev Scheme is an integer identifier for the stealth address scheme. - /// @dev MUST return zero if a registrant has not registered keys for the given inputs. - mapping(bytes => mapping(uint256 => bytes)) public stealthMetaAddressOf; - - /// @notice Sets the caller's stealth meta-address for the given stealth address scheme. - /// @param scheme An integer identifier for the stealth address scheme. - /// @param stealthMetaAddress The stealth meta-address to register. - function registerKeys(uint256 scheme, bytes memory stealthMetaAddress) external { - stealthMetaAddressOf[abi.encode(msg.sender)][scheme] = stealthMetaAddress; - } - - /// @notice Sets the `registrant`s stealth meta-address for the given scheme. - /// @param registrant Recipient identifier, such as an ENS name. - /// @param scheme An integer identifier for the stealth address scheme. - /// @param signature A signature from the `registrant` authorizing the registration. - /// @param stealthMetaAddress The stealth meta-address to register. - /// @dev MUST support both EOA signatures and EIP-1271 signatures. - /// @dev MUST revert if the signature is invalid. - function registerKeysOnBehalf( - address registrant, - uint256 scheme, - bytes memory signature, - bytes memory stealthMetaAddress - ) external { - // TODO If registrant has no code, spit signature into r, s, and v and call `ecrecover`. - // TODO If registrant has code, call `isValidSignature` on the registrant. - } + /// @dev Emitted when a registrant updates their stealth meta-address. + event StealthMetaAddressSet(bytes indexed registrant, uint256 indexed scheme, bytes stealthMetaAddress); + + /// @notice Maps a registrant's identifier to the scheme to the stealth meta-address. + /// @dev Registrant may be a 160 bit address or other recipient identifier, such as an ENS name. + /// @dev Scheme is an integer identifier for the stealth address scheme. + /// @dev MUST return zero if a registrant has not registered keys for the given inputs. + mapping(bytes => mapping(uint256 => bytes)) public stealthMetaAddressOf; + + /// @notice Sets the caller's stealth meta-address for the given stealth address scheme. + /// @param scheme An integer identifier for the stealth address scheme. + /// @param stealthMetaAddress The stealth meta-address to register. + function registerKeys(uint256 scheme, bytes memory stealthMetaAddress) external { + stealthMetaAddressOf[abi.encode(msg.sender)][scheme] = stealthMetaAddress; + } + + /// @notice Sets the `registrant`s stealth meta-address for the given scheme. + /// @param registrant Recipient identifier, such as an ENS name. + /// @param scheme An integer identifier for the stealth address scheme. + /// @param hash Hash of the data to be signed + /// @param signature A signature from the `registrant` authorizing the registration. + /// @param stealthMetaAddress The stealth meta-address to register. + /// @dev MUST support both EOA signatures and EIP-1271 signatures. + /// @dev MUST revert if the signature is invalid. + function registerKeysOnBehalf( + address registrant, + uint256 scheme, + bytes32 hash, + bytes memory signature, + bytes memory stealthMetaAddress + ) external { + if (_isContract(registrant)) { + bytes4 result = IERC1271(registrant).isValidSignature(hash, signature); + require(result == 0x1626ba7e, "INVALID_SIGNATURE"); + } else { + // If EOA, validate signature + _recoverSigner(hash, signature); + } + stealthMetaAddressOf[abi.encode(registrant)][scheme] = stealthMetaAddress; + } + + ////////////////////////////// + // Private & Internal View & Pure Functions + ////////////////////////////// + + /* + * @notice Determines if the account is a smart contract + * @param account The contract address of the user wallet + * @return A boolean indicating if the address is a smart contract + */ + function _isContract(address account) internal view returns (bool) { + bytes32 codehash; + // Hash representing an empty account (address with no code) + // Source https://eips.ethereum.org/EIPS/eip-1052 + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + assembly { + // Get the code hash of the account address + codehash := extcodehash(account) + } + // Check if the code hash is different from the empty account hash and zero + return (codehash != accountHash && codehash != 0x0); + } + + /** + * @notice Recover the signer of hash, assuming it's an EOA account + * @dev Only for EthSign signatures + * @param _hash Hash of message that was signed + * @param _signature Signature encoded as (bytes32 r, bytes32 s, uint8 v) + * @return signer Signer of the signature + */ + function _recoverSigner(bytes32 _hash, bytes memory _signature) private pure returns (address signer) { + require(_signature.length == 65, "SignatureValidator#recoverSigner: invalid signature length"); + + // Variables are not scoped in Solidity. + uint8 v = uint8(_signature[64]); + bytes32 r = _readBytes32(_signature, 0); + bytes32 s = _readBytes32(_signature, 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("SignatureValidator#recoverSigner: invalid signature 's' value"); + } + + if (v != 27 && v != 28) { + revert("SignatureValidator#recoverSigner: invalid signature 'v' value"); + } + + // Recover ECDSA signer + signer = ecrecover(_hash, v, r, s); + + // Prevent signer from being 0x0 + require(signer != address(0x0), "SignatureValidator#recoverSigner: INVALID_SIGNER"); + return signer; + } + + /** + * @notice Helper function that reads bytes at memory location and stores in result + * @dev Used to calculate v and s for signature recovery + * @param signature Signature encoded as (bytes32 r, bytes32 s, uint8 v) + * @param offset Offset in bytes where the desired bytes32 value starts + * @return The extracted bytes32 value + */ + function _readBytes32(bytes memory signature, uint256 offset) private pure returns (bytes32) { + bytes32 result; + assembly { + result := mload(add(signature, add(32, offset))) + } + return result; + } } ```