Skip to content

Commit dba9714

Browse files
fix(ecc): checked from_be_bytes (#1746)
Fixes some panics in `from_sec1_bytes` where it should error instead. However the root cause of many places is that `IntMod::from_{le,be}_bytes` previously does not require the input bytes to be reduced. While this is a feature of how we represent field elements up to equivalence, in practice in cryptographic applications other implementations always require the input to be in canonical (reduced) from. To match other implementations and prevent future errors, I have renamed the former `from_{le,be}_bytes` to `from_{le,be}_bytes_unchecked` and added new checked versions that return `Option`. Also fixed implementations of `Decompress` for p256,k256 such that it rejects when the input `x` bytes are not reduced.
1 parent e5ff381 commit dba9714

File tree

34 files changed

+285
-140
lines changed

34 files changed

+285
-140
lines changed

benchmarks/guest/kitchen-sink/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ pub fn main() {
9797

9898
let mut bytes = [0u8; 32];
9999
bytes[7] = 1 << 5; // 2^61 = modulus + 1
100-
let mut res = Mersenne61::from_le_bytes(&bytes); // No need to start from reduced representation
100+
let mut res = Mersenne61::from_le_bytes_unchecked(&bytes); // No need to start from reduced representation
101101
for _ in 0..61 {
102102
res += res.clone();
103103
}

benchmarks/guest/pairing/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub fn main() {
4343
// SAFETY: We're reading `6 * 32 == PAIR_ELEMENT_LEN` bytes from `input[idx..]`
4444
// per iteration. This is guaranteed to be in-bounds.
4545
let slice = unsafe { input.get_unchecked(start..start + 32) };
46-
Fp::from_be_bytes(&slice[..32])
46+
Fp::from_be_bytes_unchecked(&slice[..32])
4747
};
4848
// https://eips.ethereum.org/EIPS/eip-197, Fp2 is encoded as (a, b) where a * i + b
4949
let g1_x = read_fq_at(0);

book/src/custom-extensions/algebra.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The functional part is provided by the `openvm-algebra-guest` crate, which is a
1212
- `Repr` typically is `[u8; NUM_LIMBS]`, representing the number's underlying storage.
1313
- `MODULUS` is the compile-time known modulus.
1414
- `ZERO` and `ONE` represent the additive and multiplicative identities, respectively.
15-
- Constructors include `from_repr`, `from_le_bytes`, `from_be_bytes`, `from_u8`, `from_u32`, and `from_u64`.
15+
- Constructors include `from_repr`, `from_le_bytes`, `from_be_bytes`, `from_le_bytes_unchecked`, `from_be_bytes_unchecked`, `from_u8`, `from_u32`, and `from_u64`.
1616

1717
- `Field` trait:
1818
Provides constants `ZERO` and `ONE` and methods for basic arithmetic operations within a field.

examples/ecc/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ openvm_ecc_guest::sw_macros::sw_init! {
2222
// ANCHOR: main
2323
pub fn main() {
2424
let x1 = Secp256k1Coord::from_u32(1);
25-
let y1 = Secp256k1Coord::from_le_bytes(&hex!(
25+
let y1 = Secp256k1Coord::from_le_bytes_unchecked(&hex!(
2626
"EEA7767E580D75BC6FDD7F58D2A84C2614FB22586068DB63B346C6E60AF21842"
2727
));
2828
let p1 = Secp256k1Point::from_xy_nonidentity(x1, y1).unwrap();
2929

3030
let x2 = Secp256k1Coord::from_u32(2);
31-
let y2 = Secp256k1Coord::from_le_bytes(&hex!(
31+
let y2 = Secp256k1Coord::from_le_bytes_unchecked(&hex!(
3232
"D1A847A8F879E0AEE32544DA5BA0B3BD1703A1F52867A5601FF6454DD8180499"
3333
));
3434
let p2 = Secp256k1Point::from_xy_nonidentity(x2, y2).unwrap();

examples/pairing/src/main.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,31 @@ openvm_algebra_complex_macros::complex_init! {
2727
// ANCHOR: main
2828
pub fn main() {
2929
let p0 = AffinePoint::new(
30-
Fp::from_be_bytes(&hex!("17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb")),
31-
Fp::from_be_bytes(&hex!("08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1"))
30+
Fp::from_be_bytes_unchecked(&hex!("17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb")),
31+
Fp::from_be_bytes_unchecked(&hex!("08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1"))
3232
);
3333
let p1 = AffinePoint::new(
3434
Fp2::from_coeffs([
35-
Fp::from_be_bytes(&hex!("1638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053")),
36-
Fp::from_be_bytes(&hex!("0a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577"))
35+
Fp::from_be_bytes_unchecked(&hex!("1638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053")),
36+
Fp::from_be_bytes_unchecked(&hex!("0a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577"))
3737
]),
3838
Fp2::from_coeffs([
39-
Fp::from_be_bytes(&hex!("0468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899")),
40-
Fp::from_be_bytes(&hex!("0f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3"))
39+
Fp::from_be_bytes_unchecked(&hex!("0468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899")),
40+
Fp::from_be_bytes_unchecked(&hex!("0f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3"))
4141
]),
4242
);
4343
let q0 = AffinePoint::new(
44-
Fp::from_be_bytes(&hex!("0572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e")),
45-
Fp::from_be_bytes(&hex!("166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28"))
44+
Fp::from_be_bytes_unchecked(&hex!("0572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e")),
45+
Fp::from_be_bytes_unchecked(&hex!("166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28"))
4646
);
4747
let q1 = AffinePoint::new(
4848
Fp2::from_coeffs([
49-
Fp::from_be_bytes(&hex!("024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8")),
50-
Fp::from_be_bytes(&hex!("13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e"))
49+
Fp::from_be_bytes_unchecked(&hex!("024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8")),
50+
Fp::from_be_bytes_unchecked(&hex!("13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e"))
5151
]),
5252
Fp2::from_coeffs([
53-
Fp::from_be_bytes(&hex!("0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801")),
54-
Fp::from_be_bytes(&hex!("0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be"))
53+
Fp::from_be_bytes_unchecked(&hex!("0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801")),
54+
Fp::from_be_bytes_unchecked(&hex!("0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be"))
5555
]),
5656
);
5757

extensions/algebra/guest/src/lib.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,21 @@ pub trait IntMod:
151151
/// Does not enforce the integer value of `bytes` must be less than the modulus.
152152
fn from_repr(repr: Self::Repr) -> Self;
153153

154+
/// Creates a new IntMod from an array of bytes, little endian.
155+
/// Returns `None` if the integer value of `bytes` is greater than or equal to the modulus.
156+
fn from_le_bytes(bytes: &[u8]) -> Option<Self>;
157+
158+
/// Creates a new IntMod from an array of bytes, big endian.
159+
/// Returns `None` if the integer value of `bytes` is greater than or equal to the modulus.
160+
fn from_be_bytes(bytes: &[u8]) -> Option<Self>;
161+
154162
/// Creates a new IntMod from an array of bytes, little endian.
155163
/// Does not enforce the integer value of `bytes` must be less than the modulus.
156-
fn from_le_bytes(bytes: &[u8]) -> Self;
164+
fn from_le_bytes_unchecked(bytes: &[u8]) -> Self;
157165

158166
/// Creates a new IntMod from an array of bytes, big endian.
159167
/// Does not enforce the integer value of `bytes` must be less than the modulus.
160-
fn from_be_bytes(bytes: &[u8]) -> Self;
168+
fn from_be_bytes_unchecked(bytes: &[u8]) -> Self;
161169

162170
/// Creates a new IntMod from a u8.
163171
/// Does not enforce the integer value of `bytes` must be less than the modulus.

extensions/algebra/moduli-macros/src/lib.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -391,13 +391,31 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream {
391391
Self(repr)
392392
}
393393

394-
fn from_le_bytes(bytes: &[u8]) -> Self {
394+
fn from_le_bytes(bytes: &[u8]) -> Option<Self> {
395+
let elt = Self::from_le_bytes_unchecked(bytes);
396+
if elt.is_reduced() {
397+
Some(elt)
398+
} else {
399+
None
400+
}
401+
}
402+
403+
fn from_be_bytes(bytes: &[u8]) -> Option<Self> {
404+
let elt = Self::from_be_bytes_unchecked(bytes);
405+
if elt.is_reduced() {
406+
Some(elt)
407+
} else {
408+
None
409+
}
410+
}
411+
412+
fn from_le_bytes_unchecked(bytes: &[u8]) -> Self {
395413
let mut arr = [0u8; #limbs];
396414
arr.copy_from_slice(bytes);
397415
Self(arr)
398416
}
399417

400-
fn from_be_bytes(bytes: &[u8]) -> Self {
418+
fn from_be_bytes_unchecked(bytes: &[u8]) -> Self {
401419
let mut arr = [0u8; #limbs];
402420
for (a, b) in arr.iter_mut().zip(bytes.iter().rev()) {
403421
*a = *b;
@@ -761,11 +779,12 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream {
761779
fn reduce_le_bytes(bytes: &[u8]) -> Self {
762780
let mut res = <Self as openvm_algebra_guest::IntMod>::ZERO;
763781
// base should be 2 ^ #limbs which exceeds what Self can represent
764-
let mut base = <Self as openvm_algebra_guest::IntMod>::from_le_bytes(&[255u8; #limbs]);
782+
let mut base = <Self as openvm_algebra_guest::IntMod>::from_le_bytes_unchecked(&[255u8; #limbs]);
765783
base += <Self as openvm_algebra_guest::IntMod>::ONE;
766784
for chunk in bytes.chunks(#limbs).rev() {
767-
res = res * &base + <Self as openvm_algebra_guest::IntMod>::from_le_bytes(chunk);
785+
res = res * &base + <Self as openvm_algebra_guest::IntMod>::from_le_bytes_unchecked(chunk);
768786
}
787+
openvm_algebra_guest::IntMod::assert_reduced(&res);
769788
res
770789
}
771790
}

extensions/algebra/tests/programs/examples/little.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub fn main() {
3434
assert_eq!(res, inv);
3535

3636
let two = Secp256k1Coord::from_u32(2);
37-
let minus_two = Secp256k1Coord::from_le_bytes(&pow);
37+
let minus_two = Secp256k1Coord::from_le_bytes_unchecked(&pow);
3838

3939
assert_eq!(res - &minus_two, inv + &two);
4040

extensions/algebra/tests/programs/examples/moduli_setup.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,18 @@ pub fn main() {
3232
}
3333
assert_eq!(res, Mersenne61::from_u32(1));
3434

35-
let mut non_reduced = Mersenne61::from_le_bytes(&[0xff; 32]);
35+
let mut non_reduced = Mersenne61::from_le_bytes_unchecked(&[0xff; 32]);
3636
assert!(!non_reduced.is_reduced());
3737
let reduced = &non_reduced + &Mersenne61::ZERO;
3838
reduced.assert_reduced();
3939

4040
assert_eq!(&non_reduced + &non_reduced, reduced.double());
4141

42-
non_reduced = Mersenne61::from_le_bytes(&Mersenne61::MODULUS);
42+
non_reduced = Mersenne61::from_le_bytes_unchecked(&Mersenne61::MODULUS);
4343
assert!(!non_reduced.is_reduced());
4444

4545
let mut bytes = [0u8; 32];
4646
bytes[7] = 1 << 5; // 2^61 = 2^{8*7 + 5} = modulus + 1
47-
non_reduced = Mersenne61::from_le_bytes(&bytes);
47+
non_reduced = Mersenne61::from_le_bytes_unchecked(&bytes);
4848
assert!(!non_reduced.is_reduced());
4949
}

extensions/ecc/guest/src/ecdsa.rs

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,16 @@ where
128128
}
129129

130130
Tag::CompressedEvenY | Tag::CompressedOddY => {
131-
let x = Coordinate::<C>::from_be_bytes(&bytes[1..]);
131+
let x = Coordinate::<C>::from_be_bytes(&bytes[1..]).ok_or_else(Error::new)?;
132132
let rec_id = bytes[0] & 1;
133133
let point = FromCompressed::decompress(x, &rec_id).ok_or_else(Error::new)?;
134134
Ok(Self { point })
135135
}
136136

137137
Tag::Uncompressed => {
138138
let (x_bytes, y_bytes) = bytes[1..].split_at(Coordinate::<C>::NUM_LIMBS);
139-
let x = Coordinate::<C>::from_be_bytes(x_bytes);
140-
let y = Coordinate::<C>::from_be_bytes(y_bytes);
139+
let x = Coordinate::<C>::from_be_bytes(x_bytes).ok_or_else(Error::new)?;
140+
let y = Coordinate::<C>::from_be_bytes(y_bytes).ok_or_else(Error::new)?;
141141
let point = <C as IntrinsicCurve>::Point::from_xy(x, y).ok_or_else(Error::new)?;
142142
Ok(Self { point })
143143
}
@@ -441,11 +441,8 @@ where
441441
// Signature is default encoded in big endian bytes
442442
let (r_be, s_be) = sig.split_at(<C as IntrinsicCurve>::Scalar::NUM_LIMBS);
443443
// Note: Scalar internally stores using little endian
444-
let r = Scalar::<C>::from_be_bytes(r_be);
445-
let s = Scalar::<C>::from_be_bytes(s_be);
446-
if !r.is_reduced() || !s.is_reduced() {
447-
return Err(Error::new());
448-
}
444+
let r = Scalar::<C>::from_be_bytes(r_be).ok_or_else(Error::new)?;
445+
let s = Scalar::<C>::from_be_bytes(s_be).ok_or_else(Error::new)?;
449446
if r == Scalar::<C>::ZERO || s == Scalar::<C>::ZERO {
450447
return Err(Error::new());
451448
}
@@ -456,7 +453,7 @@ where
456453
let trim = prehash_bytes.len().saturating_sub(Scalar::<C>::NUM_LIMBS);
457454
// from_be_bytes still works if len < Scalar::NUM_LIMBS
458455
// we don't need to reduce because IntMod is up to modular equivalence
459-
let z = Scalar::<C>::from_be_bytes(&prehash_bytes[..prehash_bytes.len() - trim]);
456+
let z = Scalar::<C>::from_be_bytes_unchecked(&prehash_bytes[..prehash_bytes.len() - trim]);
460457

461458
// `r` is in the Scalar field, we now possibly add C::ORDER to it to get `x`
462459
// in the Coordinate field.
@@ -481,10 +478,7 @@ where
481478
};
482479
}
483480
assert!(FieldBytesSize::<C>::USIZE <= Coordinate::<C>::NUM_LIMBS);
484-
let x = Coordinate::<C>::from_be_bytes(&r_bytes);
485-
if !x.is_reduced() {
486-
return Err(Error::new());
487-
}
481+
let x = Coordinate::<C>::from_be_bytes(&r_bytes).ok_or_else(Error::new)?;
488482
let rec_id = recovery_id.to_byte();
489483
// The point R decompressed from x-coordinate `r`
490484
let R: C::Point = FromCompressed::decompress(x, &rec_id).ok_or_else(Error::new)?;
@@ -518,11 +512,8 @@ where
518512
// Signature is default encoded in big endian bytes
519513
let (r_be, s_be) = sig.split_at(<C as IntrinsicCurve>::Scalar::NUM_LIMBS);
520514
// Note: Scalar internally stores using little endian
521-
let r = Scalar::<C>::from_be_bytes(r_be);
522-
let s = Scalar::<C>::from_be_bytes(s_be);
523-
if !r.is_reduced() || !s.is_reduced() {
524-
return Err(Error::new());
525-
}
515+
let r = Scalar::<C>::from_be_bytes(r_be).ok_or_else(Error::new)?;
516+
let s = Scalar::<C>::from_be_bytes(s_be).ok_or_else(Error::new)?;
526517
if r == Scalar::<C>::ZERO || s == Scalar::<C>::ZERO {
527518
return Err(Error::new());
528519
}
@@ -533,7 +524,7 @@ where
533524
let trim = prehash_bytes.len().saturating_sub(Scalar::<C>::NUM_LIMBS);
534525
// from_be_bytes still works if len < Scalar::NUM_LIMBS
535526
// we don't need to reduce because IntMod is up to modular equivalence
536-
let z = Scalar::<C>::from_be_bytes(&prehash_bytes[..prehash_bytes.len() - trim]);
527+
let z = Scalar::<C>::from_be_bytes_unchecked(&prehash_bytes[..prehash_bytes.len() - trim]);
537528

538529
let u1 = z.div_unsafe(&s);
539530
let u2 = (&r).div_unsafe(&s);

0 commit comments

Comments
 (0)