From b34397587e453bc6ed32f4e6fff8809951c7a056 Mon Sep 17 00:00:00 2001 From: Timothy Edison Date: Mon, 1 Apr 2024 16:15:41 +0200 Subject: [PATCH 1/5] Fix invalid signature verification --- src/math/src/ed25519.cairo | 101 +++++++++----- src/math/src/mod_arithmetics.cairo | 6 + src/math/src/tests/ed25519_test.cairo | 183 +++++++------------------- 3 files changed, 125 insertions(+), 165 deletions(-) diff --git a/src/math/src/ed25519.cairo b/src/math/src/ed25519.cairo index a382c85e..b345de03 100644 --- a/src/math/src/ed25519.cairo +++ b/src/math/src/ed25519.cairo @@ -1,6 +1,8 @@ -use alexandria_data_structures::array_ext::ArrayTraitExt; +use core::box::BoxTrait; +use core::array::SpanTrait; +use alexandria_data_structures::array_ext::{ArrayTraitExt, SpanTraitExt}; use alexandria_math::mod_arithmetics::{ - add_mod, sub_mod, mult_mod, div_mod, pow_mod, add_inverse_mod + add_mod, sub_mod, mult_mod, div_mod, pow_mod, add_inverse_mod, equality_mod }; use alexandria_math::sha512::{sha512, SHA512_LEN}; use integer::u512; @@ -15,10 +17,8 @@ const d: u256 = const l: u256 = 7237005577332262213973186563042994240857116359379907606001950938285454250989; // 2^252 + 27742317777372353535851937790883648493 +const TWO_POW_8: u256 = 0x100; -const PUB_KEY_LEN: usize = 32; - -const SIG_LEN: usize = 64; #[derive(Drop, Copy)] struct Point { @@ -90,6 +90,7 @@ impl PartialEqExtendedHomogeneousPoint of PartialEq { } impl SpanU8IntoU256 of Into, u256> { + // Decode as little endian fn into(self: Span) -> u256 { if (self.len() > 32) { return 0; @@ -148,6 +149,26 @@ impl SpanU8IntoU256 of Into, u256> { } } +impl U256IntoSpanU8 of Into> { + fn into(self: u256) -> Span { + let mut ret = array![]; + let mut remaining_value = self; + let two_pow_8_non_zero = TWO_POW_8.try_into().unwrap(); + + let mut i: u8 = 0; + while (i < 32) { + let (temp_remaining, byte) = integer::U256DivRem::div_rem( + remaining_value, two_pow_8_non_zero + ); + ret.append(byte.try_into().unwrap()); + remaining_value = temp_remaining; + i += 1; + }; + + ret.span() + } +} + impl SpanU8IntoU512 of Into, u512> { fn into(self: Span) -> u512 { let half_1 = self.slice(0, SHA512_LEN / 2); @@ -159,37 +180,46 @@ impl SpanU8IntoU512 of Into, u512> { } } -impl SpanU8TryIntoPoint of TryInto, Point> { - fn try_into(mut self: Span) -> Option { +impl U256TryIntoPoint of TryInto { + fn try_into(self: u256) -> Option { let mut x = 0; + let mut y_span: Span = self.into(); + let mut y_le_span: Span = y_span.reverse().span(); - let mut y: u256 = self.into(); + let last_byte = *y_le_span[31]; + let mut normed = y_le_span.clone(); + + let _ = normed.pop_back(); + let mut normed_array: Array = normed.dedup(); + normed_array.append(last_byte & ~0x80); + + let x_0: u256 = (last_byte.into() / 128) & 1; // bitshift of 255 + + let y: u256 = normed_array.span().into(); if (y >= p) { return Option::None; } - // bitshit of 255 - let bitshift_255: u256 = - 57896044618658097711785492504343953926634992332820282019728792003956564819968; - let x_0: u256 = y / bitshift_255; // bitshift of 255 - - y = (y & bitshift_255 - 1); let y_2 = pow_mod(y, 2, p); let u: u256 = sub_mod(y_2, 1, p); let v: u256 = add_mod(mult_mod(d, y_2, p), 1, p); let v_pow_3 = pow_mod(v, 3, p); - let v_pow_4 = pow_mod(v, 4, p); - let v_pow7: u256 = mult_mod(v_pow_3, v_pow_4, p); - let p_minus_5_div_8: u256 = div_mod(p - 5, 8, p); + + let v_pow_7: u256 = pow_mod(v, 7, p); + + let p_minus_5_div_8: u256 = div_mod(sub_mod(p, 5, p), 8, p); + let u_times_v_power_3: u256 = mult_mod(u, v_pow_3, p); let x_candidate_root: u256 = mult_mod( - u_times_v_power_3, pow_mod(mult_mod(u, v_pow7, p), p_minus_5_div_8, p), p + u_times_v_power_3, pow_mod(mult_mod(u, v_pow_7, p), p_minus_5_div_8, p), p ); - let x_times_v_squared: u256 = mult_mod(v, pow_mod(x_candidate_root, 2, p), p); - if (x_times_v_squared == u) { + + let v_times_x_squared: u256 = mult_mod(v, pow_mod(x_candidate_root, 2, p), p); + + if (equality_mod(v_times_x_squared, u, p)) { x = x_candidate_root; - } else if (x_times_v_squared == add_inverse_mod(u, p)) { + } else if (equality_mod(v_times_x_squared, add_inverse_mod(u, p), p)) { let p_minus_one_over_4: u256 = div_mod(sub_mod(p, 1, p), 4, p); x = mult_mod(x_candidate_root, pow_mod(2, p_minus_one_over_4, p), p); } else { @@ -261,33 +291,44 @@ fn check_group_equation( lhs == rhs } -fn verify_signature(msg: Span, signature: Span, pub_key: Span) -> bool { - if (pub_key.len() != PUB_KEY_LEN || signature.len() != SIG_LEN) { +fn verify_signature(msg: Span, signature: Span, pub_key: u256) -> bool { + let r: u256 = *signature.get(0).unwrap().unbox(); + let r_point: Option = r.try_into(); + if (r_point.is_none()) { return false; } - let r_string = signature.slice(0, SIG_LEN / 2); - let R_opt: Option = r_string.try_into(); - if (R_opt.is_none()) { + let s: u256 = *signature.get(1).unwrap().unbox(); + let s_span: Span = s.into(); + let reversed_s_span = s_span.reverse(); + let s: u256 = reversed_s_span.span().into(); + if (s >= l) { return false; } + let A_prime_opt: Option = pub_key.try_into(); if (A_prime_opt.is_none()) { return false; } - let R: Point = R_opt.unwrap(); - let S: u256 = signature.slice(32, SIG_LEN / 2).into(); + + let R: Point = r_point.unwrap(); let A_prime: Point = A_prime_opt.unwrap(); let R_extended: ExtendedHomogeneousPoint = R.into(); let A_prime_ex: ExtendedHomogeneousPoint = A_prime.into(); + let r_bytes: Span = r.into(); + let r_bytes = r_bytes.reverse().span(); + let pub_key_bytes: Span = pub_key.into(); + let pub_key_bytes = pub_key_bytes.reverse().span(); + + let hashable = r_bytes.snapshot.concat(pub_key_bytes.snapshot).concat(msg.snapshot); // k = SHA512(dom2(F, C) -> empty string || R -> half of sig || A -> pub_key || PH(M) -> identity function for msg) - let k: Array = sha512(r_string.snapshot.concat(pub_key.snapshot).concat(msg.snapshot)); + let k: Array = sha512(hashable); let k_u512: u512 = k.span().into(); let l_non_zero: NonZero = integer::u256_try_as_non_zero(l).unwrap(); let (_, k_reduced) = integer::u512_safe_div_rem_by_u256(k_u512, l_non_zero); - check_group_equation(S, R_extended, k_reduced, A_prime_ex) + check_group_equation(s, R_extended, k_reduced, A_prime_ex) } diff --git a/src/math/src/mod_arithmetics.cairo b/src/math/src/mod_arithmetics.cairo index 5507d01f..cb1bfa5a 100644 --- a/src/math/src/mod_arithmetics.cairo +++ b/src/math/src/mod_arithmetics.cairo @@ -126,3 +126,9 @@ fn pow_mod(mut base: u256, mut pow: u256, modulo: u256) -> u256 { result } + +fn equality_mod(a: u256, b: u256, modulo: u256) -> bool { + let (_, a_rem) = integer::U256DivRem::div_rem(a, modulo.try_into().unwrap()); + let (_, b_rem) = integer::U256DivRem::div_rem(b, modulo.try_into().unwrap()); + a_rem == b_rem +} diff --git a/src/math/src/tests/ed25519_test.cairo b/src/math/src/tests/ed25519_test.cairo index b86f2a86..f2f95fd7 100644 --- a/src/math/src/tests/ed25519_test.cairo +++ b/src/math/src/tests/ed25519_test.cairo @@ -1,160 +1,73 @@ -use alexandria_math::ed25519::{p, Point, verify_signature, SpanU8TryIntoPoint}; +use alexandria_math::ed25519::{p, Point, verify_signature}; -fn gen_msg() -> Span { - // Hello World! - array!['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'].span() -} +// Public keys and signatures were generated with JS library Noble (https://github.com/paulmillr/noble-ed25519) -fn gen_wrong_msg() -> Span { - // Hello Bro! - array!['H', 'e', 'l', 'l', 'o', ' ', 'B', 'r', 'o', '!'].span() -} +#[test] +#[available_gas(3200000000)] +fn verify_signature_test_0() { + let pub_key: u256 = 0x1e6c5b385880849f46716d691b8a447d7cbe4a7ef154f3e2174ffb3c5256fcfe; -fn gen_sig() -> Span { - // 2228eb055b60cab2b3abfefbc79eef16441dc7edf9aae9778013ffa7b2607b115f7a5709381b49d18437e842a7234881a328707b3de17dbfee25608b7cb99b04 - let mut sig: Array = array![ - 0x22, - 0x28, - 0xeb, - 0x05, - 0x5b, - 0x60, - 0xca, - 0xb2, - 0xb3, - 0xab, - 0xfe, - 0xfb, - 0xc7, - 0x9e, - 0xef, - 0x16, - 0x44, - 0x1d, - 0xc7, - 0xed, - 0xf9, - 0xaa, - 0xe9, - 0x77, - 0x80, - 0x13, - 0xff, - 0xa7, - 0xb2, - 0x60, - 0x7b, - 0x11, - 0x5f, - 0x7a, - 0x57, - 0x09, - 0x38, - 0x1b, - 0x49, - 0xd1, - 0x84, - 0x37, - 0xe8, - 0x42, - 0xa7, - 0x23, - 0x48, - 0x81, - 0xa3, - 0x28, - 0x70, - 0x7b, - 0x3d, - 0xe1, - 0x7d, - 0xbf, - 0xee, - 0x25, - 0x60, - 0x8b, - 0x7c, - 0xb9, - 0x9b, - 0x04 - ]; - sig.span() -} + let msg: Span = array![0xab, 0xcd].span(); + + let r_sign: u256 = 0x71eb4ef992551292a9ba5a4817df47fdda2372b2065ed60758b7ee346b7a9e78; + let s_sign: u256 = 0x6a9473f6492676e988709498b228df873fe3cfdf59255b1a9e1add4f87ec610b; + let signature = array![r_sign, s_sign]; -fn gen_pub_key() -> Span { - let mut pub_key: Array = array![ - 0x99, - 0xb9, - 0x8a, - 0xc7, - 0x34, - 0x27, - 0x8c, - 0x17, - 0xb4, - 0x91, - 0x9a, - 0xa5, - 0xd9, - 0x7a, - 0xdc, - 0x7a, - 0xd2, - 0x18, - 0xb2, - 0xcb, - 0x40, - 0xaf, - 0x81, - 0x56, - 0x9f, - 0xfb, - 0x13, - 0x00, - 0xe7, - 0x73, - 0x89, - 0x0c - ]; - pub_key.span() + assert!(verify_signature(msg, signature.span(), pub_key), "Invalid signature"); } #[test] #[available_gas(3200000000)] -fn verify_signature_test() { - let msg: Span = gen_msg(); - let sig: Span = gen_sig(); - let pub_key: Span = gen_pub_key(); +fn verify_signature_test_1() { + let pub_key: u256 = 0xcc05c1a7ba2937b1c0e71ca1ac636e7240c39fdc8e4672bb0c125eff082324d4; + + let msg: Span = array![0xab, 0xcd].span(); - assert!(verify_signature(msg, sig, pub_key), "Invalid signature"); + let r_sign: u256 = 0xfb781006491fb38af68f9e4be91ce983a32e9363cd8d878325820336283b7d9d; + let s_sign: u256 = 0x7d1308162466f8e6097f8afa310c074796d13459d4b53cdecf80ca7413410000; + let signature = array![r_sign, s_sign]; + + assert!(verify_signature(msg, signature.span(), pub_key), "Invalid signature"); } #[test] #[available_gas(3200000000)] -fn verify_wrong_signature_test() { - let wrong_msg: Span = gen_wrong_msg(); - let sig: Span = gen_sig(); - let pub_key: Span = gen_pub_key(); +fn verify_signature_test_2() { + let pub_key: u256 = 0x136fa0f7464a55d9a19e9dd0e2edf4f605d3b3f3228dbbe3d7337136ae216d49; + + let msg: Span = array![0xab, 0xcd].span(); - assert!(!verify_signature(wrong_msg, sig, pub_key), "Signature should be invalid"); + let r_sign: u256 = 0x9bb411c27a49ea96de338a9c2d5c920357cb9eef5f121f7922c4d2bb00def377; + let s_sign: u256 = 0xcc2e419abf32f91bc20419ba0905ad52923c7c110d14623b62300711b8f9370c; + let signature = array![r_sign, s_sign]; + + assert!(verify_signature(msg, signature.span(), pub_key), "Invalid signature"); } #[test] #[available_gas(3200000000)] -fn verify_signature_empty_sig_test() { - let empty_msg: Span = gen_msg(); - let sig = array![].span(); - let pub_key: Span = gen_pub_key(); +fn verify_signature_test_3() { + let pub_key: u256 = 0x040369a47bcee3ae0cb373037ec0d2e36cae4a3762e388ff0682962aef49f444; + + let msg: Span = array![0x12, 0x34, 0x56, 0x78].span(); - assert!(!verify_signature(empty_msg, sig, pub_key), "Signature should be invalid"); + let r_sign: u256 = 0xc71970448f7368c295d11cd64bb4fc7bb8899c830d9055832b6686b3f606b76d; + let s_sign: u256 = 0x68e015fa8775659d1f40a01e1f69b8af4409046f4dc8ff02cdb04fdc3585eb0d; + let signature = array![r_sign, s_sign]; + + assert!(verify_signature(msg, signature.span(), pub_key), "Invalid signature"); } #[test] #[available_gas(3200000000)] -fn verify_signature_empty_pub_key_test() { - let empty_msg: Span = gen_msg(); - let sig: Span = gen_sig(); - let pub_key = array![].span(); +fn verify_signature_invalid() { + let pub_key: u256 = 0x040369a47bcee3ae0cb373037ec0d2e36cae4a3762e388ff0682962aef49f444; + + let msg: Span = array![0x12, 0x34, 0x56, 0x78].span(); + + let r_sign: u256 = 0xc71970448f7368c295d11cd64bb4fc7bb8899c830d9055832b6686b3f606b76a; + let s_sign: u256 = 0x68e015fa8775659d1f40a01e1f69b8af4409046f4dc8ff02cdb04fdc3585eb01; + let signature = array![r_sign, s_sign]; - assert!(!verify_signature(empty_msg, sig, pub_key), "Signature should be invalid"); + assert!(verify_signature(msg, signature.span(), pub_key) == false, "Invalid signature"); } From 39168f82e6fc6b1c03399df8e4e53e128b412b6f Mon Sep 17 00:00:00 2001 From: Timothy Edison Date: Mon, 1 Apr 2024 16:42:33 +0200 Subject: [PATCH 2/5] Fmt --- src/math/src/ed25519.cairo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/math/src/ed25519.cairo b/src/math/src/ed25519.cairo index b345de03..31575504 100644 --- a/src/math/src/ed25519.cairo +++ b/src/math/src/ed25519.cairo @@ -1,10 +1,10 @@ -use core::box::BoxTrait; -use core::array::SpanTrait; use alexandria_data_structures::array_ext::{ArrayTraitExt, SpanTraitExt}; use alexandria_math::mod_arithmetics::{ add_mod, sub_mod, mult_mod, div_mod, pow_mod, add_inverse_mod, equality_mod }; use alexandria_math::sha512::{sha512, SHA512_LEN}; +use core::array::SpanTrait; +use core::box::BoxTrait; use integer::u512; // As per RFC-8032: https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.7 From ac98f8e30420fd6d5545643289c88ef8a14d3ef4 Mon Sep 17 00:00:00 2001 From: Timothy Edison Date: Wed, 3 Apr 2024 18:15:59 +0200 Subject: [PATCH 3/5] Address PR comments --- src/math/src/ed25519.cairo | 7 +++---- src/math/src/tests/ed25519_test.cairo | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/math/src/ed25519.cairo b/src/math/src/ed25519.cairo index 31575504..46dade6c 100644 --- a/src/math/src/ed25519.cairo +++ b/src/math/src/ed25519.cairo @@ -90,7 +90,7 @@ impl PartialEqExtendedHomogeneousPoint of PartialEq { } impl SpanU8IntoU256 of Into, u256> { - // Decode as little endian + /// Decode as little endian fn into(self: Span) -> u256 { if (self.len() > 32) { return 0; @@ -187,10 +187,9 @@ impl U256TryIntoPoint of TryInto { let mut y_le_span: Span = y_span.reverse().span(); let last_byte = *y_le_span[31]; - let mut normed = y_le_span.clone(); - let _ = normed.pop_back(); - let mut normed_array: Array = normed.dedup(); + let _ = y_le_span.pop_back(); + let mut normed_array: Array = y_le_span.dedup(); normed_array.append(last_byte & ~0x80); let x_0: u256 = (last_byte.into() / 128) & 1; // bitshift of 255 diff --git a/src/math/src/tests/ed25519_test.cairo b/src/math/src/tests/ed25519_test.cairo index f2f95fd7..7b471b57 100644 --- a/src/math/src/tests/ed25519_test.cairo +++ b/src/math/src/tests/ed25519_test.cairo @@ -69,5 +69,5 @@ fn verify_signature_invalid() { let s_sign: u256 = 0x68e015fa8775659d1f40a01e1f69b8af4409046f4dc8ff02cdb04fdc3585eb01; let signature = array![r_sign, s_sign]; - assert!(verify_signature(msg, signature.span(), pub_key) == false, "Invalid signature"); + assert!(!verify_signature(msg, signature.span(), pub_key), "Invalid signature"); } From c8d3de17aee3d8d9687d06e59cd48b657d3d0e7e Mon Sep 17 00:00:00 2001 From: Timothy Edison Date: Fri, 5 Apr 2024 11:42:00 +0200 Subject: [PATCH 4/5] use IndexView + check for length of signature + add invalid tests --- src/math/src/ed25519.cairo | 8 ++++++-- src/math/src/tests/ed25519_test.cairo | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/math/src/ed25519.cairo b/src/math/src/ed25519.cairo index 46dade6c..c2539bd1 100644 --- a/src/math/src/ed25519.cairo +++ b/src/math/src/ed25519.cairo @@ -291,13 +291,17 @@ fn check_group_equation( } fn verify_signature(msg: Span, signature: Span, pub_key: u256) -> bool { - let r: u256 = *signature.get(0).unwrap().unbox(); + if (signature.len() != 2) { + return false; + } + + let r: u256 = *signature[0]; let r_point: Option = r.try_into(); if (r_point.is_none()) { return false; } - let s: u256 = *signature.get(1).unwrap().unbox(); + let s: u256 = *signature[1]; let s_span: Span = s.into(); let reversed_s_span = s_span.reverse(); let s: u256 = reversed_s_span.span().into(); diff --git a/src/math/src/tests/ed25519_test.cairo b/src/math/src/tests/ed25519_test.cairo index 7b471b57..1a323753 100644 --- a/src/math/src/tests/ed25519_test.cairo +++ b/src/math/src/tests/ed25519_test.cairo @@ -1,4 +1,4 @@ -use alexandria_math::ed25519::{p, Point, verify_signature}; +use alexandria_math::ed25519::verify_signature; // Public keys and signatures were generated with JS library Noble (https://github.com/paulmillr/noble-ed25519) @@ -71,3 +71,17 @@ fn verify_signature_invalid() { assert!(!verify_signature(msg, signature.span(), pub_key), "Invalid signature"); } + +#[test] +#[available_gas(3200000000)] +fn verify_signature_invalid_2() { + let pub_key: u256 = 0x040369a47bcee3ae0cb373037ec0d2e36cae4a3762e388ff0682962aef49f444; + + let msg: Span = array![0x0].span(); + + let r_sign: u256 = 0xc71970448f7368c295d11cd64bb4fc7bb8899c830d9055832b6686b3f606b76d; + let s_sign: u256 = 0x68e015fa8775659d1f40a01e1f69b8af4409046f4dc8ff02cdb04fdc3585eb0d; + let signature = array![r_sign, s_sign]; + + assert!(!verify_signature(msg, signature.span(), pub_key), "Invalid signature"); +} From 3c47aeca775cb669e3b0c810a0c6d96e98fef992 Mon Sep 17 00:00:00 2001 From: LucasLvy Date: Fri, 5 Apr 2024 13:25:16 +0200 Subject: [PATCH 5/5] test(ed25519): fix invalid length test --- src/math/src/tests/ed25519_test.cairo | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/math/src/tests/ed25519_test.cairo b/src/math/src/tests/ed25519_test.cairo index 1a323753..97a331fe 100644 --- a/src/math/src/tests/ed25519_test.cairo +++ b/src/math/src/tests/ed25519_test.cairo @@ -74,14 +74,15 @@ fn verify_signature_invalid() { #[test] #[available_gas(3200000000)] -fn verify_signature_invalid_2() { +fn verify_signature_invalid_length() { let pub_key: u256 = 0x040369a47bcee3ae0cb373037ec0d2e36cae4a3762e388ff0682962aef49f444; let msg: Span = array![0x0].span(); let r_sign: u256 = 0xc71970448f7368c295d11cd64bb4fc7bb8899c830d9055832b6686b3f606b76d; let s_sign: u256 = 0x68e015fa8775659d1f40a01e1f69b8af4409046f4dc8ff02cdb04fdc3585eb0d; - let signature = array![r_sign, s_sign]; + let signature = array![r_sign, s_sign, s_sign]; assert!(!verify_signature(msg, signature.span(), pub_key), "Invalid signature"); + assert!(!verify_signature(msg, array![r_sign].span(), pub_key), "Invalid signature"); }