Skip to content

Instantly share code, notes, and snippets.

@3esmit
Last active June 3, 2021 20:24
Show Gist options
  • Save 3esmit/ef9fbaaab13b62c1b1b459a170db3caa to your computer and use it in GitHub Desktop.
Save 3esmit/ef9fbaaab13b62c1b1b459a170db3caa to your computer and use it in GitHub Desktop.
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;
}
}
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