From 42f624bc2f069e0fcaacd6a07595527357d56534 Mon Sep 17 00:00:00 2001 From: Thrilok kumar Date: Tue, 16 Jul 2024 23:21:14 -0400 Subject: [PATCH] EIP-1271 --- contracts/v2/accounts/default/ECDSA.sol | 99 ++++++++++++++++ .../default/implementation_default.sol | 107 ++++++++++++++++++ contracts/v2/accounts/variables.sol | 2 + 3 files changed, 208 insertions(+) create mode 100644 contracts/v2/accounts/default/ECDSA.sol diff --git a/contracts/v2/accounts/default/ECDSA.sol b/contracts/v2/accounts/default/ECDSA.sol new file mode 100644 index 0000000..83c62b5 --- /dev/null +++ b/contracts/v2/accounts/default/ECDSA.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.0; + +/** + * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. + * + * These functions can be used to verify that a message was signed by the holder + * of the private keys of a given address. + */ +library ECDSA { + /** + * @dev Returns the address that signed a hashed message (`hash`) with + * `signature`. This address can then be used for verification purposes. + * + * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: + * this function rejects them by requiring the `s` value to be in the lower + * half order, and the `v` value to be either 27 or 28. + * + * IMPORTANT: `hash` _must_ be the result of a hash operation for the + * verification to be secure: it is possible to craft signatures that + * recover to arbitrary addresses for non-hashed data. A safe way to ensure + * this is by receiving a hash of the original message (which may otherwise + * be too long), and then calling {toEthSignedMessageHash} on it. + */ + function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { + // Check the signature length + if (signature.length != 65) { + revert("ECDSA: invalid signature length"); + } + + // Divide the signature in r, s and v variables + bytes32 r; + bytes32 s; + uint8 v; + + // ecrecover takes the signature parameters, and the only way to get them + // currently is to use assembly. + // solhint-disable-next-line no-inline-assembly + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + + return recover(hash, v, r, s); + } + + /** + * @dev Overload of {ECDSA-recover} that receives the `v`, + * `r` and `s` signature fields separately. + */ + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { + // 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. + require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value"); + require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value"); + + // If the signature is valid (and not malleable), return the signer address + address signer = ecrecover(hash, v, r, s); + require(signer != address(0), "ECDSA: invalid signature"); + + return signer; + } + + /** + * @dev Returns an Ethereum Signed Message, created from a `hash`. This + * produces hash corresponding to the one signed with the + * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] + * JSON-RPC method as part of EIP-191. + * + * See {recover}. + */ + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { + // 32 is the length in bytes of hash, + // enforced by the type signature above + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + + /** + * @dev Returns an Ethereum Signed Typed Data, created from a + * `domainSeparator` and a `structHash`. This produces hash corresponding + * to the one signed with the + * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] + * JSON-RPC method as part of EIP-712. + * + * See {recover}. + */ + function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); + } +} \ No newline at end of file diff --git a/contracts/v2/accounts/default/implementation_default.sol b/contracts/v2/accounts/default/implementation_default.sol index 6ad8803..f6fe915 100644 --- a/contracts/v2/accounts/default/implementation_default.sol +++ b/contracts/v2/accounts/default/implementation_default.sol @@ -3,6 +3,8 @@ pragma experimental ABIEncoderV2; import { Variables } from "../variables.sol"; +import { ECDSA } from "./ECDSA.sol"; + interface IndexInterface { function list() external view returns (address); } @@ -20,6 +22,31 @@ contract Constants is Variables { // The Account Module Version. uint256 public constant version = 2; + // constants for EIP712 values + string public constant DOMAIN_SEPARATOR_NAME = "DeFi-Smart-Wallet"; + string public constant DOMAIN_SEPARATOR_VERSION = "2.0.0"; + + // hashed EIP712 values + bytes32 internal constant DOMAIN_SEPARATOR_NAME_HASHED = keccak256(bytes(DOMAIN_SEPARATOR_NAME)); + bytes32 internal constant DOMAIN_SEPARATOR_VERSION_HASHED = keccak256(bytes(DOMAIN_SEPARATOR_VERSION)); + + bytes32 internal constant TYPE_HASH = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + + // EIP712 typehash for signed hashes used for EIP1271 (`isValidSignature()`) + bytes32 public constant EIP1271_TYPE_HASH = keccak256("DSA(bytes32 hash)"); + + // "magic value" according to EIP1271 https://eips.ethereum.org/EIPS/eip-1271#specification + bytes4 internal constant EIP1271_MAGIC_VALUE = 0x1626ba7e; + + function getChainID() internal pure returns (uint256) { + uint256 id; + assembly { + id := chainid() + } + return id; + } + constructor(address _instaIndex) { instaIndex = _instaIndex; } @@ -31,6 +58,8 @@ contract Record is Constants { event LogEnableUser(address indexed user); event LogDisableUser(address indexed user); event LogBetaMode(bool indexed beta); + event LogSignedMessage(bytes32 indexed message); + event LogRemoveSignedMessage(bytes32 indexed message); /** * @dev Check for Auth if enabled. @@ -47,6 +76,29 @@ contract Record is Constants { return _beta; } + /** + * @dev Returns the domain separator + */ + function domainSeparatorV4() public view returns (bytes32) { + return + keccak256( + abi.encode( + TYPE_HASH, + DOMAIN_SEPARATOR_NAME_HASHED, + DOMAIN_SEPARATOR_VERSION_HASHED, + getChainID(), + address(this) + ) + ); + } + + /** + * @dev Check is `message` is signed or not + */ + function isSignedMessage(bytes32 message) public view returns (bool) { + return _signedMessages[message]; + } + /** * @dev Enable New User. * @param user Owner address @@ -119,6 +171,61 @@ contract Record is Constants { ) external returns (bytes4) { return 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) } + + /** + * @dev Marks a bytes32 `message` (signature digest) as signed, making it verifiable by EIP-1271 `isValidSignature()`. + * @param message data hash to be allow-listed as signed. Input `message` is hashed with `domainSeparatorV4()` according to EIP712 typed data (`EIP1271_TYPE_HASH`) + */ + function signMessage(bytes32 message) external { + require(msg.sender == address(this), "not-self"); + + // hashing with domain separator mitigates any potential replaying on other networks or other Avocados of the same owner + message = ECDSA.toTypedDataHash( + domainSeparatorV4(), + keccak256(abi.encode(EIP1271_TYPE_HASH, message)) + ); + + _signedMessages[message] = true; + + emit LogSignedMessage(message); + } + + /** + * @dev Removes a previously `signMessage()` signed bytes32 `message_` (signature digest). + * @param message data hash to be removed from allow-listed signatures + */ + function removeSignedMessage(bytes32 message) external { + require(msg.sender == address(this), "not-self"); + + delete _signedMessages[message]; + + emit LogRemoveSignedMessage(message); + } + + /** + * @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) { + // hashing with domain separator mitigates any potential replaying on other networks or other Avocados of the same owner + hash = ECDSA.toTypedDataHash( + domainSeparatorV4(), + keccak256(abi.encode(EIP1271_TYPE_HASH, hash)) + ); + + if (signature.length == 0) { + // must be pre-allow-listed via `signMessage()` method + require(_signedMessages[hash], "invalid-signed-message"); + } else { + address signer = ECDSA.recover(hash, signature); + + require(_auth[signer], "invalid-EIP-1271-signature"); + } + + return EIP1271_MAGIC_VALUE; + } + } contract InstaDefaultImplementation is Record { diff --git a/contracts/v2/accounts/variables.sol b/contracts/v2/accounts/variables.sol index a30e862..bb653b9 100644 --- a/contracts/v2/accounts/variables.sol +++ b/contracts/v2/accounts/variables.sol @@ -5,4 +5,6 @@ contract Variables { mapping (address => bool) internal _auth; // enable beta mode to access all the beta features. bool internal _beta; + // signed message for EIP-1271 + mapping (bytes32 => bool) internal _signedMessages; } \ No newline at end of file