Last active
June 3, 2021 20:24
-
-
Save 3esmit/ef9fbaaab13b62c1b1b459a170db3caa to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity >=0.5.0 <0.6.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. | |
* | |
* NOTE: This call _does not revert_ if the signature is invalid, or | |
* if the signer is otherwise unable to be retrieved. In those scenarios, | |
* the zero address is returned. | |
* | |
* 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) { | |
return (address(0)); | |
} | |
// 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))) | |
} | |
// 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. | |
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { | |
return address(0); | |
} | |
if (v != 27 && v != 28) { | |
return address(0); | |
} | |
// If the signature is valid (and not malleable), return the signer address | |
return ecrecover(hash, v, r, s); | |
} | |
/** | |
* @dev Returns an Ethereum Signed Message, created from a `hash`. This | |
* replicates the behavior of the | |
* https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`personal_sign`] | |
* JSON-RPC method. | |
* | |
* 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)); | |
return keccak256(abi.encodePacked(byte(0x19), bytes("Ethereum Signed Message:\n32"), abi.encodePacked(hash))); | |
} | |
/** | |
* @dev Returns an ERC191 Signed Message, created from a `hash`. This | |
* replicates the behavior of the | |
* https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_signTypedData`] | |
* JSON-RPC method. | |
* | |
* See {recover}. | |
*/ | |
function toERC191SignedMessage(bytes memory data) internal pure returns (bytes32) { | |
return keccak256(abi.encodePacked(byte(0x19), byte(0x0), data)); | |
} | |
} | |
/** | |
* @notice ERC-1271: Standard Signature Validation Method for Contracts | |
*/ | |
contract Signer { | |
//bytes4(keccak256("isValidSignature(bytes,bytes)") | |
bytes4 constant internal MAGICVALUE = 0x20c13b0b; | |
/** | |
* @dev Should return whether the signature provided is valid for the provided data | |
* @param _data Arbitrary length data signed on the behalf of address(this) | |
* @param _signature Signature byte array associated with _data | |
* | |
* MUST return the bytes4 magic value 0x20c13b0b 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( | |
bytes memory _data, | |
bytes memory _signature | |
) | |
public | |
view | |
returns (bytes4 magicValue); | |
} | |
/** | |
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) | |
*/ | |
contract MultisigAccount is Signer { | |
string internal constant ERR_BAD_SIGNER = "Bad signer"; | |
bytes4 internal constant MSG_EXECUTE_PREFIX = bytes4( | |
keccak256("execute(uint256,address,uint256)") | |
); | |
uint256 nonce = 0; | |
uint256 available = 0; | |
uint256 required = 0; | |
mapping(address => bool) isKey; | |
modifier self { | |
require(msg.sender == address(this), "Unauthorized"); | |
_; | |
} | |
constructor(address[] memory _keys, uint256 _required) public { | |
available = _keys.length; | |
required = _required; | |
for(uint i = 0; i < available; i++) { | |
address key = _keys[i]; | |
require(isKey[key] == false, "Duplicated"); | |
isKey[key] = true; | |
} | |
} | |
function callSigned( | |
address _to, | |
uint256 _value, | |
bytes calldata _data, | |
bytes calldata _signature | |
) | |
external | |
returns (bool success, bytes memory returndata) | |
{ | |
require( | |
isValidSignature( | |
abi.encodePacked( | |
address(this), | |
MSG_EXECUTE_PREFIX, | |
nonce, | |
_to, | |
_value, | |
_data | |
), | |
_signature | |
) == MAGICVALUE, | |
ERR_BAD_SIGNER | |
); | |
nonce++; | |
(success, returndata) = _to.call.value(_value)(_data); | |
} | |
function setKey(address key, bool isValid) external self { | |
require(key != address(0), "Invalid address"); | |
require(isKey[key] != isValid, "Already set"); | |
isKey[key] = isValid; | |
isValid ? available++ : available--; | |
require(available >= required, "Reduce required first"); | |
} | |
function setRequired(uint256 _required) external self { | |
require(available >= _required, "No enough keys"); | |
required = _required; | |
} | |
function isValidSignature( | |
bytes memory _data, | |
bytes memory _signature | |
) | |
public | |
view | |
returns (bytes4 magicValue) | |
{ | |
magicValue = 0xffffffff; | |
uint _amountSignatures = _signature.length / 65; | |
if(_amountSignatures != required) { | |
return magicValue; | |
} | |
address lastSigner = address(0); | |
uint8 v; | |
bytes32 r; | |
bytes32 s; | |
bytes32 dataHash = ECDSA.toERC191SignedMessage(_data); | |
for (uint256 i = 0; i < _amountSignatures; i++) { | |
assembly { | |
let signaturePos := mul(0x41, i) | |
r := mload(add(_signature, add(signaturePos, 0x20))) | |
s := mload(add(_signature, add(signaturePos, 0x40))) | |
v := and(mload(add(_signature, add(signaturePos, 0x41))), 0xff) | |
} | |
address signer = ecrecover(dataHash, v, r, s); | |
if (signer < lastSigner || !isKey[signer] ) { | |
return magicValue; | |
} | |
lastSigner = signer; | |
} | |
magicValue = MAGICVALUE; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity >=0.5.0 <0.6.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. | |
* | |
* NOTE: This call _does not revert_ if the signature is invalid, or | |
* if the signer is otherwise unable to be retrieved. In those scenarios, | |
* the zero address is returned. | |
* | |
* 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) { | |
return (address(0)); | |
} | |
// 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))) | |
} | |
// 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. | |
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { | |
return address(0); | |
} | |
if (v != 27 && v != 28) { | |
return address(0); | |
} | |
// If the signature is valid (and not malleable), return the signer address | |
return ecrecover(hash, v, r, s); | |
} | |
/** | |
* @dev Returns an Ethereum Signed Message, created from a `hash`. This | |
* replicates the behavior of the | |
* https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`personal_sign`] | |
* JSON-RPC method. | |
* | |
* 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)); | |
return keccak256(abi.encodePacked(byte(0x19), bytes("Ethereum Signed Message:\n32"), abi.encodePacked(hash))); | |
} | |
/** | |
* @dev Returns an ERC191 Signed Message, created from a `hash`. This | |
* replicates the behavior of the | |
* https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_signTypedData`] | |
* JSON-RPC method. | |
* | |
* See {recover}. | |
*/ | |
function toERC191SignedMessage(bytes memory data) internal pure returns (bytes32) { | |
return keccak256(abi.encodePacked(byte(0x19), byte(0x0), data)); | |
} | |
} | |
/** | |
* @notice ERC-1271: Standard Signature Validation Method for Contracts | |
*/ | |
contract Signer { | |
//bytes4(keccak256("isValidSignature(bytes,bytes)") | |
bytes4 constant internal MAGICVALUE = 0x20c13b0b; | |
/** | |
* @dev Should return whether the signature provided is valid for the provided data | |
* @param _data Arbitrary length data signed on the behalf of address(this) | |
* @param _signature Signature byte array associated with _data | |
* | |
* MUST return the bytes4 magic value 0x20c13b0b 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( | |
bytes memory _data, | |
bytes memory _signature | |
) | |
public | |
view | |
returns (bytes4 magicValue); | |
} | |
/** | |
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) | |
*/ | |
contract SimpleAccount is Signer { | |
string internal constant ERR_BAD_PARAMETER = "Bad parameter"; | |
string internal constant ERR_UNAUTHORIZED = "Unauthorized"; | |
string internal constant ERR_BAD_SIGNER = "Bad signer"; | |
bytes4 internal constant MSG_EXECUTE_PREFIX = bytes4( | |
keccak256("execute(uint256,address,uint256)") | |
); | |
uint256 nonce = 0; | |
address owner; | |
modifier self { | |
require(msg.sender == address(this), "Unauthorized"); | |
_; | |
} | |
constructor() public { | |
owner = msg.sender; | |
} | |
function callSigned( | |
address _to, | |
uint256 _value, | |
bytes calldata _data, | |
bytes calldata _signature | |
) | |
external | |
returns (bool success, bytes memory returndata) | |
{ | |
require( | |
isValidSignature( | |
abi.encodePacked( | |
address(this), | |
MSG_EXECUTE_PREFIX, | |
nonce, | |
_to, | |
_value, | |
_data | |
), | |
_signature | |
) == MAGICVALUE, | |
ERR_BAD_SIGNER | |
); | |
nonce++; | |
(success, returndata) = _to.call.value(_value)(_data); | |
} | |
/** | |
* @notice Replace owner address. | |
* @param newOwner address of externally owned account or ERC1271 contract to control this account | |
*/ | |
function changeOwner(address newOwner) | |
external | |
self | |
{ | |
require(newOwner != address(0), ERR_BAD_PARAMETER); | |
owner = newOwner; | |
} | |
/** | |
* @notice checks if owner signed `_data`. ERC1271 interface. | |
* @param _data Data signed | |
* @param _signature owner's signature(s) of data | |
*/ | |
function isValidSignature( | |
bytes memory _data, | |
bytes memory _signature | |
) | |
public | |
view | |
returns (bytes4 magicValue) | |
{ | |
if(isContract(owner)){ | |
return Signer(owner).isValidSignature(_data, _signature); | |
} else { | |
return owner == ECDSA.recover(ECDSA.toERC191SignedMessage(_data), _signature) ? MAGICVALUE : bytes4(0xffffffff); | |
} | |
} | |
/** | |
* @dev Internal function to determine if an address is a contract | |
* @param _target The address being queried | |
* @return True if `_addr` is a contract | |
*/ | |
function isContract(address _target) internal view returns(bool result) { | |
assembly { | |
result := gt(extcodesize(_target), 0) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment