Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
92248ba
Rename to `MontgomeryPoint` to `MontgomeryXpoint`
daxpedda Jul 18, 2025
5a53e32
Move `MontgomeryXpoint` to its own module
daxpedda Jul 18, 2025
d307f96
Implement `Eq` for `ProjectiveMontgomeryXpoint`
daxpedda Jul 18, 2025
f9be2d7
Change `MontgomeryPoint` scalar multiplication output to `ProjectiveM…
daxpedda Jul 18, 2025
5506659
Add y-coordinate recovery
daxpedda Jul 18, 2025
76346a9
Make `ProjectiveMontgomeryXpoint::identity()` an associated const
daxpedda Jul 18, 2025
71f9599
Add `ProjectiveMontgomeryXpoint::GENERATOR`
daxpedda Jul 18, 2025
3a2fadd
Document source of `montgomery::differential_add_and_double`
daxpedda Jul 18, 2025
6ca0a3f
Expose Montgomery ladder with additional output internally
daxpedda Jul 18, 2025
8a8124b
Add `ProjectiveMontgomeryXpoint::double()`
daxpedda Jul 18, 2025
14b584f
Add full-coordinate `MontgomeryPoint` skeleton
daxpedda Jul 18, 2025
6947850
Drop `CurveArithmetic` requirement from `CurveWithScalar`
daxpedda Jul 18, 2025
44f3e1b
Add `MontgomeryScalar`
daxpedda Jul 18, 2025
5e66ff4
Add arithmetic operations to Montgomery
daxpedda Jul 18, 2025
0412d03
Add Montgomery <-> Edwards conversions
daxpedda Jul 18, 2025
a9ee7cd
Implement `CurveArithmetic` for Curve448
daxpedda Jul 18, 2025
89d7a75
Implement `Reduce` for `MontgomeryScalar`
daxpedda Jul 19, 2025
6da7e75
Implement hash2curve for `Curve448`
daxpedda Jul 19, 2025
55df86c
Add hash2curve to `ProjectiveMontgomeryXpoint`
daxpedda Jul 19, 2025
70b3a75
Test Montgomery -> Edwards through hash2curve
daxpedda Jul 19, 2025
dddc008
Rename `MontgomeryPoint` fields from `x|y` to `U|V`
daxpedda Jul 26, 2025
3746ef6
Rename `MontgomeryPoint` to `AffineMontgomeryPoint`
daxpedda Jul 26, 2025
f62cc79
Not every full-coordinate Montgomery point is valid
daxpedda Aug 3, 2025
47bb5d7
Add benchmarks
daxpedda Sep 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions ed448-goldilocks/benches/bench.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use ed448_goldilocks::{
Decaf448, DecafPoint, DecafScalar, Ed448, EdwardsPoint, EdwardsScalar, MontgomeryPoint,
Curve448, Decaf448, DecafPoint, DecafScalar, Ed448, EdwardsPoint, EdwardsScalar,
MontgomeryScalar, MontgomeryXpoint, ProjectiveMontgomeryPoint, ProjectiveMontgomeryXpoint,
};
use elliptic_curve::group::GroupEncoding;
use elliptic_curve::{Field, Group};
Expand Down Expand Up @@ -123,22 +124,76 @@ pub fn decaf448(c: &mut Criterion) {
group.finish();
}

pub fn curve448(c: &mut Criterion) {
let mut group = c.benchmark_group("Curve448");

group.bench_function("scalar multiplication", |b| {
b.iter_batched(
|| {
let point = ProjectiveMontgomeryPoint::try_from_rng(&mut OsRng).unwrap();
let scalar = MontgomeryScalar::try_from_rng(&mut OsRng).unwrap();
(point, scalar)
},
|(point, scalar)| point * scalar,
BatchSize::SmallInput,
)
});

group.bench_function("point addition", |b| {
b.iter_batched(
|| {
let p1 = ProjectiveMontgomeryPoint::try_from_rng(&mut OsRng).unwrap();
let p2 = ProjectiveMontgomeryPoint::try_from_rng(&mut OsRng).unwrap();
(p1, p2)
},
|(p1, p2)| p1 + p2,
BatchSize::SmallInput,
)
});

group.bench_function("encode_to_curve", |b| {
b.iter_batched(
|| {
let mut msg = [0; 64];
OsRng.try_fill_bytes(&mut msg).unwrap();
msg
},
|msg| Curve448::encode_from_bytes(&msg, b"test DST"),
BatchSize::SmallInput,
)
});

group.finish();
}

pub fn x448(c: &mut Criterion) {
let mut group = c.benchmark_group("X448");

group.bench_function("scalar multiplication", |b| {
b.iter_batched(
|| {
let mut point = MontgomeryPoint::default();
let mut point = MontgomeryXpoint::default();
OsRng.try_fill_bytes(&mut point.0).unwrap();
let scalar = EdwardsScalar::try_from_rng(&mut OsRng).unwrap();
let scalar = MontgomeryScalar::try_from_rng(&mut OsRng).unwrap();
(point, scalar)
},
|(point, scalar)| &point * &scalar,
BatchSize::SmallInput,
)
});

group.bench_function("encode_to_curve", |b| {
b.iter_batched(
|| {
let mut msg = [0; 64];
OsRng.try_fill_bytes(&mut msg).unwrap();
msg
},
|msg| ProjectiveMontgomeryXpoint::encode_with_defaults(&msg, b"test DST"),
BatchSize::SmallInput,
)
});

group.finish();
}

Expand Down
31 changes: 0 additions & 31 deletions ed448-goldilocks/src/edwards/affine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,37 +95,6 @@ impl AffinePoint {
}
}

pub(crate) fn isogeny(&self) -> Self {
let x = self.x;
let y = self.y;
let mut t0 = x.square(); // x^2
let t1 = t0 + FieldElement::ONE; // x^2+1
t0 -= FieldElement::ONE; // x^2-1
let mut t2 = y.square(); // y^2
t2 = t2.double(); // 2y^2
let t3 = x.double(); // 2x

let mut t4 = t0 * y; // y(x^2-1)
t4 = t4.double(); // 2y(x^2-1)
let xNum = t4.double(); // xNum = 4y(x^2-1)

let mut t5 = t0.square(); // x^4-2x^2+1
t4 = t5 + t2; // x^4-2x^2+1+2y^2
let xDen = t4 + t2; // xDen = x^4-2x^2+1+4y^2

t5 *= x; // x^5-2x^3+x
t4 = t2 * t3; // 4xy^2
let yNum = t4 - t5; // yNum = -(x^5-2x^3+x-4xy^2)

t4 = t1 * t2; // 2x^2y^2+2y^2
let yDen = t5 - t4; // yDen = x^5-2x^3+x-2x^2y^2-2y^2

Self {
x: xNum * xDen.invert(),
y: yNum * yDen.invert(),
}
}

/// Standard compression; store Y and sign of X
pub fn compress(&self) -> CompressedEdwardsY {
let affine_x = self.x;
Expand Down
55 changes: 44 additions & 11 deletions ed448-goldilocks/src/edwards/extended.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ impl EdwardsPoint {
T: FieldElement::ZERO,
};

/// Convert this point to [`MontgomeryPoint`]
pub fn to_montgomery(&self) -> MontgomeryPoint {
/// Convert this point to [`MontgomeryXpoint`]
pub fn to_montgomery_x(&self) -> MontgomeryXpoint {
// u = y^2 * [(1-dy^2)/(1-y^2)]

let affine = self.to_affine();
Expand All @@ -322,7 +322,29 @@ impl EdwardsPoint {

let u = yy * (FieldElement::ONE - dyy) * (FieldElement::ONE - yy).invert();

MontgomeryPoint(u.to_bytes())
MontgomeryXpoint(u.to_bytes())
}

/// Convert this point to [`AffineMontgomeryPoint`]
// See https://www.rfc-editor.org/rfc/rfc7748#section-4.2 4-isogeny maps
pub fn to_montgomery(&self) -> AffineMontgomeryPoint {
// u = y^2/x^2
// v = (2 - x^2 - y^2)*y/x^3

let affine = self.to_affine();

// TODO: optimize to a single inversion.
let xx = affine.x.square();
let yy = affine.y.square();

let u = yy * xx.invert();
let v = (FieldElement::TWO - xx - yy) * affine.y * (xx * affine.x).invert();

AffineMontgomeryPoint::conditional_select(
&AffineMontgomeryPoint::new(u, v),
&AffineMontgomeryPoint::IDENTITY,
self.ct_eq(&Self::IDENTITY),
)
}

/// Generic scalar multiplication to compute s*P
Expand Down Expand Up @@ -787,6 +809,7 @@ mod tests {
use proptest::prelude::any;
use proptest::proptest;
use rand_core::{OsRng, TryRngCore};
use sha3::Shake256;

fn hex_to_field(hex: &'static str) -> FieldElement {
assert_eq!(hex.len(), 56 * 2);
Expand Down Expand Up @@ -947,9 +970,7 @@ mod tests {
];

for (msg, x, y) in MSGS {
let p =
hash2curve::hash_from_bytes::<Ed448, ExpandMsgXof<sha3::Shake256>>(&[msg], &[DST])
.unwrap();
let p = Ed448::hash_from_bytes(msg, DST).unwrap();
assert_eq!(p.is_on_curve().unwrap_u8(), 1u8);
let p = p.to_affine();
let mut xx = [0u8; 56];
Expand Down Expand Up @@ -986,11 +1007,7 @@ mod tests {
];

for (msg, x, y) in MSGS {
let p = hash2curve::encode_from_bytes::<Ed448, ExpandMsgXof<sha3::Shake256>>(
&[msg],
&[DST],
)
.unwrap();
let p = Ed448::encode_from_bytes(msg, DST).unwrap();
assert_eq!(p.is_on_curve().unwrap_u8(), 1u8);
let p = p.to_affine();
let mut xx = [0u8; 56];
Expand All @@ -1001,6 +1018,22 @@ mod tests {
yy.reverse();
assert_eq!(p.x.to_bytes(), xx);
assert_eq!(p.y.to_bytes(), yy);

// Test Montgomery to Edwards conversion.
// See https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/664b13592116cecc9e52fb192dcde0ade36f904e/poc/ell2_opt_3mod4.sage#L243-L245.
let conv_p =
ProjectiveMontgomeryXpoint::encode::<ExpandMsgXof<Shake256>>(&[msg], &[DST])
.unwrap()
.to_affine();
let conv_p1 = conv_p.to_edwards(Choice::from(0)).unwrap();
let conv_p2 = conv_p.to_edwards(Choice::from(1)).unwrap();
assert!(conv_p1.x == p.x || conv_p2.x == p.x);
assert!(conv_p1.y == p.y || conv_p2.y == p.y);

let conv_p =
AffinePoint::from(Curve448::encode_from_bytes(msg, DST).unwrap().to_affine());
assert_eq!(conv_p.x, p.x);
assert_eq!(conv_p.y, p.y);
}
}

Expand Down
16 changes: 3 additions & 13 deletions ed448-goldilocks/src/edwards/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::Ed448;
use crate::field::{CurveWithScalar, ORDER, Scalar, ScalarBytes, WideScalarBytes};

use elliptic_curve::array::Array;
use elliptic_curve::bigint::{Limb, NonZero, U448, U704};
use elliptic_curve::consts::{U57, U84, U88};
use elliptic_curve::bigint::{Limb, U448};
use elliptic_curve::consts::{U57, U84};
use elliptic_curve::ops::Reduce;
use elliptic_curve::scalar::FromUintUnchecked;
use subtle::{Choice, CtOption};
Expand Down Expand Up @@ -84,17 +84,7 @@ impl From<&EdwardsScalar> for elliptic_curve::scalar::ScalarBits<Ed448> {

impl Reduce<Array<u8, U84>> for EdwardsScalar {
fn reduce(value: &Array<u8, U84>) -> Self {
const SEMI_WIDE_MODULUS: NonZero<U704> = NonZero::<U704>::new_unwrap(U704::from_be_hex(
"00000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3",
));
let mut tmp = Array::<u8, U88>::default();
tmp[4..].copy_from_slice(&value[..]);

let mut num = U704::from_be_slice(&tmp[..]);
num %= SEMI_WIDE_MODULUS;
let mut words = [0; U448::LIMBS];
words.copy_from_slice(&num.to_words()[..U448::LIMBS]);
Scalar::new(U448::from_words(words))
Self::from_okm_u84(value)
}
}

Expand Down
45 changes: 38 additions & 7 deletions ed448-goldilocks/src/field/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};

use super::{ConstMontyType, MODULUS};
use crate::{
AffinePoint, Decaf448, DecafPoint, Ed448, EdwardsPoint,
curve::twedwards::extended::ExtendedPoint as TwistedExtendedPoint,
AffineMontgomeryPoint, AffinePoint, Curve448, Decaf448, DecafPoint, Ed448, EdwardsPoint,
ProjectiveMontgomeryPoint, curve::twedwards::extended::ExtendedPoint as TwistedExtendedPoint,
};
use elliptic_curve::ops::Reduce;
use elliptic_curve::{
Expand Down Expand Up @@ -196,7 +196,7 @@ impl MapToCurve for Ed448 {
type Length = U84;

fn map_to_curve(element: FieldElement) -> EdwardsPoint {
element.map_to_curve_elligator2().isogeny().to_edwards()
AffinePoint::from(element.map_to_curve_elligator2_curve448()).to_edwards()
}
}

Expand Down Expand Up @@ -272,6 +272,16 @@ impl Field for FieldElement {
}
}

impl MapToCurve for Curve448 {
type SecurityLevel = U28;
type FieldElement = FieldElement;
type Length = U84;

fn map_to_curve(element: FieldElement) -> ProjectiveMontgomeryPoint {
element.map_to_curve_elligator2_curve448().into()
}
}

impl FieldElement {
pub const A_PLUS_TWO_OVER_FOUR: Self = Self(ConstMontyType::new(&U448::from_be_hex(
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000098aa",
Expand Down Expand Up @@ -377,6 +387,10 @@ impl FieldElement {
Self(self.0.double())
}

pub fn triple(&self) -> Self {
self.double() + self
}

/// Computes the inverse square root of a field element
/// Returns the result and a boolean to indicate whether self
/// was a Quadratic residue
Expand Down Expand Up @@ -430,7 +444,7 @@ impl FieldElement {
Self(self.0.div_by_2())
}

pub(crate) fn map_to_curve_elligator2(&self) -> AffinePoint {
pub(crate) fn map_to_curve_elligator2_curve448(&self) -> AffineMontgomeryPoint {
let mut t1 = self.square(); // 1. t1 = u^2
t1 *= Self::Z; // 2. t1 = Z * t1 // Z * u^2
let e1 = t1.ct_eq(&Self::MINUS_ONE); // 3. e1 = t1 == -1 // exceptional case: Z * u^2 == -1
Expand All @@ -450,7 +464,26 @@ impl FieldElement {
let mut y = y2.sqrt(); // 17. y = sqrt(y2)
let e3 = y.is_negative(); // 18. e3 = sgn0(y) == 1
y.conditional_negate(e2 ^ e3); // y = CMOV(-y, y, e2 xor e3)
AffinePoint { x, y }
AffineMontgomeryPoint::new(x, y)
}

// See https://www.rfc-editor.org/rfc/rfc9380.html#name-curve448-q-3-mod-4-k-1.
// Without y-coordinate.
pub(crate) fn map_to_curve_elligator2_curve448_x(&self) -> FieldElement {
let mut t1 = self.square(); // 1. t1 = u^2
t1 *= Self::Z; // 2. t1 = Z * t1 // Z * u^2
let e1 = t1.ct_eq(&Self::MINUS_ONE); // 3. e1 = t1 == -1 // exceptional case: Z * u^2 == -1
t1.conditional_assign(&Self::ZERO, e1); // 4. t1 = CMOV(t1, 0, e1) // if t1 == -1, set t1 = 0
let mut x1 = t1 + Self::ONE; // 5. x1 = t1 + 1
x1 = x1.invert(); // 6. x1 = inv0(x1)
x1 *= -Self::J; // 7. x1 = -A * x1 // x1 = -A / (1 + Z * u^2)
let mut gx1 = x1 + Self::J; // 8. gx1 = x1 + A
gx1 *= x1; // 9. gx1 = gx1 * x1
gx1 += Self::ONE; // 10. gx1 = gx1 + B
gx1 *= x1; // 11. gx1 = gx1 * x1 // gx1 = x1^3 + A * x1^2 + B * x1
let x2 = -x1 - Self::J; // 12. x2 = -x1 - A
let e2 = gx1.is_square(); // 14. e2 = is_square(gx1)
Self::conditional_select(&x2, &x1, e2) // 15. x = CMOV(x2, x1, e2) // If is_square(gx1), x = x1, else x = x2
}

// See https://www.shiftleft.org/papers/decaf/decaf.pdf#section.A.3.
Expand Down Expand Up @@ -527,15 +560,13 @@ mod tests {
.unwrap();
let mut data = Array::<u8, U84>::default();
expander.fill_bytes(&mut data).unwrap();
// TODO: This should be `Curve448FieldElement`.
let u0 = FieldElement::reduce(&data);
let mut e_u0 = *expected_u0;
e_u0.reverse();
let mut e_u1 = *expected_u1;
e_u1.reverse();
assert_eq!(u0.to_bytes(), e_u0);
expander.fill_bytes(&mut data).unwrap();
// TODO: This should be `Curve448FieldElement`.
let u1 = FieldElement::reduce(&data);
assert_eq!(u1.to_bytes(), e_u1);
}
Expand Down
Loading