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

Optimize EOA signature verification #2661

Merged
merged 16 commits into from
Aug 6, 2021
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
56 changes: 47 additions & 9 deletions contracts/utils/cryptography/ECDSA.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pragma solidity ^0.8.0;
library ECDSA {
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
* `signature` or error string. 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
Expand All @@ -23,7 +23,7 @@ library ECDSA {
* 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) {
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, string memory) {
// Divide the signature in r, s and v variables
bytes32 r;
bytes32 s;
Expand Down Expand Up @@ -52,17 +52,39 @@ library ECDSA {
v := add(shr(255, vs), 27)
}
} else {
revert("ECDSA: invalid signature length");
return (address(0), "ECDSA: invalid signature length");
}

return recover(hash, v, r, s);
return (recover(hash, v, r, s), "");
Amxx marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* @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) {
(address recovered, string memory error) = tryRecover(hash, signature);
if (bytes(error).length > 0) {
revert(error);
}
return recovered;
}

/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, string memory) {
// 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
Expand All @@ -73,13 +95,29 @@ library ECDSA {
// 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 (v != 27 && v != 28) {
return (address(0), "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");
if (signer == address(0)) {
return (address(0), "ECDSA: invalid signature");
}

return signer;
return (signer, "");
}

/**
* @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) {
(address recovered, string memory error) = tryRecover(hash, v, r, s);
if (bytes(error).length > 0) {
revert(error);
}
return recovered;
}

/**
Expand Down
14 changes: 7 additions & 7 deletions contracts/utils/cryptography/SignatureChecker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import "../../interfaces/IERC1271.sol";
*/
library SignatureChecker {
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
if (Address.isContract(signer)) {
try IERC1271(signer).isValidSignature(hash, signature) returns (bytes4 magicValue) {
return magicValue == IERC1271(signer).isValidSignature.selector;
} catch {
return false;
if (signature.length == 64 || signature.length == 65) {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
(address recovered, string memory error) = ECDSA.tryRecover(hash, signature);
if (bytes(error).length == 0 && recovered == signer) {
return true;
}
} else {
return ECDSA.recover(hash, signature) == signer;
}

(bool success, bytes memory result) = signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature));
return (success && result.length == 32 && abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);
}
}