Skip to content

Commit 244546b

Browse files
committed
Improve Edwards448 decompression checks
1 parent 912d939 commit 244546b

File tree

4 files changed

+79
-3
lines changed

4 files changed

+79
-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: 68 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,
@@ -240,7 +240,7 @@ impl CompressedEdwardsY {
240240
/// - if the input point has nonzero torsion component.
241241
pub fn decompress(&self) -> CtOption<EdwardsPoint> {
242242
self.decompress_unchecked()
243-
.and_then(|pt| CtOption::new(pt, pt.is_on_curve() & pt.is_torsion_free()))
243+
.and_then(|pt| CtOption::new(pt, pt.is_in_subgroup()))
244244
}
245245

246246
/// View this `CompressedEdwardsY` as an array of bytes.
@@ -661,6 +661,59 @@ impl EdwardsPoint {
661661
AffinePoint { x, y }
662662
}
663663

664+
// See https://eprint.iacr.org/2022/1164.
665+
fn is_in_subgroup(self) -> Choice {
666+
const A: FieldElement = FieldElement(ConstMontyType::new(&U448::from_be_hex(
667+
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffeceaf",
668+
)));
669+
const A1: FieldElement = FieldElement(ConstMontyType::new(&U448::from_u64(156320)));
670+
const MINUS_SQRT_B1: FieldElement = FieldElement(ConstMontyType::new(&U448::from_be_hex(
671+
"749a7410536c225f1025ca374176557d7839611d691caad26d74a1fca5cfad15f196642c0a4484b67f321025577cc6b5a6f443c2eaa36327",
672+
)));
673+
674+
let mut e = self.X * (self.Z - self.Y);
675+
let ee = e.square();
676+
let mut u = FieldElement::A_PLUS_TWO_OVER_FOUR * (self.Z + self.Y) * e * self.X;
677+
let w = self.Z.double() * (self.Z - self.Y);
678+
679+
let u2 = u.double().double();
680+
let w2 = w.double();
681+
682+
let mut w1 = u2.sqrt();
683+
let mut ok = w1.square().ct_eq(&u2);
684+
let u1 = (u2 - A1 * ee - w1 * w2).half();
685+
686+
// If `u1` happens not to be a square, then `sqrt(u1)` returns `sqrt(-u1)`
687+
// in that case (since we are in a finite field GF(q) with q = 3 mod 4,
688+
// if `u1` is not a square then `-u1` must be a square). In such a case, we
689+
// should replace `(u1,w1)` with `((B1*e^4)/u1, -w1)`. To avoid the division,
690+
// we instead switch to an isomorphic curve; namely:
691+
// u2 = B1*(e^4)*u1
692+
// w2 = -w1*u1
693+
// e2 = e*u1
694+
// Then:
695+
// w = sqrt(u2) = sqrt(-B1)*(e^2)*sqrt(-u1)
696+
// u = (w^2 - A*e^2 - w*w1)/2
697+
let mut w = u1.sqrt();
698+
let u1_is_square = w.square().ct_eq(&u1);
699+
w1.conditional_assign(&-(w1 * u1), !u1_is_square);
700+
e.conditional_assign(&(e * u1), !u1_is_square);
701+
w.conditional_assign(&(MINUS_SQRT_B1 * ee * w), !u1_is_square);
702+
u = (w.square() - A * e.square() - w * w1).half();
703+
704+
ok &= u.is_square();
705+
706+
// If the source point was a low-order point, then the computations
707+
// above are incorrect. We handle this case here; among the
708+
// low-order points, only the neutral point is in the prime-order
709+
// subgroup.
710+
let is_low_order = self.X.is_zero() | self.Y.is_zero();
711+
let is_neutral = self.Y.ct_eq(&self.Z);
712+
ok ^= is_low_order & (ok ^ is_neutral);
713+
714+
ok
715+
}
716+
664717
/// Edwards_Isogeny is derived from the doubling formula
665718
/// XXX: There is a duplicate method in the twisted edwards module to compute the dual isogeny
666719
/// XXX: Not much point trying to make it generic I think. So what we can do is optimise each respective isogeny method for a=1 or a = -1 (currently, I just made it really slow and simple)
@@ -972,6 +1025,8 @@ mod tests {
9721025
use super::*;
9731026
use elliptic_curve::Field;
9741027
use hex_literal::hex;
1028+
use proptest::prelude::any;
1029+
use proptest::proptest;
9751030
use rand_core::TryRngCore;
9761031

9771032
fn hex_to_field(hex: &'static str) -> FieldElement {
@@ -1267,4 +1322,15 @@ mod tests {
12671322

12681323
assert_eq!(computed_commitment, expected_commitment);
12691324
}
1325+
1326+
proptest! {
1327+
#[test]
1328+
fn is_in_subgroup(
1329+
bytes in any::<[u8; 57]>()
1330+
) {
1331+
let scalar = EdwardsScalar::from_bytes_mod_order(&bytes.into());
1332+
let point = EdwardsPoint::mul_by_generator(&scalar);
1333+
assert_eq!(point.is_in_subgroup().unwrap_u8(), 1);
1334+
}
1335+
}
12701336
}

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)