Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-1271 #80

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions contracts/v2/accounts/default/ECDSA.sol
Original file line number Diff line number Diff line change
@@ -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 {
bergben marked this conversation as resolved.
Show resolved Hide resolved
/**
* @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));
}
}
107 changes: 107 additions & 0 deletions contracts/v2/accounts/default/implementation_default.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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) {
thrilok209 marked this conversation as resolved.
Show resolved Hide resolved
uint256 id;
assembly {
id := chainid()
}
return id;
}

constructor(address _instaIndex) {
instaIndex = _instaIndex;
}
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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 DSA of the same owner
hash = ECDSA.toTypedDataHash(
domainSeparatorV4(),
keccak256(abi.encode(EIP1271_TYPE_HASH, hash))
);
KaymasJain marked this conversation as resolved.
Show resolved Hide resolved

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;
KaymasJain marked this conversation as resolved.
Show resolved Hide resolved
}

}

contract InstaDefaultImplementation is Record {
Expand Down
2 changes: 2 additions & 0 deletions contracts/v2/accounts/variables.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Loading