Skip to content

Commit a1c1087

Browse files
authored
fix: find_non_qr doesn't work for p = 1 mod 4 (#1469)
- Fix `find_non_qr` for cases when p = 1 mod 4 but is not 5 mod 8. - Fix the `num_limbs` of `prime_limbs` in `FieldExpr` (this issue arose when adding tests with 224-bit moduli; it doesn't affect the soundness of the Mod builder but will cause the correct trace to fail). Resolves INT-3640
1 parent b92feee commit a1c1087

File tree

7 files changed

+262
-144
lines changed

7 files changed

+262
-144
lines changed

crates/circuits/mod-builder/src/builder.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,11 @@ impl<AB: InteractionBuilder> SubAir<AB> for FieldExpr {
331331
// only the first segment will have setup called
332332

333333
let expected = iter::empty()
334-
.chain(self.builder.prime_limbs.clone())
334+
.chain({
335+
let mut prime_limbs = self.builder.prime_limbs.clone();
336+
prime_limbs.resize(self.builder.num_limbs, 0);
337+
prime_limbs
338+
})
335339
.chain(self.setup_values.iter().flat_map(|x| {
336340
big_uint_to_num_limbs(x, self.builder.limb_bits, self.builder.num_limbs)
337341
.into_iter()

extensions/ecc/circuit/src/weierstrass_extension.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,10 @@ pub(crate) mod phantom {
493493
&BigUint::from_u8(2).unwrap(),
494494
&(modulus - BigUint::from_u8(1).unwrap()),
495495
);
496-
let exponent = (modulus - BigUint::one()) >> 2;
496+
// To check if non_qr is a quadratic nonresidue, we compute non_qr^((p-1)/2)
497+
// If the result is p-1, then non_qr is a quadratic nonresidue
498+
// Otherwise, non_qr is a quadratic residue
499+
let exponent = (modulus - BigUint::one()) >> 1;
497500
while non_qr.modpow(&exponent, modulus) != modulus - BigUint::one() {
498501
non_qr = rng.gen_biguint_range(
499502
&BigUint::from_u8(2).unwrap(),

extensions/ecc/sw-macros/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ pub fn sw_init(input: TokenStream) -> TokenStream {
559559
rs1 = In p1.as_ptr(),
560560
rs2 = Const "x0" // will be parsed as 0 and therefore transpiled to SETUP_EC_DOUBLE
561561
);
562-
}
562+
}
563563
}
564564
});
565565

extensions/ecc/tests/programs/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ p256 = ["openvm-ecc-guest/p256"]
4040
# Note that these tests are expected to not terminate
4141
test_secp256k1_possible = []
4242
test_secp256k1_impossible = []
43-
test_mycurve_possible = []
44-
test_mycurve_impossible = []
43+
test_curvepoint5mod8_possible = []
44+
test_curvepoint5mod8_impossible = []
45+
test_curvepoint1mod4_possible = []
46+
test_curvepoint1mod4_impossible = []
4547

4648
[profile.release]
4749
panic = "abort"

extensions/ecc/tests/programs/examples/decompress.rs

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use core::ops::Neg;
55
extern crate alloc;
66

7+
use hex_literal::hex;
78
use openvm::io::read_vec;
89
use openvm_ecc_guest::{
910
algebra::{Field, IntMod},
@@ -15,17 +16,24 @@ use openvm_ecc_guest::{
1516
openvm::entry!(main);
1617

1718
openvm_algebra_moduli_macros::moduli_declare! {
19+
// a prime that is 5 mod 8
20+
Fp5mod8 { modulus = "115792089237316195423570985008687907853269984665640564039457584007913129639501" },
1821
// a prime that is 1 mod 4
19-
Fp { modulus = "115792089237316195423570985008687907853269984665640564039457584007913129639501" },
22+
Fp1mod4 { modulus = "0xffffffffffffffffffffffffffffffff000000000000000000000001" },
2023
}
2124

2225
openvm_algebra_moduli_macros::moduli_init! {
2326
"0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F",
2427
"0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141",
2528
"115792089237316195423570985008687907853269984665640564039457584007913129639501",
29+
"1000000007",
30+
"0xffffffffffffffffffffffffffffffff000000000000000000000001",
31+
"0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d",
2632
}
2733

28-
impl Field for Fp {
34+
const CURVE_B_5MOD8: Fp5mod8 = Fp5mod8::from_const_u8(3);
35+
36+
impl Field for Fp5mod8 {
2937
const ZERO: Self = <Self as IntMod>::ZERO;
3038
const ONE: Self = <Self as IntMod>::ONE;
3139

@@ -40,24 +48,51 @@ impl Field for Fp {
4048
}
4149
}
4250

43-
const MY_CURVE_B: Fp = Fp::from_const_u8(3);
51+
const CURVE_A_1MOD4: Fp1mod4 = Fp1mod4::from_const_bytes(hex!(
52+
"FEFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000"
53+
));
54+
const CURVE_B_1MOD4: Fp1mod4 = Fp1mod4::from_const_bytes(hex!(
55+
"B4FF552343390B27BAD8BFD7B7B04450563241F5ABB3040C850A05B400000000"
56+
));
4457

45-
openvm_ecc_sw_macros::sw_declare! {
46-
MyCurvePoint {
47-
mod_type = Fp,
48-
b = MY_CURVE_B,
58+
impl Field for Fp1mod4 {
59+
const ZERO: Self = <Self as IntMod>::ZERO;
60+
const ONE: Self = <Self as IntMod>::ONE;
61+
62+
type SelfRef<'a> = &'a Self;
63+
64+
fn double_assign(&mut self) {
65+
IntMod::double_assign(self);
4966
}
67+
68+
fn square_assign(&mut self) {
69+
IntMod::square_assign(self);
70+
}
71+
}
72+
73+
openvm_ecc_sw_macros::sw_declare! {
74+
CurvePoint5mod8 {
75+
mod_type = Fp5mod8,
76+
b = CURVE_B_5MOD8,
77+
},
78+
CurvePoint1mod4 {
79+
mod_type = Fp1mod4,
80+
a = CURVE_A_1MOD4,
81+
b = CURVE_B_1MOD4,
82+
},
5083
}
5184

5285
openvm_ecc_sw_macros::sw_init! {
5386
Secp256k1Point,
54-
MyCurvePoint,
87+
CurvePoint5mod8,
88+
CurvePoint1mod4,
5589
}
5690

5791
// test decompression under an honest host
5892
pub fn main() {
5993
setup_0();
6094
setup_2();
95+
setup_4();
6196
setup_all_curves();
6297

6398
let bytes = read_vec();
@@ -69,13 +104,21 @@ pub fn main() {
69104
// x = 5 is not on the x-coordinate of any point on the Secp256k1 curve
70105
test_impossible_decompression_secp256k1(&Secp256k1Coord::from_u8(5), rec_id);
71106

72-
let x = Fp::from_le_bytes(&bytes[64..96]);
73-
let y = Fp::from_le_bytes(&bytes[96..128]);
107+
let x = Fp5mod8::from_le_bytes(&bytes[64..96]);
108+
let y = Fp5mod8::from_le_bytes(&bytes[96..128]);
74109
let rec_id = y.as_le_bytes()[0] & 1;
75110

76-
test_possible_decompression::<MyCurvePoint>(&x, &y, rec_id);
77-
// x = 3 is not on the x-coordinate of any point on the MyCurve curve
78-
test_impossible_decompression_mycurve(&Fp::from_u8(3), rec_id);
111+
test_possible_decompression::<CurvePoint5mod8>(&x, &y, rec_id);
112+
// x = 3 is not on the x-coordinate of any point on the CurvePoint5mod8 curve
113+
test_impossible_decompression_curvepoint5mod8(&Fp5mod8::from_u8(3), rec_id);
114+
115+
let x = Fp1mod4::from_le_bytes(&bytes[128..160]);
116+
let y = Fp1mod4::from_le_bytes(&bytes[160..192]);
117+
let rec_id = y.as_le_bytes()[0] & 1;
118+
119+
test_possible_decompression::<CurvePoint1mod4>(&x, &y, rec_id);
120+
// x = 1 is not on the x-coordinate of any point on the CurvePoint1mod4 curve
121+
test_impossible_decompression_curvepoint1mod4(&Fp1mod4::from_u8(1), rec_id);
79122
}
80123

81124
fn test_possible_decompression<P: WeierstrassPoint + FromCompressed<P::Coordinate>>(
@@ -98,18 +141,18 @@ fn test_possible_decompression<P: WeierstrassPoint + FromCompressed<P::Coordinat
98141
// The test_impossible_decompression_* functions cannot be combined into a single function with a const generic parameter
99142
// since the get_non_qr() function is not part of the WeierstrassPoint trait.
100143

101-
fn test_impossible_decompression_mycurve(x: &Fp, rec_id: u8) {
102-
let hint = MyCurvePoint::hint_decompress(x, &rec_id).expect("hint should be well-formed");
144+
fn test_impossible_decompression_curvepoint5mod8(x: &Fp5mod8, rec_id: u8) {
145+
let hint = CurvePoint5mod8::hint_decompress(x, &rec_id).expect("hint should be well-formed");
103146
if hint.possible {
104147
panic!("decompression should be impossible");
105148
} else {
106149
let rhs = x * x * x
107-
+ x * &<MyCurvePoint as WeierstrassPoint>::CURVE_A
108-
+ &<MyCurvePoint as WeierstrassPoint>::CURVE_B;
109-
assert_eq!(&hint.sqrt * &hint.sqrt, rhs * MyCurvePoint::get_non_qr());
150+
+ x * &<CurvePoint5mod8 as WeierstrassPoint>::CURVE_A
151+
+ &<CurvePoint5mod8 as WeierstrassPoint>::CURVE_B;
152+
assert_eq!(&hint.sqrt * &hint.sqrt, rhs * CurvePoint5mod8::get_non_qr());
110153
}
111154

112-
let p = MyCurvePoint::decompress(x.clone(), &rec_id);
155+
let p = CurvePoint5mod8::decompress(x.clone(), &rec_id);
113156
assert!(p.is_none());
114157
}
115158

@@ -127,3 +170,18 @@ fn test_impossible_decompression_secp256k1(x: &Secp256k1Coord, rec_id: u8) {
127170
let p = Secp256k1Point::decompress(x.clone(), &rec_id);
128171
assert!(p.is_none());
129172
}
173+
174+
fn test_impossible_decompression_curvepoint1mod4(x: &Fp1mod4, rec_id: u8) {
175+
let hint = CurvePoint1mod4::hint_decompress(x, &rec_id).expect("hint should be well-formed");
176+
if hint.possible {
177+
panic!("decompression should be impossible");
178+
} else {
179+
let rhs = x * x * x
180+
+ x * &<CurvePoint1mod4 as WeierstrassPoint>::CURVE_A
181+
+ &<CurvePoint1mod4 as WeierstrassPoint>::CURVE_B;
182+
assert_eq!(&hint.sqrt * &hint.sqrt, rhs * CurvePoint1mod4::get_non_qr());
183+
}
184+
185+
let p = CurvePoint1mod4::decompress(x.clone(), &rec_id);
186+
assert!(p.is_none());
187+
}

0 commit comments

Comments
 (0)