Skip to content

Commit a86b193

Browse files
committed
Improve Edwards448 decompression checks
1 parent e25ccd1 commit a86b193

File tree

4 files changed

+76
-4
lines changed

4 files changed

+76
-4
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ed448-goldilocks/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ serde = ["dep:serdect", "ed448?/serde_bytes"]
4040
criterion = { version = "0.7", default-features = false, features = ["cargo_bench_support"] }
4141
hex-literal = "1"
4242
hex = "0.4"
43+
proptest = "1"
4344
rand_core = { version = "0.9", features = ["os_rng"] }
4445
rand_chacha = "0.9"
4546
serde_bare = "0.5"

ed448-goldilocks/src/edwards/extended.rs

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::curve::scalar_mul::variable_base;
77
use crate::curve::twedwards::IsogenyMap;
88
use crate::curve::twedwards::extended::ExtendedPoint as TwistedExtendedPoint;
99
use crate::edwards::affine::PointBytes;
10-
use crate::field::FieldElement;
10+
use crate::field::{ConstMontyType, FieldElement};
1111
use crate::*;
1212
use elliptic_curve::{
1313
BatchNormalize, CurveGroup, Error,
@@ -474,8 +474,57 @@ impl EdwardsPoint {
474474
/// prime-order subgroup;
475475
/// * `false` if `self` has a nonzero torsion component and is not
476476
/// in the prime-order subgroup.
477+
// See https://eprint.iacr.org/2022/1164.
477478
pub fn is_torsion_free(&self) -> Choice {
478-
(self * EdwardsScalar::new(*ORDER)).ct_eq(&Self::IDENTITY)
479+
const A: FieldElement = FieldElement(ConstMontyType::new(&U448::from_be_hex(
480+
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffeceaf",
481+
)));
482+
const A1: FieldElement = FieldElement(ConstMontyType::new(&U448::from_u64(156320)));
483+
const MINUS_SQRT_B1: FieldElement = FieldElement(ConstMontyType::new(&U448::from_be_hex(
484+
"749a7410536c225f1025ca374176557d7839611d691caad26d74a1fca5cfad15f196642c0a4484b67f321025577cc6b5a6f443c2eaa36327",
485+
)));
486+
487+
let mut e = self.X * (self.Z - self.Y);
488+
let ee = e.square();
489+
let mut u = FieldElement::A_PLUS_TWO_OVER_FOUR * (self.Z + self.Y) * e * self.X;
490+
let w = self.Z.double() * (self.Z - self.Y);
491+
492+
let u2 = u.double().double();
493+
let w2 = w.double();
494+
495+
let mut w1 = u2.sqrt();
496+
let mut ok = w1.square().ct_eq(&u2);
497+
let u1 = (u2 - A1 * ee - w1 * w2).div_by_2();
498+
499+
// If `u1` happens not to be a square, then `sqrt(u1)` returns `sqrt(-u1)`
500+
// in that case (since we are in a finite field GF(q) with q = 3 mod 4,
501+
// if `u1` is not a square then `-u1` must be a square). In such a case, we
502+
// should replace `(u1,w1)` with `((B1*e^4)/u1, -w1)`. To avoid the division,
503+
// we instead switch to an isomorphic curve; namely:
504+
// u2 = B1*(e^4)*u1
505+
// w2 = -w1*u1
506+
// e2 = e*u1
507+
// Then:
508+
// w = sqrt(u2) = sqrt(-B1)*(e^2)*sqrt(-u1)
509+
// u = (w^2 - A*e^2 - w*w1)/2
510+
let mut w = u1.sqrt();
511+
let u1_is_square = w.square().ct_eq(&u1);
512+
w1.conditional_assign(&-(w1 * u1), !u1_is_square);
513+
e.conditional_assign(&(e * u1), !u1_is_square);
514+
w.conditional_assign(&(MINUS_SQRT_B1 * ee * w), !u1_is_square);
515+
u = (w.square() - A * e.square() - w * w1).div_by_2();
516+
517+
ok &= u.is_square();
518+
519+
// If the source point was a low-order point, then the computations
520+
// above are incorrect. We handle this case here; among the
521+
// low-order points, only the neutral point is in the prime-order
522+
// subgroup.
523+
let is_low_order = self.X.is_zero() | self.Y.is_zero();
524+
let is_neutral = self.Y.ct_eq(&self.Z);
525+
ok ^= is_low_order & (ok ^ is_neutral);
526+
527+
ok
479528
}
480529

481530
/// Hash a message to a point on the curve
@@ -791,7 +840,9 @@ mod tests {
791840
use super::*;
792841
use elliptic_curve::Field;
793842
use hex_literal::hex;
794-
use rand_core::OsRng;
843+
use proptest::prelude::any;
844+
use proptest::proptest;
845+
use rand_core::{OsRng, TryRngCore};
795846

796847
fn hex_to_field(hex: &'static str) -> FieldElement {
797848
assert_eq!(hex.len(), 56 * 2);
@@ -1115,4 +1166,15 @@ mod tests {
11151166
assert_eq!(affine_point, point.to_affine());
11161167
}
11171168
}
1169+
1170+
proptest! {
1171+
#[test]
1172+
fn fuzz_is_torsion_free(
1173+
bytes in any::<[u8; 57]>()
1174+
) {
1175+
let scalar = EdwardsScalar::from_bytes_mod_order(&bytes.into());
1176+
let point = EdwardsPoint::mul_by_generator(&scalar);
1177+
assert_eq!(point.is_torsion_free().unwrap_u8(), 1);
1178+
}
1179+
}
11181180
}

ed448-goldilocks/src/field/element.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use elliptic_curve::{
1111
Field,
1212
array::Array,
1313
bigint::{
14-
Integer, NonZero, U448, U704,
14+
Integer, NonZero, U448, U704, Zero,
1515
consts::{U56, U84, U88},
1616
modular::ConstMontyParams,
1717
},
@@ -331,6 +331,10 @@ impl FieldElement {
331331
self.0.retrieve().is_odd()
332332
}
333333

334+
pub fn is_zero(&self) -> Choice {
335+
self.0.is_zero()
336+
}
337+
334338
/// Inverts a field element
335339
pub fn invert(&self) -> Self {
336340
Self(self.0.invert().unwrap_or(ConstMontyType::default()))
@@ -440,6 +444,10 @@ impl FieldElement {
440444
(inv_sqrt_x * u, zero_u | is_res)
441445
}
442446

447+
pub(crate) fn div_by_2(&self) -> FieldElement {
448+
Self(self.0.div_by_2())
449+
}
450+
443451
pub(crate) fn map_to_curve_elligator2(&self) -> AffinePoint {
444452
let mut t1 = self.square(); // 1. t1 = u^2
445453
t1 *= Self::Z; // 2. t1 = Z * t1 // Z * u^2

0 commit comments

Comments
 (0)