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 verify_signature function for secp curves #4120

Merged
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
4 changes: 4 additions & 0 deletions corelib/src/starknet.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ use eth_address::{
EthAddress, EthAddressIntoFelt252, EthAddressSerde, EthAddressZeroable, Felt252TryIntoEthAddress
};

// EthSignature
mod eth_signature;
use eth_signature::verify_eth_signature;

// ClassHash
mod class_hash;
use class_hash::{
Expand Down
59 changes: 59 additions & 0 deletions corelib/src/starknet/eth_signature.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use option::OptionTrait;
use starknet::{
EthAddress,
secp256_trait::{
Secp256Trait, Secp256PointTrait, recover_public_key, is_signature_entry_valid, Signature
},
secp256k1::Secp256k1Point, SyscallResult, SyscallResultTrait
};
use keccak::keccak_u256s_be_inputs;

/// Asserts that an Ethereum signature is valid w.r.t. a given Eth address
/// Also verifies that r and s components of the signature are in the range (0, N),
/// where N is the size of the curve.
fn verify_eth_signature(msg_hash: u256, signature: Signature, eth_address: EthAddress) {
match is_eth_signature_valid(:msg_hash, :signature, :eth_address) {
Result::Ok(()) => {},
Result::Err(err) => panic_with_felt252(err),
}
}

/// Asserts that an Ethereum signature is valid w.r.t. a given Eth address
/// Also verifies that r and s components of the signature are in the range (0, N),
/// where N is the size of the curve.
/// Returns a Result with an error string if the signature is invalid.
fn is_eth_signature_valid(
msg_hash: u256, signature: Signature, eth_address: EthAddress
) -> Result<(), felt252> {
if !is_signature_entry_valid::<Secp256k1Point>(signature.r) {
return Result::Err('Signature out of range');
}
if !is_signature_entry_valid::<Secp256k1Point>(signature.s) {
return Result::Err('Signature out of range');
}

let public_key_point = recover_public_key::<Secp256k1Point>(:msg_hash, :signature).unwrap();
let calculated_eth_address = public_key_point_to_eth_address(:public_key_point);
if eth_address != calculated_eth_address {
return Result::Err('Invalid signature');
}
Result::Ok(())
}

/// Converts a public key point to the corresponding Ethereum address.
fn public_key_point_to_eth_address<
Secp256Point, +Drop<Secp256Point>, +Secp256Trait<Secp256Point>, +Secp256PointTrait<Secp256Point>
>(
public_key_point: Secp256Point
) -> EthAddress {
let (x, y) = public_key_point.get_coordinates().unwrap_syscall();

// Keccak output is little endian.
let point_hash_le = keccak_u256s_be_inputs(array![x, y].span());
let point_hash = u256 {
low: integer::u128_byte_reverse(point_hash_le.high),
high: integer::u128_byte_reverse(point_hash_le.low)
};

point_hash.into()
}
102 changes: 35 additions & 67 deletions corelib/src/starknet/secp256_trait.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use array::ArrayTrait;
use keccak::keccak_u256s_be_inputs;
use math::{u256_mul_mod_n, inv_mod};
use option::OptionTrait;
use starknet::{eth_address::U256IntoEthAddress, EthAddress, SyscallResult, SyscallResultTrait};
Expand Down Expand Up @@ -43,6 +42,41 @@ trait Secp256PointTrait<Secp256Point> {
fn mul(self: Secp256Point, scalar: u256) -> SyscallResult<Secp256Point>;
}

/// Checks whether `value` is in the range [1, N), where N is the size of the curve.
fn is_signature_entry_valid<
Secp256Point, +Drop<Secp256Point>, impl Secp256Impl: Secp256Trait<Secp256Point>
>(
value: u256
) -> bool {
value != 0_u256 && value < Secp256Impl::get_curve_size()
}

fn is_valid_signature<
Secp256Point,
+Drop<Secp256Point>,
impl Secp256Impl: Secp256Trait<Secp256Point>,
+Secp256PointTrait<Secp256Point>
>(
msg_hash: u256, r: u256, s: u256, public_key: Secp256Point
) -> bool {
if !is_signature_entry_valid::<Secp256Point>(r)
|| !is_signature_entry_valid::<Secp256Point>(s) {
return false;
}

let n_nz = Secp256Impl::get_curve_size().try_into().unwrap();
let s_inv = inv_mod(s.try_into().unwrap(), n_nz).unwrap();
let u1 = u256_mul_mod_n(msg_hash, s_inv, n_nz);
let u2 = u256_mul_mod_n(r, s_inv, n_nz);

let generator_point = Secp256Impl::get_generator_point();
let point1 = generator_point.mul(u1).unwrap_syscall();
let point2 = public_key.mul(u2).unwrap_syscall();
let sum = point1.add(point2).unwrap_syscall();

let (x, y) = sum.get_coordinates().unwrap_syscall();
x == r
}

/// Receives a signature and the signed message hash.
/// Returns the public key associated with the signer, represented as a point on the curve.
Expand Down Expand Up @@ -85,69 +119,3 @@ fn secp256_ec_negate_scalar<
) -> u256 {
Secp256Impl::get_curve_size() - c
}


/// Checks a Secp256 ECDSA signature.
/// Also verifies that r and s components of the signature are in the range (0, N),
/// where N is the size of the curve.
/// Returns a Result with an error string if the signature is invalid.
fn is_eth_signature_valid<
Secp256Point, +Drop<Secp256Point>, +Secp256Trait<Secp256Point>, +Secp256PointTrait<Secp256Point>
>(
msg_hash: u256, signature: Signature, eth_address: EthAddress
) -> Result<(), felt252> {
if !is_signature_entry_valid::<Secp256Point>(signature.r) {
return Result::Err('Signature out of range');
}
if !is_signature_entry_valid::<Secp256Point>(signature.s) {
return Result::Err('Signature out of range');
}

let public_key_point = recover_public_key::<Secp256Point>(:msg_hash, :signature).unwrap();
let calculated_eth_address = public_key_point_to_eth_address(:public_key_point);
if eth_address != calculated_eth_address {
return Result::Err('Invalid signature');
}
Result::Ok(())
}

/// Asserts that a Secp256 ECDSA signature is valid.
/// Also verifies that r and s components of the signature are in the range (0, N),
/// where N is the size of the curve.
fn verify_eth_signature<
Secp256Point, +Drop<Secp256Point>, +Secp256Trait<Secp256Point>, +Secp256PointTrait<Secp256Point>
>(
msg_hash: u256, signature: Signature, eth_address: EthAddress
) {
match is_eth_signature_valid::<Secp256Point>(:msg_hash, :signature, :eth_address) {
Result::Ok(()) => {},
Result::Err(err) => panic_with_felt252(err),
}
}

/// Checks whether `value` is in the range [1, N), where N is the size of the curve.
fn is_signature_entry_valid<
Secp256Point, +Drop<Secp256Point>, impl Secp256Impl: Secp256Trait<Secp256Point>
>(
value: u256
) -> bool {
value != 0_u256 && value < Secp256Impl::get_curve_size()
}

/// Converts a public key point to the corresponding Ethereum address.
fn public_key_point_to_eth_address<
Secp256Point, +Drop<Secp256Point>, +Secp256Trait<Secp256Point>, +Secp256PointTrait<Secp256Point>
>(
public_key_point: Secp256Point
) -> EthAddress {
let (x, y) = public_key_point.get_coordinates().unwrap_syscall();

// Keccak output is little endian.
let point_hash_le = keccak_u256s_be_inputs(array![x, y].span());
let point_hash = u256 {
low: integer::u128_byte_reverse(point_hash_le.high),
high: integer::u128_byte_reverse(point_hash_le.low)
};

point_hash.into()
}
5 changes: 4 additions & 1 deletion corelib/src/starknet/secp256k1.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

use option::OptionTrait;
use starknet::{
EthAddress, secp256_trait::{Secp256Trait, Secp256PointTrait}, SyscallResult, SyscallResultTrait
secp256_trait::{
Secp256Trait, Secp256PointTrait, recover_public_key, is_signature_entry_valid, Signature
},
SyscallResult, SyscallResultTrait
};

#[derive(Copy, Drop)]
Expand Down
41 changes: 36 additions & 5 deletions corelib/src/test/secp256k1_test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use starknet::{
eth_address::U256IntoEthAddress, EthAddress, secp256k1::Secp256k1Impl, SyscallResultTrait
};
use starknet::secp256_trait::{
Signature, recover_public_key, verify_eth_signature, Secp256PointTrait, signature_from_vrs
Signature, recover_public_key, Secp256PointTrait, signature_from_vrs, is_valid_signature
};
use starknet::secp256k1::{Secp256k1Point, Secp256k1PointImpl};
use starknet::eth_signature::verify_eth_signature;

#[test]
fn test_secp256k1_recover_public_key() {
Expand Down Expand Up @@ -68,7 +69,7 @@ fn test_verify_eth_signature() {
get_message_and_signature(
:y_parity
);
verify_eth_signature::<Secp256k1Point>(:msg_hash, :signature, :eth_address);
verify_eth_signature(:msg_hash, :signature, :eth_address);
}

#[test]
Expand All @@ -80,7 +81,7 @@ fn test_verify_eth_signature_wrong_eth_address() {
:y_parity
);
let eth_address = (eth_address.into() + 1).try_into().unwrap();
verify_eth_signature::<Secp256k1Point>(:msg_hash, :signature, :eth_address);
verify_eth_signature(:msg_hash, :signature, :eth_address);
}

#[test]
Expand All @@ -92,7 +93,7 @@ fn test_verify_eth_signature_overflowing_signature_r() {
:y_parity
);
signature.r = Secp256k1Impl::get_curve_size() + 1;
verify_eth_signature::<Secp256k1Point>(:msg_hash, :signature, :eth_address);
verify_eth_signature(:msg_hash, :signature, :eth_address);
}

#[test]
Expand All @@ -104,5 +105,35 @@ fn test_verify_eth_signature_overflowing_signature_s() {
:y_parity
);
signature.s = Secp256k1Impl::get_curve_size() + 1;
verify_eth_signature::<Secp256k1Point>(:msg_hash, :signature, :eth_address);
verify_eth_signature(:msg_hash, :signature, :eth_address);
}

#[test]
#[available_gas(100000000)]
fn test_verify_signature() {
let (msg_hash, signature, public_key_x, public_key_y, _) = get_message_and_signature(false);

let public_key = Secp256k1Impl::secp256_ec_new_syscall(public_key_x, public_key_y)
.unwrap_syscall()
.unwrap();

let is_valid = is_valid_signature::<
Secp256k1Point
>(msg_hash, signature.r, signature.s, public_key);
assert(is_valid, 'Signature should be valid');
}

#[test]
#[available_gas(100000000)]
fn test_verify_signature_invalid_signature() {
let (msg_hash, signature, public_key_x, public_key_y, _) = get_message_and_signature(false);

let public_key = Secp256k1Impl::secp256_ec_new_syscall(public_key_x, public_key_y)
.unwrap_syscall()
.unwrap();

let is_valid = is_valid_signature::<
Secp256k1Point
>(msg_hash, signature.r + 1, signature.s, public_key);
assert(!is_valid, 'Signature should be invalid');
}
67 changes: 36 additions & 31 deletions corelib/src/test/secp256r1_test.cairo
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
use starknet::{
eth_address::U256IntoEthAddress, EthAddress, secp256r1::Secp256r1Impl, SyscallResultTrait
};
use starknet::secp256_trait::{
recover_public_key, verify_eth_signature, Secp256PointTrait, Signature
};
use starknet::{secp256r1::Secp256r1Impl, SyscallResultTrait};
use starknet::secp256_trait::{recover_public_key, Secp256PointTrait, Signature, is_valid_signature};
use starknet::secp256r1::{Secp256r1Point, Secp256r1PointImpl};
use test::test_utils::assert_eq;

Expand All @@ -19,7 +15,7 @@ fn test_secp256r1_recover_public_key() {


/// Returns a golden valid message hash and its signature, for testing.
fn get_message_and_signature() -> (u256, Signature, u256, u256, EthAddress) {
fn get_message_and_signature() -> (u256, Signature, u256, u256, Secp256r1Point) {
// msg = ""
// public key: (0x04aaec73635726f213fb8a9e64da3b8632e41495a944d0045b522eba7240fad5,
// 0x0087d9315798aaa3a5ba01775787ced05eaaf7b4e09fc81d6d1aa546e8365d525d)
Expand All @@ -31,43 +27,52 @@ fn get_message_and_signature() -> (u256, Signature, u256, u256, EthAddress) {
0x04aaec73635726f213fb8a9e64da3b8632e41495a944d0045b522eba7240fad5,
0x0087d9315798aaa3a5ba01775787ced05eaaf7b4e09fc81d6d1aa546e8365d525d
);
let eth_address = 0x492882426e1cda979008bfaf874ff796eb3bb1c0_u256.into();

(msg_hash, Signature { r, s, y_parity: true }, public_key_x, public_key_y, eth_address)
let public_key = Secp256r1Impl::secp256_ec_new_syscall(public_key_x, public_key_y)
.unwrap_syscall()
.unwrap();

(msg_hash, Signature { r, s, y_parity: true }, public_key_x, public_key_y, public_key)
}

#[test]
fn test_verify_eth_signature() {
let (msg_hash, signature, expected_public_key_x, expected_public_key_y, eth_address) =
get_message_and_signature();
verify_eth_signature::<Secp256r1Point>(:msg_hash, :signature, :eth_address);
#[available_gas(100000000)]
fn test_verify_signature() {
let (msg_hash, signature, _, _, public_key) = get_message_and_signature();
let is_valid = is_valid_signature::<
Secp256r1Point
>(msg_hash, signature.r, signature.s, public_key);
assert(is_valid, 'Signature should be valid');
}

#[test]
#[should_panic(expected: ('Invalid signature',))]
fn test_verify_eth_signature_wrong_eth_address() {
let (msg_hash, signature, expected_public_key_x, expected_public_key_y, eth_address) =
get_message_and_signature();
let eth_address = (eth_address.into() + 1).try_into().unwrap();
verify_eth_signature::<Secp256r1Point>(:msg_hash, :signature, :eth_address);
#[available_gas(100000000)]
fn test_verify_signature_invalid_signature() {
let (msg_hash, signature, _, _, public_key) = get_message_and_signature();
let is_valid = is_valid_signature::<
Secp256r1Point
>(msg_hash, signature.r + 1, signature.s, public_key);
assert(!is_valid, 'Signature should be invalid');
}

#[test]
#[should_panic(expected: ('Signature out of range',))]
fn test_verify_eth_signature_overflowing_signature_r() {
let (msg_hash, mut signature, expected_public_key_x, expected_public_key_y, eth_address) =
get_message_and_signature();
signature.r = Secp256r1Impl::get_curve_size() + 1;
verify_eth_signature::<Secp256r1Point>(:msg_hash, :signature, :eth_address);
#[available_gas(100000000)]
fn test_verify_signature_overflowing_signature_r() {
let (msg_hash, mut signature, _, _, public_key) = get_message_and_signature();
let is_valid = is_valid_signature::<
Secp256r1Point
>(msg_hash, Secp256r1Impl::get_curve_size() + 1, signature.s, public_key);
assert(!is_valid, 'Signature out of range');
}

#[test]
#[should_panic(expected: ('Signature out of range',))]
fn test_verify_eth_signature_overflowing_signature_s() {
let (msg_hash, mut signature, expected_public_key_x, expected_public_key_y, eth_address) =
get_message_and_signature();
signature.s = Secp256r1Impl::get_curve_size() + 1;
verify_eth_signature::<Secp256r1Point>(:msg_hash, :signature, :eth_address);
#[available_gas(100000000)]
fn test_verify_signature_overflowing_signature_s() {
let (msg_hash, mut signature, _, _, public_key) = get_message_and_signature();
let is_valid = is_valid_signature::<
Secp256r1Point
>(msg_hash, signature.r, Secp256r1Impl::get_curve_size() + 1, public_key);
assert(!is_valid, 'Signature out of range');
}


Expand Down