Skip to content

Commit 0b51143

Browse files
committed
Improve Edwards448 decompression checks
1 parent 912d939 commit 0b51143

File tree

4 files changed

+75
-3
lines changed

4 files changed

+75
-3
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
@@ -39,6 +39,7 @@ serde = ["dep:serdect", "ed448?/serde_bytes"]
3939
[dev-dependencies]
4040
hex-literal = "1"
4141
hex = "0.4"
42+
proptest = "1"
4243
rand_core = { version = "0.9", features = ["os_rng"] }
4344
rand_chacha = "0.9"
4445
serde_bare = "0.5"

ed448-goldilocks/src/edwards/extended.rs

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
55

66
use crate::curve::scalar_mul::variable_base;
77
use crate::curve::twedwards::extended::ExtendedPoint as TwistedExtendedPoint;
8-
use crate::field::FieldElement;
8+
use crate::field::{ConstMontyType, FieldElement};
99
use crate::*;
1010
use elliptic_curve::{
1111
CurveGroup, Error,
@@ -722,8 +722,57 @@ impl EdwardsPoint {
722722
/// prime-order subgroup;
723723
/// * `false` if `self` has a nonzero torsion component and is not
724724
/// in the prime-order subgroup.
725+
// See https://eprint.iacr.org/2022/1164.
725726
pub fn is_torsion_free(&self) -> Choice {
726-
(self * EdwardsScalar::new(ORDER)).ct_eq(&Self::IDENTITY)
727+
const A: FieldElement = FieldElement(ConstMontyType::new(&U448::from_be_hex(
728+
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffeceaf",
729+
)));
730+
const A1: FieldElement = FieldElement(ConstMontyType::new(&U448::from_u64(156320)));
731+
const MINUS_SQRT_B1: FieldElement = FieldElement(ConstMontyType::new(&U448::from_be_hex(
732+
"749a7410536c225f1025ca374176557d7839611d691caad26d74a1fca5cfad15f196642c0a4484b67f321025577cc6b5a6f443c2eaa36327",
733+
)));
734+
735+
let mut e = self.X * (self.Z - self.Y);
736+
let ee = e.square();
737+
let mut u = FieldElement::A_PLUS_TWO_OVER_FOUR * (self.Z + self.Y) * e * self.X;
738+
let w = self.Z.double() * (self.Z - self.Y);
739+
740+
let u2 = u.double().double();
741+
let w2 = w.double();
742+
743+
let mut w1 = u2.sqrt();
744+
let mut ok = w1.square().ct_eq(&u2);
745+
let u1 = (u2 - A1 * ee - w1 * w2).half();
746+
747+
// If `u1` happens not to be a square, then `sqrt(u1)` returns `sqrt(-u1)`
748+
// in that case (since we are in a finite field GF(q) with q = 3 mod 4,
749+
// if `u1` is not a square then `-u1` must be a square). In such a case, we
750+
// should replace `(u1,w1)` with `((B1*e^4)/u1, -w1)`. To avoid the division,
751+
// we instead switch to an isomorphic curve; namely:
752+
// u2 = B1*(e^4)*u1
753+
// w2 = -w1*u1
754+
// e2 = e*u1
755+
// Then:
756+
// w = sqrt(u2) = sqrt(-B1)*(e^2)*sqrt(-u1)
757+
// u = (w^2 - A*e^2 - w*w1)/2
758+
let mut w = u1.sqrt();
759+
let u1_is_square = w.square().ct_eq(&u1);
760+
w1.conditional_assign(&-(w1 * u1), !u1_is_square);
761+
e.conditional_assign(&(e * u1), !u1_is_square);
762+
w.conditional_assign(&(MINUS_SQRT_B1 * ee * w), !u1_is_square);
763+
u = (w.square() - A * e.square() - w * w1).half();
764+
765+
ok &= u.is_square();
766+
767+
// If the source point was a low-order point, then the computations
768+
// above are incorrect. We handle this case here; among the
769+
// low-order points, only the neutral point is in the prime-order
770+
// subgroup.
771+
let is_low_order = self.X.is_zero() | self.Y.is_zero();
772+
let is_neutral = self.Y.ct_eq(&self.Z);
773+
ok ^= is_low_order & (ok ^ is_neutral);
774+
775+
ok
727776
}
728777

729778
/// Hash a message to a point on the curve
@@ -972,6 +1021,8 @@ mod tests {
9721021
use super::*;
9731022
use elliptic_curve::Field;
9741023
use hex_literal::hex;
1024+
use proptest::prelude::any;
1025+
use proptest::proptest;
9751026
use rand_core::TryRngCore;
9761027

9771028
fn hex_to_field(hex: &'static str) -> FieldElement {
@@ -1267,4 +1318,15 @@ mod tests {
12671318

12681319
assert_eq!(computed_commitment, expected_commitment);
12691320
}
1321+
1322+
proptest! {
1323+
#[test]
1324+
fn fuzz_is_torsion_free(
1325+
bytes in any::<[u8; 57]>()
1326+
) {
1327+
let scalar = EdwardsScalar::from_bytes_mod_order(&bytes.into());
1328+
let point = EdwardsPoint::mul_by_generator(&scalar);
1329+
assert_eq!(point.is_torsion_free().unwrap_u8(), 1);
1330+
}
1331+
}
12701332
}

ed448-goldilocks/src/field/element.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
use elliptic_curve::{
1010
array::Array,
1111
bigint::{
12-
Integer, NonZero, U448, U704,
12+
Integer, NonZero, U448, U704, Zero,
1313
consts::{U56, U84, U88},
1414
},
1515
group::cofactor::CofactorGroup,
@@ -261,6 +261,10 @@ impl FieldElement {
261261
self.0.retrieve().is_odd()
262262
}
263263

264+
pub fn is_zero(&self) -> Choice {
265+
self.0.is_zero()
266+
}
267+
264268
/// Inverts a field element
265269
/// Previous chain length: 462, new length 460
266270
pub fn invert(&self) -> Self {
@@ -368,6 +372,10 @@ impl FieldElement {
368372
(inv_sqrt_x * u, zero_u | is_res)
369373
}
370374

375+
pub(crate) fn half(&self) -> FieldElement {
376+
Self(self.0.div_by_2())
377+
}
378+
371379
pub(crate) fn map_to_curve_elligator2(&self) -> AffinePoint {
372380
let mut t1 = self.square(); // 1. t1 = u^2
373381
t1 *= Self::Z; // 2. t1 = Z * t1 // Z * u^2

0 commit comments

Comments
 (0)