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

💥 Add NIST P-256 (a.k.a. secp256r1) ECDSA Verification Function #243

Merged
merged 6 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
190 changes: 99 additions & 91 deletions .gas-snapshot

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
[submodule "lib/erc4626-tests"]
path = lib/erc4626-tests
url = https://github.com/a16z/erc4626-tests.git
[submodule "lib/FreshCryptoLib"]
path = lib/FreshCryptoLib
url = https://github.com/rdubois-crypto/FreshCryptoLib.git
[submodule "lib/solidity-bytes-utils"]
path = lib/solidity-bytes-utils
url = https://github.com/GNSPS/solidity-bytes-utils.git
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ lib/forge-std
lib/properties
lib/create-util
lib/erc4626-tests
lib/FreshCryptoLib
lib/solidity-bytes-utils
lib/openzeppelin-contracts
echidna-corpus
Expand Down
1 change: 1 addition & 0 deletions .solhintignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ lib/forge-std
lib/properties
lib/create-util
lib/erc4626-tests
lib/FreshCryptoLib
lib/solidity-bytes-utils
lib/openzeppelin-contracts
echidna-corpus
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## [`0.1.0`](https://github.com/pcaversaccio/snekmate/releases/tag/v0.1.0) (Unreleased)

### ♻️ Refactoring
### 💥 New Features

- **Authentication**
- [`ownable`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/src/snekmate/auth/ownable.vy): Make `ownable` module-friendly. ([#218](https://github.com/pcaversaccio/snekmate/pull/218))
Expand All @@ -23,6 +23,7 @@
- [`create_address`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/src/snekmate/utils/create_address.vy): Make `create_address` module-friendly. ([#224](https://github.com/pcaversaccio/snekmate/pull/224))
- [`create2_address`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/src/snekmate/utils/create2_address.vy): Make `create2_address` module-friendly. ([#225](https://github.com/pcaversaccio/snekmate/pull/225))
- [`ecdsa`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/src/snekmate/utils/ecdsa.vy): Make `ecdsa` module-friendly. ([#227](https://github.com/pcaversaccio/snekmate/pull/227))
- [`p256`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/src/snekmate/utils/p256.vy): Add NIST P-256 (a.k.a. secp256r1) ECDSA verification function. ([#243](https://github.com/pcaversaccio/snekmate/pull/243))
- [`message_hash_utils`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/src/snekmate/utils/message_hash_utils.vy): Move the `ecdsa` message hash methods to a separate `message_hash_utils` library module. ([#227](https://github.com/pcaversaccio/snekmate/pull/227))
- [`signature_checker`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/src/snekmate/utils/signature_checker.vy): Make `signature_checker` module-friendly. ([#228](https://github.com/pcaversaccio/snekmate/pull/228))
- [`eip712_domain_separator`](https://github.com/pcaversaccio/snekmate/blob/v0.1.0/src/snekmate/utils/eip712_domain_separator.vy): Make `eip712_domain_separator` module-friendly. ([#229](https://github.com/pcaversaccio/snekmate/pull/229))
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ src
├── batch_distributor — "Batch Sending Both Native and ERC-20 Tokens"
├── create_address — "`CREATE` EVM Opcode Utility Function for Address Calculation"
├── create2_address — "`CREATE2` EVM Opcode Utility Functions for Address Calculations"
├── ecdsa — "Elliptic Curve Digital Signature Algorithm (ECDSA) Functions"
├── ecdsa — "Elliptic Curve Digital Signature Algorithm (ECDSA) Secp256k1-Based Functions"
├── p256 — "Elliptic Curve Digital Signature Algorithm (ECDSA) Secp256r1-Based Functions"
├── message_hash_utils — "Signature Message Hash Utility Functions"
├── signature_checker — "ECDSA and EIP-1271 Signature Verification Functions"
├── eip712_domain_separator — "EIP-712 Domain Separator"
Expand All @@ -78,6 +79,7 @@ src
├── create_address_mock — "`create_address` Module Reference Implementation"
├── create2_address_mock — "`create2_address` Module Reference Implementation"
├── ecdsa_mock — "`ecdsa` Module Reference Implementation"
├── p256_mock — "`p256` Module Reference Implementation"
├── message_hash_utils_mock — "`message_hash_utils` Module Reference Implementation"
├── signature_checker_mock — "`signature_checker` Module Reference Implementation"
├── eip712_domain_separator_mock — "`eip712_domain_separator` Module Reference Implementation"
Expand Down Expand Up @@ -160,6 +162,7 @@ This repository contains [Foundry](https://github.com/foundry-rs/foundry)-based
| `create_address` | ✅ | ✅ | ❌ |
| `create2_address` | ✅ | ✅ | ❌ |
| `ecdsa` | ✅ | ✅ | ❌ |
| `p256` | ✅ | ✅ | ❌ |
| `message_hash_utils` | ✅ | ✅ | ❌ |
| `signature_checker` | ✅ | ✅ | ❌ |
| `eip712_domain_separator` | ✅ | ✅ | ❌ |
Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module.exports = [
"lib/properties/**",
"lib/create-util/**",
"lib/erc4626-tests/**",
"lib/FreshCryptoLib/**",
"lib/solidity-bytes-utils/**",
"lib/openzeppelin-contracts/**",
"echidna-corpus/**",
Expand Down
1 change: 1 addition & 0 deletions lib/FreshCryptoLib
Submodule FreshCryptoLib added at 76f3f1
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ forge-std/=lib/forge-std/src/
erc4626-tests/=lib/erc4626-tests/
properties/=lib/properties/contracts/
create-util/=lib/create-util/contracts/
fresh-crypto-lib=lib/FreshCryptoLib/solidity/src/
openzeppelin/=lib/openzeppelin-contracts/contracts/
solidity-bytes-utils/=lib/solidity-bytes-utils/contracts/
18 changes: 11 additions & 7 deletions src/snekmate/utils/ecdsa.vy
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# pragma version ~=0.4.0rc3
"""
@title Elliptic Curve Digital Signature Algorithm (ECDSA) Functions
@title Elliptic Curve Digital Signature Algorithm (ECDSA) Secp256k1-Based Functions
@custom:contract-name ecdsa
@license GNU Affero General Public License v3.0 only
@author pcaversaccio
@notice These functions can be used to verify that a message was signed by
the holder of the private key of a given address. The implementation
is inspired by OpenZeppelin's implementation here:
the holder of the private key of a given address. All cryptographic
calculations are based on the Ethereum-native secp256k1 elliptic curve
(see https://en.bitcoin.it/wiki/Secp256k1). For verification functions
based on the NIST P-256 elliptic curve (also known as secp256r1), see
the {p256} contract. The implementation is inspired by OpenZeppelin's
implementation here:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol.
@custom:security Signatures must not be used as unique identifiers since the
`ecrecover` EVM precompile allows for malleable (non-unique)
Expand All @@ -17,8 +21,8 @@


# @dev Constants used as part of the ECDSA recovery function.
_MALLEABILITY_THRESHOLD: constant(bytes32) = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
_SIGNATURE_INCREMENT: constant(bytes32) = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
_MALLEABILITY_THRESHOLD: constant(uint256) = 57_896_044_618_658_097_711_785_492_504_343_953_926_418_782_139_537_452_191_302_581_570_759_080_747_168
_SIGNATURE_INCREMENT: constant(uint256) = 57_896_044_618_658_097_711_785_492_504_343_953_926_634_992_332_820_282_019_728_792_003_956_564_819_967


@deploy
Expand Down Expand Up @@ -98,7 +102,7 @@ def _try_recover_r_vs(hash: bytes32, r: uint256, vs: uint256) -> address:
@param vs The secp256k1 32-byte short signature field of `v` and `s`.
@return address The recovered 20-byte signer address.
"""
s: uint256 = vs & convert(_SIGNATURE_INCREMENT, uint256)
s: uint256 = vs & _SIGNATURE_INCREMENT
# We do not check for an overflow here since the shift operation
# `vs >> 255` results essentially in a `uint8` type (`0` or `1`) and
# we use `uint256` as result type.
Expand All @@ -123,7 +127,7 @@ def _try_recover_vrs(hash: bytes32, v: uint256, r: uint256, s: uint256) -> addre
@param s The secp256k1 32-byte signature parameter `s`.
@return address The recovered 20-byte signer address.
"""
assert s <= convert(_MALLEABILITY_THRESHOLD, uint256), "ecdsa: invalid signature `s` value"
assert s <= _MALLEABILITY_THRESHOLD, "ecdsa: invalid signature `s` value"

signer: address = ecrecover(hash, v, r, s)
assert signer != empty(address), "ecdsa: invalid signature"
Expand Down
43 changes: 43 additions & 0 deletions src/snekmate/utils/mocks/p256_mock.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# pragma version ~=0.4.0rc3
"""
@title `p256` Module Reference Implementation
@custom:contract-name p256_mock
@license GNU Affero General Public License v3.0 only
@author pcaversaccio
"""


# @dev We import the `p256` module.
# @notice Please note that the `p256` module
# is stateless and therefore does not require
# the `initializes` keyword for initialisation.
from .. import p256 as p2


@deploy
@payable
def __init__():
"""
@dev To omit the opcodes for checking the `msg.value`
in the creation-time EVM bytecode, the constructor
is declared as `payable`.
"""
pass


@external
@view
def verify_sig(hash: bytes32, r: uint256, s: uint256, qx: uint256, qy: uint256) -> bool:
"""
@dev Verifies the signature of a message digest `hash`
based on the secp256r1 signature parameters `r` and
`s`, and the public key coordinates `qx` and `qy`.
@param hash The 32-byte message digest that was signed.
@param r The secp256r1 32-byte signature parameter `r`.
@param s The secp256r1 32-byte signature parameter `s`.
@param qx The 32-byte public key coordinate `qx`.
@param qy The 32-byte public key coordinate `qy`.
@return bool The verification whether the signature is
authentic or not.
"""
return p2._verify_sig(hash, r, s, qx, qy)
8 changes: 4 additions & 4 deletions src/snekmate/utils/multicall.vy
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def _multicall(data: DynArray[Batch, _DYNARRAY_BOUND]) -> DynArray[Result, _DYNA
success = True
results.append(Result(success=success, return_data=return_data))
else:
success, return_data = \
success, return_data =\
raw_call(batch.target, batch.calldata, max_outsize=255, revert_on_failure=False)
results.append(Result(success=success, return_data=return_data))
return results
Expand Down Expand Up @@ -129,7 +129,7 @@ def _multicall_value(data: DynArray[BatchValue, _DYNARRAY_BOUND]) -> DynArray[Re
success = True
results.append(Result(success=success, return_data=return_data))
else:
success, return_data = \
success, return_data =\
raw_call(batch.target, batch.calldata, max_outsize=255, value=msg_value, revert_on_failure=False)
results.append(Result(success=success, return_data=return_data))
assert msg.value == value_accumulator, "multicall: value mismatch"
Expand Down Expand Up @@ -165,7 +165,7 @@ def _multicall_self(data: DynArray[BatchSelf, _DYNARRAY_BOUND]) -> DynArray[Resu
success = True
results.append(Result(success=success, return_data=return_data))
else:
success, return_data = \
success, return_data =\
raw_call(self, batch.calldata, max_outsize=255, is_delegate_call=True, revert_on_failure=False)
results.append(Result(success=success, return_data=return_data))
return results
Expand All @@ -192,7 +192,7 @@ def _multistaticcall(data: DynArray[Batch, _DYNARRAY_BOUND]) -> DynArray[Result,
success = True
results.append(Result(success=success, return_data=return_data))
else:
success, return_data = \
success, return_data =\
raw_call(batch.target, batch.calldata, max_outsize=255, is_static_call=True, revert_on_failure=False)
results.append(Result(success=success, return_data=return_data))
return results
Loading