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 is_valid_secp256_signature support #1189

Merged
merged 10 commits into from
Oct 29, 2024
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- `is_valid_p256_signature` utility function to `openzeppelin_account::utils::signature` (#1189)
- `Secp256r1KeyPair` type and helpers to `openzeppelin_testing::signing` (#1189)
- Embeddable impls for ERC2981 component (#1173)
- `ERC2981Info` with read functions for discovering the component's state
- `ERC2981AdminOwnable` providing admin functions for a token that implements Ownable component
- `ERC2981AdminAccessControl` providing admin functions for a token that implements AccessControl component

### Changed (Breaking)

- Refactor `openzeppelin_account::utils::secp256k1` module to `openzeppelin_account::utils::secp256_point` (#1189)
- `Secp256k1PointStorePacking` replaced by a generic `Secp256PointStorePacking`
- `Secp256k1PointPartialEq` replaced by a generic `Secp256PointPartialEq`
- `DebugSecp256k1Point` replaced by a generic `DebugSecp256Point`
- Apply underscore pattern to the internal functions of `ERC2981Component` to prevent collisions
with new external functions (#1173)

Expand Down
8 changes: 8 additions & 0 deletions docs/modules/ROOT/pages/api/testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

:stark: https://docs.starknet.io/architecture-and-concepts/cryptography/stark-curve/[Stark]
:secp256k1: https://github.com/starkware-libs/cairo/blob/main/corelib/src/starknet/secp256k1.cairo[Secp256k1]
:secp256r1: https://www.nervos.org/knowledge-base/what_is_secp256r1[Secp256r1]

This crate provides various helper functions for declaring, deploying,
and testing smart contracts using the `snforge` toolchain from Starknet Foundry.
Expand Down Expand Up @@ -267,6 +268,7 @@ A module offering utility functions for easier management of key pairs and signa
.Functions
* xref:#testing-signing-get_stark_keys_from[`++get_stark_keys_from(private_key)++`]
* xref:#testing-signing-get_secp256k1_keys_from[`++get_secp256k1_keys_from(private_key)++`]
* xref:#testing-signing-get_secp256r1_keys_from[`++get_secp256r1_keys_from(private_key)++`]

.Traits
* xref:#testing-signing-SerializedSigning[`++SerializedSigning++`]
Expand All @@ -287,6 +289,12 @@ Builds a {stark} key pair from a private key represented by a `felt252` value.

Builds a {secp256k1} key pair from a private key represented by a `u256` value.

[.contract-item]
[[testing-signing-get_secp256r1_keys_from]]
==== `[.contract-item-name]#++get_secp256r1_keys_from++#++(private_key: u256) → Secp256r1KeyPair++` [.item-kind]#function#

Builds a {secp256r1} key pair from a private key represented by a `u256` value.

[#testing-signing-Traits]
==== Traits

Expand Down
2 changes: 1 addition & 1 deletion packages/account/src/eth_account.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub mod EthAccountComponent {
use core::starknet::secp256_trait::Secp256PointTrait;
use crate::interface::EthPublicKey;
use crate::interface;
use crate::utils::secp256k1::Secp256k1PointStorePacking;
use crate::utils::secp256_point::Secp256PointStorePacking;
use crate::utils::{MIN_TRANSACTION_VERSION, QUERY_OFFSET};
use crate::utils::{execute_calls, is_valid_eth_signature};
use openzeppelin_introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait;
Expand Down
1 change: 1 addition & 0 deletions packages/account/src/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use starknet::account::Call;

pub type EthPublicKey = starknet::secp256k1::Secp256k1Point;
pub type P256PublicKey = starknet::secp256r1::Secp256r1Point;

pub const ISRC6_ID: felt252 = 0x2ceccef7f994940b3962a6c67e0ba4fcd37df7d131417c604f91e03caecc1cd;

Expand Down
2 changes: 1 addition & 1 deletion packages/account/src/tests.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ mod extensions;

mod test_account;
mod test_eth_account;
mod test_secp256k1;
mod test_secp256_point;
mod test_signature;
10 changes: 5 additions & 5 deletions packages/account/src/tests/test_eth_account.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use crate::EthAccountComponent::{PublicKeyCamelImpl, PublicKeyImpl};
use crate::EthAccountComponent;
use crate::interface::{EthAccountABIDispatcherTrait, EthAccountABIDispatcher};
use crate::interface::{ISRC6, ISRC6_ID};
use crate::utils::secp256k1::{DebugSecp256k1Point, Secp256k1PointPartialEq};
use crate::utils::signature::EthSignature;
use crate::utils::secp256_point::{DebugSecp256Point, Secp256PointPartialEq};
use crate::utils::signature::Secp256Signature;
use openzeppelin_introspection::interface::{ISRC5, ISRC5_ID};
use openzeppelin_test_common::eth_account::EthAccountSpyHelpers;
use openzeppelin_test_common::eth_account::{
Expand Down Expand Up @@ -364,14 +364,14 @@ fn test_account_called_from_contract() {
//

#[test]
#[should_panic(expected: ('Secp256k1Point: Invalid point.',))]
#[should_panic(expected: ('Secp256Point: Invalid point.',))]
ericnordelo marked this conversation as resolved.
Show resolved Hide resolved
fn test_cannot_get_without_initialize() {
let state = COMPONENT_STATE();
state.get_public_key();
}

#[test]
#[should_panic(expected: ('Secp256k1Point: Invalid point.',))]
#[should_panic(expected: ('Secp256Point: Invalid point.',))]
ericnordelo marked this conversation as resolved.
Show resolved Hide resolved
fn test_cannot_set_without_initialize() {
let key_pair = KEY_PAIR();
let mut state = COMPONENT_STATE();
Expand Down Expand Up @@ -529,7 +529,7 @@ fn test_assert_valid_new_owner_invalid_signature() {

start_cheat_caller_address(test_address(), test_address());
let mut bad_signature = array![];
EthSignature { r: 'BAD'.into(), s: 'SIG'.into() }.serialize(ref bad_signature);
Secp256Signature { r: 'BAD'.into(), s: 'SIG'.into() }.serialize(ref bad_signature);
let new_key_pair = KEY_PAIR_2();

state
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::utils::secp256k1::{
DebugSecp256k1Point, Secp256k1PointPartialEq, Secp256k1PointStorePacking as StorePacking
use crate::utils::secp256_point::{
DebugSecp256Point, Secp256PointPartialEq, Secp256PointStorePacking as StorePacking
};
use starknet::SyscallResultTrait;
use starknet::secp256_trait::{Secp256Trait, Secp256PointTrait};
Expand All @@ -11,7 +11,6 @@ fn test_pack_big_secp256k1_points() {
let curve_size = Secp256Trait::<Secp256k1Point>::get_curve_size();

// Check point 1

let (xlow, xhigh_and_parity) = StorePacking::pack(big_point_1);
let xhigh_and_parity: u256 = xhigh_and_parity.into();

Expand All @@ -24,7 +23,6 @@ fn test_pack_big_secp256k1_points() {
assert_eq!(parity, true, "Parity should be odd");

// Check point 2

let (xlow, xhigh_and_parity) = StorePacking::pack(big_point_2);
let xhigh_and_parity: u256 = xhigh_and_parity.into();

Expand All @@ -42,21 +40,21 @@ fn test_unpack_big_secp256k1_points() {
let (big_point_1, big_point_2) = get_points();

// Check point 1

let (expected_x, expected_y) = big_point_1.get_coordinates().unwrap_syscall();

let (xlow, xhigh_and_parity) = StorePacking::pack(big_point_1);
let (x, y) = StorePacking::unpack((xlow, xhigh_and_parity)).get_coordinates().unwrap_syscall();
let point: Secp256k1Point = StorePacking::unpack((xlow, xhigh_and_parity));
let (x, y) = point.get_coordinates().unwrap_syscall();

assert_eq!(x, expected_x);
assert_eq!(y, expected_y);

// Check point 2

let (expected_x, _) = big_point_2.get_coordinates().unwrap_syscall();

let (xlow, xhigh_and_parity) = StorePacking::pack(big_point_2);
let (x, _) = StorePacking::unpack((xlow, xhigh_and_parity)).get_coordinates().unwrap_syscall();
let point: Secp256k1Point = StorePacking::unpack((xlow, xhigh_and_parity));
let (x, _) = point.get_coordinates().unwrap_syscall();

assert_eq!(x, expected_x);
assert_eq!(y, expected_y);
Expand Down
120 changes: 116 additions & 4 deletions packages/account/src/tests/test_signature.cairo
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
use crate::utils::signature::{is_valid_stark_signature, is_valid_eth_signature};
use crate::interface::P256PublicKey;
use crate::utils::signature::{
is_valid_stark_signature, is_valid_eth_signature, is_valid_p256_signature
};
use openzeppelin_account::utils::signature::Secp256Signature;
use openzeppelin_test_common::account::SIGNED_TX_DATA as stark_signature_data;
use openzeppelin_test_common::eth_account::SIGNED_TX_DATA as eth_signature_data;
use openzeppelin_testing::constants::{stark, secp256k1};
use openzeppelin_testing::constants::{TRANSACTION_HASH, stark, secp256k1, secp256r1};
use openzeppelin_testing::signing::{Secp256r1KeyPair, Secp256r1SerializedSigning};
use snforge_std::signature::secp256r1_curve::Secp256r1CurveSignerImpl;
use starknet::secp256_trait::Secp256Trait;
use starknet::secp256k1::Secp256k1Point;
use starknet::secp256r1::Secp256r1Point;

//
// is_valid_stark_signature
Expand Down Expand Up @@ -86,7 +93,7 @@ fn test_is_valid_eth_signature_invalid_format_sig() {
}

#[test]
fn test_signature_r_out_of_range() {
fn test_eth_signature_r_out_of_range() {
let key_pair = secp256k1::KEY_PAIR();
let data = eth_signature_data(key_pair);
let mut bad_signature = data.signature;
Expand All @@ -106,7 +113,7 @@ fn test_signature_r_out_of_range() {
}

#[test]
fn test_signature_s_out_of_range() {
fn test_eth_signature_s_out_of_range() {
let key_pair = secp256k1::KEY_PAIR();
let data = eth_signature_data(key_pair);
let mut bad_signature = data.signature;
Expand All @@ -124,3 +131,108 @@ fn test_signature_s_out_of_range() {
);
assert!(is_invalid);
}

//
// is_valid_p256_signature
//

#[derive(Drop)]
pub struct SignedTransactionData {
pub private_key: u256,
pub public_key: P256PublicKey,
pub tx_hash: felt252,
pub signature: Secp256Signature
}

fn p256_signature_data(key_pair: Secp256r1KeyPair) -> SignedTransactionData {
let tx_hash = TRANSACTION_HASH;
let (r, s) = key_pair.sign(tx_hash.into()).unwrap();
SignedTransactionData {
private_key: key_pair.secret_key,
public_key: key_pair.public_key,
tx_hash,
signature: Secp256Signature { r, s }
}
}

#[test]
fn test_is_valid_p256_signature_good_sig() {
let key_pair = secp256r1::KEY_PAIR();
let data = p256_signature_data(key_pair);

let mut serialized_good_signature = array![];
data.signature.serialize(ref serialized_good_signature);

let is_valid = is_valid_p256_signature(
data.tx_hash, key_pair.public_key, serialized_good_signature.span()
);
assert!(is_valid);
}

#[test]
fn test_is_valid_p256_signature_bad_sig() {
let key_pair = secp256r1::KEY_PAIR();
let data = p256_signature_data(key_pair);
let mut bad_signature = data.signature;

bad_signature.r += 1;

let mut serialized_bad_signature = array![];

bad_signature.serialize(ref serialized_bad_signature);

let is_invalid = !is_valid_p256_signature(
data.tx_hash, key_pair.public_key, serialized_bad_signature.span()
);
assert!(is_invalid);
}

#[test]
#[should_panic(expected: ('Signature: Invalid format.',))]
ericnordelo marked this conversation as resolved.
Show resolved Hide resolved
fn test_is_valid_p256_signature_invalid_format_sig() {
let key_pair = secp256r1::KEY_PAIR();
let data = p256_signature_data(key_pair);
let mut serialized_bad_signature = array![0x1];

is_valid_p256_signature(data.tx_hash, key_pair.public_key, serialized_bad_signature.span());
}

#[test]
fn test_p256_signature_r_out_of_range() {
let key_pair = secp256r1::KEY_PAIR();
let data = p256_signature_data(key_pair);
let mut bad_signature = data.signature;

let curve_size = Secp256Trait::<Secp256r1Point>::get_curve_size();

bad_signature.r = curve_size + 1;

let mut serialized_bad_signature = array![];

bad_signature.serialize(ref serialized_bad_signature);

let is_invalid = !is_valid_p256_signature(
data.tx_hash, key_pair.public_key, serialized_bad_signature.span()
);
assert!(is_invalid);
}

#[test]
fn test_p256_signature_s_out_of_range() {
let key_pair = secp256r1::KEY_PAIR();
let data = p256_signature_data(key_pair);
let mut bad_signature = data.signature;

let curve_size = Secp256Trait::<Secp256r1Point>::get_curve_size();

bad_signature.s = curve_size + 1;

let mut serialized_bad_signature = array![];

bad_signature.serialize(ref serialized_bad_signature);

let is_invalid = !is_valid_p256_signature(
data.tx_hash, key_pair.public_key, serialized_bad_signature.span()
);
assert!(is_invalid);
}
4 changes: 2 additions & 2 deletions packages/account/src/utils.cairo
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts for Cairo v0.18.0 (account/utils.cairo)

pub mod secp256k1;
pub mod secp256_point;
pub mod signature;

pub use signature::{is_valid_stark_signature, is_valid_eth_signature};
pub use signature::{is_valid_stark_signature, is_valid_eth_signature, is_valid_p256_signature};

use starknet::SyscallResultTrait;
use starknet::account::Call;
Expand Down
Loading