-
Notifications
You must be signed in to change notification settings - Fork 332
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
secp256r1 utils and is_valid_p256_signature #988
base: main
Are you sure you want to change the base?
Changes from all commits
237546a
fa87a04
f2c175b
ad74c63
fd49eed
77a2260
e6754f1
fa2485d
9e6d203
95e709b
150375a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[files] | ||
extend-exclude = ["test_p256_account.cairo"] | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// SPDX-License-Identifier: MIT | ||
// OpenZeppelin Contracts for Cairo v0.13.0 (account/utils/secp256r1.cairo) | ||
|
||
use core::fmt::{Debug, Formatter, Error}; | ||
use starknet::SyscallResultTrait; | ||
use starknet::secp256_trait::Secp256PointTrait; | ||
use starknet::secp256r1::{ | ||
Secp256r1Point, secp256r1_get_point_from_x_syscall, secp256r1_new_syscall | ||
}; | ||
|
||
/// Packs a Secp256r1Point into a (felt252, felt252). | ||
/// | ||
/// The packing is done as follows: | ||
/// - First felt contains x.low (x being the x-coordinate of the point). | ||
/// - Second felt contains x.high and the parity bit, at the least significant bits (2 * x.high + parity). | ||
impl Secp256r1PointStorePacking of starknet::StorePacking<Secp256r1Point, (felt252, felt252)> { | ||
fn pack(value: Secp256r1Point) -> (felt252, felt252) { | ||
let (x, y) = value.get_coordinates().unwrap_syscall(); | ||
|
||
let parity = y % 2; | ||
let xhigh_and_parity = 2 * x.high.into() + parity.try_into().unwrap(); | ||
|
||
(x.low.into(), xhigh_and_parity) | ||
} | ||
|
||
fn unpack(value: (felt252, felt252)) -> Secp256r1Point { | ||
let (xlow, xhigh_and_parity) = value; | ||
let xhigh_and_parity: u256 = xhigh_and_parity.into(); | ||
|
||
let x = u256 { | ||
low: xlow.try_into().unwrap(), high: (xhigh_and_parity / 2).try_into().unwrap(), | ||
}; | ||
let parity = xhigh_and_parity % 2 == 1; | ||
|
||
// Expects parity odd to be true | ||
secp256r1_get_point_from_x_syscall(x, parity) | ||
.unwrap_syscall() | ||
.expect('Secp256r1Point: Invalid point.') | ||
} | ||
} | ||
|
||
impl Secp256r1PointSerde of Serde<Secp256r1Point> { | ||
fn serialize(self: @Secp256r1Point, ref output: Array<felt252>) { | ||
let point = (*self).get_coordinates().unwrap_syscall(); | ||
point.serialize(ref output) | ||
} | ||
fn deserialize(ref serialized: Span<felt252>) -> Option<Secp256r1Point> { | ||
let (x, y) = Serde::<(u256, u256)>::deserialize(ref serialized)?; | ||
secp256r1_new_syscall(x, y).unwrap_syscall() | ||
} | ||
} | ||
|
||
impl Secp256r1PointPartialEq of PartialEq<Secp256r1Point> { | ||
#[inline(always)] | ||
fn eq(lhs: @Secp256r1Point, rhs: @Secp256r1Point) -> bool { | ||
(*lhs).get_coordinates().unwrap_syscall() == (*rhs).get_coordinates().unwrap_syscall() | ||
} | ||
#[inline(always)] | ||
fn ne(lhs: @Secp256r1Point, rhs: @Secp256r1Point) -> bool { | ||
!(lhs == rhs) | ||
} | ||
} | ||
|
||
impl DebugSecp256r1Point of core::fmt::Debug<Secp256r1Point> { | ||
fn fmt(self: @Secp256r1Point, ref f: Formatter) -> Result<(), Error> { | ||
let (x, y) = (*self).get_coordinates().unwrap_syscall(); | ||
write!(f, "({x:?},{y:?})") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
use openzeppelin::account::interface::P256PublicKey; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we don't have this account in the library yet, I would move this into the test_secp256r1 test file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You do not but we do, see https://github.com/0xknwn/starknet-modular-account/blob/749a0485b0f50b9350f8f41eb1898e88f2f3a195/src/modules/p256validator/p256validator.cairo#L17 . The reason behind this PR is because we use your project as a library to develop a modular account. It makes sense that we use the same tool for secp256k1 and secp256r1 and that you provide the technology. For that purpose, it really makes sense types are kept in files that are not test and Eth and P256 public keys are in a parallel location There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it. I'm not recommending to move the type though, but the contents of this file (to move There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay. I will do! |
||
use openzeppelin::account::utils::signature::P256Signature; | ||
use starknet::secp256r1::secp256r1_new_syscall; | ||
|
||
#[derive(Drop)] | ||
struct SignedTransactionData { | ||
private_key: u256, | ||
public_key: P256PublicKey, | ||
transaction_hash: felt252, | ||
signature: P256Signature | ||
} | ||
|
||
/// This signature was computed using @noble/curves. | ||
fn SIGNED_TX_DATA() -> SignedTransactionData { | ||
SignedTransactionData { | ||
private_key: 0x1efecf7ee1e25bb87098baf2aaab0406167aae0d5ea9ba0d31404bf01886bd0e, | ||
public_key: secp256r1_new_syscall( | ||
0x097420e05fbc83afe4d73b31890187d0cacf2c3653e27f434701a91625f916c2, | ||
0x98a304ff544db99c864308a9b3432324adc6c792181bae33fe7a4cbd48cf263a | ||
) | ||
.unwrap() | ||
.unwrap(), | ||
transaction_hash: 0x1e0cb9e0eb2a8b414df99964673bd493b594c4a627ab031c150ffc81b330706, | ||
signature: P256Signature { | ||
r: 0xfe4e53a283f4715bba1969dff40227c2ca24a6321a89a02e37a0b830c1a0918e, | ||
s: 0x52257a68cfe886341cfaf23841f744230f2af8dadf8bee2e6560c6bbfed8f28f, | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
use openzeppelin::account::utils::secp256r1::{ | ||
SyscallResultTrait, Secp256r1Point, DebugSecp256r1Point, Secp256r1PointSerde, | ||
Secp256r1PointPartialEq, Secp256r1PointStorePacking as StorePacking, secp256r1_new_syscall | ||
}; | ||
use starknet::secp256_trait::Secp256PointTrait; | ||
use starknet::secp256r1::Secp256r1Impl; | ||
|
||
#[test] | ||
fn test_pack_big_secp256r1_points() { | ||
let (big_point_1, big_point_2) = get_points(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these points big points? The idea of this test is to check that the packing is right even for big values, maybe it makes sense to use just big values even if they are not valid points. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you define big points or point me in the right direction so that I can build values that would make sense for that test? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A point with x big enough for the high part of the u256 to use as many bits as possible would be the best case, but making sure that at least the high part is not 0 should suffice. |
||
let private_key = P256_PRIVATEKEY_SAMPLE(); | ||
|
||
// Check point 1 | ||
|
||
let (xlow, xhigh_and_parity) = StorePacking::pack(big_point_1); | ||
let xhigh_and_parity: u256 = xhigh_and_parity.into(); | ||
|
||
let x = u256 { | ||
low: xlow.try_into().unwrap(), high: (xhigh_and_parity / 2).try_into().unwrap() | ||
}; | ||
let parity = xhigh_and_parity % 2 == 1; | ||
|
||
assert_eq!(x, private_key); | ||
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(); | ||
|
||
let x = u256 { | ||
low: xlow.try_into().unwrap(), high: (xhigh_and_parity / 2).try_into().unwrap() | ||
}; | ||
let parity = xhigh_and_parity % 2 == 1; | ||
|
||
assert_eq!(x, private_key); | ||
assert_eq!(parity, false, "Parity should be even"); | ||
} | ||
|
||
#[test] | ||
fn test_unpack_big_secp256r1_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(); | ||
|
||
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(); | ||
|
||
assert_eq!(x, expected_x); | ||
assert_eq!(y, expected_y); | ||
} | ||
|
||
#[test] | ||
fn test_secp256r1_serialization() { | ||
let (big_point_1, big_point_2) = get_points(); | ||
|
||
let mut serialized_point = array![]; | ||
let mut expected_serialization = array![]; | ||
|
||
// Check point 1 | ||
|
||
big_point_1.serialize(ref serialized_point); | ||
big_point_1.get_coordinates().unwrap_syscall().serialize(ref expected_serialization); | ||
|
||
assert!(serialized_point == expected_serialization); | ||
|
||
// Check point 2 | ||
|
||
big_point_2.serialize(ref serialized_point); | ||
big_point_2.get_coordinates().unwrap_syscall().serialize(ref expected_serialization); | ||
|
||
assert!(serialized_point == expected_serialization); | ||
} | ||
|
||
#[test] | ||
fn test_secp256r1_deserialization() { | ||
let (big_point_1, big_point_2) = get_points(); | ||
|
||
// Check point 1 | ||
|
||
let mut expected_serialization = array![]; | ||
|
||
big_point_1.get_coordinates().unwrap_syscall().serialize(ref expected_serialization); | ||
let mut expected_serialization = expected_serialization.span(); | ||
let deserialized_point = Secp256r1PointSerde::deserialize(ref expected_serialization).unwrap(); | ||
|
||
assert_eq!(big_point_1, deserialized_point); | ||
|
||
// Check point 2 | ||
|
||
let mut expected_serialization = array![]; | ||
|
||
big_point_2.get_coordinates().unwrap_syscall().serialize(ref expected_serialization); | ||
let mut expected_serialization = expected_serialization.span(); | ||
let deserialized_point = Secp256r1PointSerde::deserialize(ref expected_serialization).unwrap(); | ||
|
||
assert_eq!(big_point_2, deserialized_point); | ||
} | ||
|
||
#[test] | ||
fn test_partial_eq() { | ||
let (big_point_1, big_point_2) = get_points(); | ||
|
||
assert_eq!(big_point_1, big_point_1); | ||
assert_eq!(big_point_2, big_point_2); | ||
assert!(big_point_1 != big_point_2); | ||
assert!(big_point_2 != big_point_1); | ||
} | ||
|
||
// | ||
// Helpers | ||
// | ||
|
||
/// This signature was computed using @noble/curves. | ||
fn P256_PRIVATEKEY_SAMPLE() -> u256 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we are not verifying signatures for this test, Is this function needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see comment my next comment. I will remove it, once we agree on what to name it. |
||
0x1efecf7ee1e25bb87098baf2aaab0406167aae0d5ea9ba0d31404bf01886bd0e | ||
} | ||
|
||
fn get_points() -> (Secp256r1Point, Secp256r1Point) { | ||
let private_key = P256_PRIVATEKEY_SAMPLE(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need x to be a private key? The name seems a bit misleading. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can replace the x value on the curve by a const and remove the function. Whatever you want but tell me what you prefer otherwise we may spin around:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case, I think the value is okay. |
||
let point_1 = Secp256r1Impl::secp256_ec_get_point_from_x_syscall(private_key, true) | ||
.unwrap_syscall() | ||
.unwrap(); | ||
let point_2 = Secp256r1Impl::secp256_ec_get_point_from_x_syscall(private_key, false) | ||
.unwrap_syscall() | ||
.unwrap(); | ||
|
||
(point_1, point_2) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we excluding this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because your tool that checks for syntax consider I do spell some hexa value wrong and I do not know any other ways to pass the associated test. see https://github.com/OpenZeppelin/cairo-contracts/actions/runs/9023095283/job/24794108410 for details. What is a better way to fix it?