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

Support EIP-1271 #9

Merged
merged 4 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
83 changes: 65 additions & 18 deletions src/EncumberableToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ contract EncumberableToken is ERC20, IERC20Permit, IERC7246 {
/// @dev The EIP-712 typehash for the contract's domain
bytes32 internal constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

/// @dev The magic value that a contract's `isValidSignature(bytes32 hash, bytes signature)` function should return for a valid signature
/// See https://eips.ethereum.org/EIPS/eip-1271
bytes4 internal constant EIP1271_MAGIC_VALUE = 0x1626ba7e;

/// @notice Number of decimals used for the user represenation of the token
uint8 private immutable _decimals;

Expand Down Expand Up @@ -243,18 +247,16 @@ contract EncumberableToken is ERC20, IERC20Permit, IERC7246 {
bytes32 r,
bytes32 s
) external {
require(uint256(s) <= MAX_VALID_ECDSA_S, "Invalid value s");
// v ∈ {27, 28} (source: https://ethereum.github.io/yellowpaper/paper.pdf #308)
require(v == 27 || v == 28, 'Invalid value v');
require(block.timestamp < expiry, "Signature expired");
uint nonce = nonces[owner];
bytes32 structHash = keccak256(abi.encode(AUTHORIZATION_TYPEHASH, owner, spender, amount, nonce, expiry));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), structHash));
address signatory = ecrecover(digest, v, r, s);
require(signatory != address(0), 'Bad signatory');
require(owner == signatory, 'Bad signatory');
require(block.timestamp < expiry, 'Signature expired');
nonces[signatory]++;
_approve(owner, spender, amount);
if (isValidSignature(owner, digest, v, r, s)) {
nonces[owner]++;
_approve(owner, spender, amount);
} else {
revert("Bad signatory");
}
}

/**
Expand All @@ -276,18 +278,63 @@ contract EncumberableToken is ERC20, IERC20Permit, IERC7246 {
bytes32 r,
bytes32 s
) external {
require(uint256(s) <= MAX_VALID_ECDSA_S, "Invalid value s");
// v ∈ {27, 28} (source: https://ethereum.github.io/yellowpaper/paper.pdf #308)
require(v == 27 || v == 28, 'Invalid value v');
require(block.timestamp < expiry, "Signature expired");
uint nonce = nonces[owner];
bytes32 structHash = keccak256(abi.encode(ENCUMBER_TYPEHASH, owner, taker, amount, nonce, expiry));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), structHash));
address signatory = ecrecover(digest, v, r, s);
require(signatory != address(0), 'Bad signatory');
require(owner == signatory, 'Bad signatory');
require(block.timestamp < expiry, 'Signature expired');
nonces[signatory]++;
_encumber(owner, taker, amount);
if (isValidSignature(owner, digest, v, r, s)) {
nonces[owner]++;
_encumber(owner, taker, amount);
} else {
revert("Bad signatory");
}
}

/**
* @notice Checks if a signature is valid
* @dev Supports EIP-1271 signatures for smart contracts
* @param signer The address that signed the signature
* @param digest The hashed message that is signed
* @param v The recovery byte of the signature
* @param r Half of the ECDSA signature pair
* @param s Half of the ECDSA signature pair
* @return bool Whether the signature is valid
*/
function isValidSignature(
kevincheng96 marked this conversation as resolved.
Show resolved Hide resolved
address signer,
bytes32 digest,
uint8 v,
bytes32 r,
bytes32 s
) internal view returns (bool) {
if (hasCode(signer)) {
bytes memory signature = abi.encodePacked(r, s, v);
(bool success, bytes memory data) = signer.staticcall(
abi.encodeWithSelector(EIP1271_MAGIC_VALUE, digest, signature)
);
require(success == true, "Call to verify EIP1271 signature failed");
bytes4 returnValue = abi.decode(data, (bytes4));
return returnValue == EIP1271_MAGIC_VALUE;
} else {
require(uint256(s) <= MAX_VALID_ECDSA_S, "Invalid value s");
// v ∈ {27, 28} (source: https://ethereum.github.io/yellowpaper/paper.pdf #308)
require(v == 27 || v == 28, "Invalid value v");
address signatory = ecrecover(digest, v, r, s);
require(signatory != address(0), "Bad signatory");
require(signatory == signer, "Bad signatory");
return true;
}
}

/**
* @notice Checks if an address has code deployed to it
* @param addr The address to check
* @return bool Whether the address contains code
*/
function hasCode(address addr) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(addr) }
return size > 0;
}

/**
Expand Down
65 changes: 65 additions & 0 deletions src/test/EIP1271Signer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.15;

contract EIP1271Signer {
bytes4 internal constant EIP1271_MAGIC_VALUE = 0x1626ba7e;

address public owner;

constructor(address _owner) {
owner = _owner;
}

function isValidSignature(bytes32 messageHash, bytes memory signature) external view returns (bytes4) {
if (recoverSigner(messageHash, signature) == owner) {
return EIP1271_MAGIC_VALUE;
} else {
return 0xffffffff;
}
}

function recoverSigner(bytes32 messageHash, bytes memory signature) internal pure returns (address) {
require(signature.length == 65, "SignatureValidator#recoverSigner: invalid signature length");

bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(signature, 32))
s := mload(add(signature, 64))
v := and(mload(add(signature, 65)), 255)
}

// 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
kevincheng96 marked this conversation as resolved.
Show resolved Hide resolved

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
address signer = ecrecover(messageHash, v, r, s);

// Prevent signer from being 0x0
require(
signer != address(0x0),
"SignatureValidator#recoverSigner: INVALID_SIGNER"
);

return signer;
}
}
Loading