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

Cryptography library improvements #289

Draft
wants to merge 28 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
97cf3c7
add scaffold-contract task
ItsNickBarry Mar 19, 2025
619f183
fix scaffold interfaces
ItsNickBarry Mar 19, 2025
d438270
add EIP712Constants generator task
ItsNickBarry Mar 19, 2025
f6ff206
generate domain separator calculation functions
ItsNickBarry Mar 19, 2025
11b3cbb
use assembly for chainid and address lookup
ItsNickBarry Mar 19, 2025
136f3bc
remove assembly-sourced values from calculator parameters
ItsNickBarry Mar 19, 2025
4ea8f31
restrict calculator functions to pure where possible
ItsNickBarry Mar 19, 2025
72e3739
generate calculator comments
ItsNickBarry Mar 19, 2025
695cd94
generate EIP712Constants tests
ItsNickBarry Mar 19, 2025
2a0091d
use ejs for templating
ItsNickBarry Mar 20, 2025
0b0d185
remove template whitespace
ItsNickBarry Mar 20, 2025
02edad1
fix test name spelling
ItsNickBarry Mar 20, 2025
ab23b1e
mark ECDSA assembly as memory-safe
ItsNickBarry Mar 20, 2025
e5bff61
test FungibleTokenPermit nonces
ItsNickBarry Mar 20, 2025
cc7dfa2
add ECDSA#tryRecover functions using error function passing
ItsNickBarry Mar 20, 2025
c4f00c0
fix ECDSA comments
ItsNickBarry Mar 20, 2025
a1f871a
encapsulate error function passing within internally accessible funct…
ItsNickBarry Mar 20, 2025
d3dad19
test tryRecover functions
ItsNickBarry Mar 20, 2025
c71230a
fix whitespace and add comments to error wrapper functions
ItsNickBarry Mar 20, 2025
9d104ac
clean up generated EIP712 whitespace
ItsNickBarry Mar 20, 2025
3853ed6
overwrite EIP712 library with procedurally generated version
ItsNickBarry Mar 20, 2025
ea376f3
reference domain hash constants in domain separator calculation funct…
ItsNickBarry Mar 20, 2025
b3ad2be
generate ERC5267 fields constants
ItsNickBarry Mar 20, 2025
a9bb1b8
test ERC5267 fields constants
ItsNickBarry Mar 20, 2025
06dc3d8
referene ERC5267 fields constant in FungibleTokenPermit
ItsNickBarry Mar 20, 2025
b7fb3e9
add toEIP712RecoverableHash utility function
ItsNickBarry Mar 20, 2025
c6954a3
rename function to toEthSignRecoverableHash and use assembly for hashing
ItsNickBarry Mar 20, 2025
a61388f
test toEIP712RecoverableHash
ItsNickBarry Mar 20, 2025
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
193 changes: 174 additions & 19 deletions contracts/cryptography/ECDSA.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ library ECDSA {
error ECDSA__InvalidV();

/**
* @notice recover signer of hashed message from signature
* @notice recover signer of hashed message from signature, reverting on failure
* @param hash hashed data payload
* @param signature signed data payload
* @return signer recovered message signer
Expand All @@ -22,35 +22,107 @@ library ECDSA {
bytes32 hash,
bytes memory signature
) internal pure returns (address signer) {
if (signature.length != 65) revert ECDSA__InvalidSignatureLength();
function() pure errorFn;

(signer, errorFn) = _tryRecover(hash, signature);

if (signer == address(0)) errorFn();
}

/**
* @notice recover signer of hashed message from signature v, r, and s values, reverting on failure
* @param hash hashed data payload
* @param v signature "v" value
* @param r signature "r" value
* @param s signature "s" value
* @return signer recovered message signer
*/
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address signer) {
function() pure errorFn;

(signer, errorFn) = _tryRecover(hash, v, r, s);

if (signer == address(0)) errorFn();
}

/**
* @notice attempt to recover signer of hashed message from signature
* @param hash hashed data payload
* @param signature signed data payload
* @return signer recovered message signer (zero address on recovery failure)
*/
function tryRecover(
bytes32 hash,
bytes memory signature
) internal pure returns (address signer) {
(signer, ) = _tryRecover(hash, signature);
}

/**
* @notice attempt to recover signer of hashed message from signature v, r, and s values
* @param hash hashed data payload
* @param v signature "v" value
* @param r signature "r" value
* @param s signature "s" value
* @return signer recovered message signer (zero address on recovery failure)
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address signer) {
(signer, ) = _tryRecover(hash, v, r, s);
}

/**
* @notice attempt to recover signer of hashed message from signature
* @param hash hashed data payload
* @param signature signed data payload
* @return signer recovered message signer (zero address on recovery failure)
* @return errorFn wrapper function around custom error revert
*/
function _tryRecover(
bytes32 hash,
bytes memory signature
) private pure returns (address signer, function() pure errorFn) {
if (signature.length != 65) {
return (address(0), _revert_ECDSA__InvalidSignatureLength);
}

bytes32 r;
bytes32 s;
uint8 v;

assembly {
assembly ('memory-safe') {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}

signer = recover(hash, v, r, s);
(signer, errorFn) = _tryRecover(hash, v, r, s);
}

/**
* @notice recover signer of hashed message from signature v, r, and s values
* @notice attempt to recover signer of hashed message from signature v, r, and s values
* @param hash hashed data payload
* @param v signature "v" value
* @param r signature "r" value
* @param s signature "s" value
* @return signer recovered message signer
* @return signer recovered message signer (zero address on recovery failure)
* @return errorFn wrapper function around custom error revert
*/
function recover(
function _tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address signer) {
) private pure returns (address signer, function() pure errorFn) {
// 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 @@ -63,24 +135,107 @@ library ECDSA {
if (
uint256(s) >
0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
) revert ECDSA__InvalidS();
if (v != 27 && v != 28) revert ECDSA__InvalidV();
) return (address(0), _revert_ECDSA__InvalidS);

if (v != 27 && v != 28) return (address(0), _revert_ECDSA__InvalidV);

// If the signature is valid (and not malleable), return the signer address
signer = ecrecover(hash, v, r, s);
if (signer == address(0)) revert ECDSA__InvalidSignature();

if (signer == address(0))
return (address(0), _revert_ECDSA__InvalidSignature);
}

/**
* @notice generate an "Ethereum Signed Message" in the format returned by the eth_sign JSON-RPC method
* @param hash hashed data payload
* @return signedMessage signed message hash
* @param payloadHash hashed data payload
* @return recoverableHash hash to validate against signature via ECDSA function
*/
function toEthSignRecoverableHash(
bytes32 payloadHash
) internal pure returns (bytes32 recoverableHash) {
assembly {
// assembly block equivalent to:
//
// recoverableHash = keccak256(
// abi.encodePacked(
// '\x19Ethereum Signed Message:\n32',
// payloadHash
// )
// );

// load free memory pointer
let pointer := mload(64)

mstore(pointer, '\x19Ethereum Signed Message:\n32')
mstore(add(pointer, 28), payloadHash)

recoverableHash := keccak256(pointer, 60)
}
}

/**
* @notice generate an EIP712 signable hash of typed structured data
* @param domainSeparator EIP712 domain separator
* @param structHash hash of underlying signed data generated through the EIP712 "hashStruct" process
* @return recoverableHash hash to validate against signature via ECDSA function
*/
function toEIP712RecoverableHash(
bytes32 domainSeparator,
bytes32 structHash
) internal pure returns (bytes32 recoverableHash) {
assembly {
// assembly block equivalent to:
//
// recoverableHash = keccak256(
// abi.encodePacked(
// uint16(0x1901),
// domainSeparator,
// structHash
// )
// );

// load free memory pointer
let pointer := mload(64)

// this magic value is the EIP-191 signed data header, consisting of
// the hardcoded 0x19 and the one-byte version 0x01
mstore(
pointer,
0x1901000000000000000000000000000000000000000000000000000000000000
)
mstore(add(pointer, 2), domainSeparator)
mstore(add(pointer, 34), structHash)

recoverableHash := keccak256(pointer, 66)
}
}

/**
* @notice wrapper function for passing custom errors internally
*/
function _revert_ECDSA__InvalidS() private pure {
revert ECDSA__InvalidS();
}

/**
* @notice wrapper function for passing custom errors internally
*/
function _revert_ECDSA__InvalidSignature() private pure {
revert ECDSA__InvalidSignature();
}

/**
* @notice wrapper function for passing custom errors internally
*/
function _revert_ECDSA__InvalidSignatureLength() private pure {
revert ECDSA__InvalidSignatureLength();
}

/**
* @notice wrapper function for passing custom errors internally
*/
function toEthSignedMessageHash(
bytes32 hash
) internal pure returns (bytes32 signedMessage) {
signedMessage = keccak256(
abi.encodePacked('\x19Ethereum Signed Message:\n32', hash)
);
function _revert_ECDSA__InvalidV() private pure {
revert ECDSA__InvalidV();
}
}
Loading