Skip to content

Curve448 with full coordinates #1306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c41120c
Rename to `MontgomeryPoint` to `MontgomeryXpoint`
daxpedda Jul 18, 2025
878cb85
Move `MontgomeryXpoint` to its own module
daxpedda Jul 18, 2025
d17b45b
Implement `Eq` for `ProjectiveMontgomeryXpoint`
daxpedda Jul 18, 2025
1c67312
Change `MontgomeryPoint` scalar multiplication output to `ProjectiveM…
daxpedda Jul 18, 2025
c9553b2
Add y-coordinate recovery
daxpedda Jul 18, 2025
9475f55
Make `ProjectiveMontgomeryXpoint::identity()` an associated const
daxpedda Jul 18, 2025
c6b22d3
Add `ProjectiveMontgomeryXpoint::GENERATOR`
daxpedda Jul 18, 2025
c57407d
Document source of `montgomery::differential_add_and_double`
daxpedda Jul 18, 2025
52fa4e4
Expose Montgomery ladder with additional output internally
daxpedda Jul 18, 2025
eddcf4a
Add `ProjectiveMontgomeryXpoint::double()`
daxpedda Jul 18, 2025
033ab5c
Add full-coordinate `MontgomeryPoint` skeleton
daxpedda Jul 18, 2025
5d5bd3d
Drop `CurveArithmetic` requirement from `CurveWithScalar`
daxpedda Jul 18, 2025
51f03d7
Add `MontgomeryScalar`
daxpedda Jul 18, 2025
d171bd7
Add arithmetic operations to Montgomery
daxpedda Jul 18, 2025
27f97a3
Add Montgomery <-> Edwards conversions
daxpedda Jul 18, 2025
fce6482
Implement `CurveArithmetic` for Curve448
daxpedda Jul 18, 2025
c3d3212
Implement `FromOkm` for `MontgomeryScalar`
daxpedda Jul 19, 2025
4306725
Implement hash2curve for `Curve448`
daxpedda Jul 19, 2025
705d717
Add hash2curve to `ProjectiveMontgomeryXpoint`
daxpedda Jul 19, 2025
bc51594
Test Montgomery -> Edwards through hash2curve
daxpedda Jul 19, 2025
15beab8
Rename `MontgomeryPoint` fields from `x|y` to `U|V`
daxpedda Jul 26, 2025
247f5e9
Rename `MontgomeryPoint` to `AffineMontgomeryPoint`
daxpedda Jul 26, 2025
874805c
Not every full-coordinate Montgomery point is valid
daxpedda Aug 3, 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
31 changes: 0 additions & 31 deletions ed448-goldilocks/src/edwards/affine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,37 +69,6 @@ impl AffinePoint {
y: FieldElement::ONE,
};

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(),
}
}

/// Convert to edwards extended point
pub fn to_edwards(&self) -> EdwardsPoint {
EdwardsPoint {
Expand Down
52 changes: 46 additions & 6 deletions ed448-goldilocks/src/edwards/extended.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,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 @@ -547,7 +547,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 @@ -962,6 +984,7 @@ mod tests {
use elliptic_curve::Field;
use hex_literal::hex;
use rand_core::TryRngCore;
use sha3::Shake256;

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

for (msg, x, y) in MSGS {
let p = Ed448::hash_from_bytes::<ExpandMsgXof<sha3::Shake256>>(&[msg], &[DST]).unwrap();
let p = Ed448::hash_from_bytes::<ExpandMsgXof<Shake256>>(&[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 @@ -1159,8 +1182,7 @@ mod tests {
];

for (msg, x, y) in MSGS {
let p =
Ed448::encode_from_bytes::<ExpandMsgXof<sha3::Shake256>>(&[msg], &[DST]).unwrap();
let p = Ed448::encode_from_bytes::<ExpandMsgXof<Shake256>>(&[msg], &[DST]).unwrap();
assert_eq!(p.is_on_curve().unwrap_u8(), 1u8);
let p = p.to_affine();
let mut xx = [0u8; 56];
Expand All @@ -1171,6 +1193,24 @@ 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])
.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::<ExpandMsgXof<Shake256>>(&[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::field::{CurveWithScalar, NZ_ORDER, Scalar, ScalarBytes, WideScalarByt
use crate::{Ed448, ORDER};

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::scalar::FromUintUnchecked;
use hash2curve::FromOkm;
use subtle::{Choice, CtOption};
Expand Down Expand Up @@ -86,17 +86,7 @@ impl FromOkm for EdwardsScalar {
type Length = U84;

fn from_okm(data: &Array<u8, Self::Length>) -> 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(&data[..]);

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(data)
}
}

Expand Down
93 changes: 72 additions & 21 deletions ed448-goldilocks/src/field/element.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
use core::fmt::{self, Debug, Display, Formatter, LowerHex, UpperHex};
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};

use super::ConstMontyType;
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::{
array::Array,
bigint::{
Integer, NonZero, U448, U704,
consts::{U56, U84, U88},
modular::ConstMontyParams,
},
group::cofactor::CofactorGroup,
zeroize::DefaultIsZeroes,
};
use hash2curve::{FromOkm, MapToCurve};
use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq};
use subtle::{
Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, ConstantTimeLess,
CtOption,
};

#[derive(Clone, Copy, Default)]
pub struct FieldElement(pub(crate) ConstMontyType);
Expand Down Expand Up @@ -64,7 +68,7 @@ impl PartialEq for FieldElement {
}
impl Eq for FieldElement {}

impl FromOkm for Ed448FieldElement {
impl FromOkm for FieldElementU84 {
type Length = U84;

fn from_okm(data: &Array<u8, Self::Length>) -> Self {
Expand All @@ -85,7 +89,7 @@ impl FromOkm for Ed448FieldElement {
}
}

impl FromOkm for Decaf448FieldElement {
impl FromOkm for FieldElementU56 {
type Length = U56;

fn from_okm(data: &Array<u8, Self::Length>) -> Self {
Expand Down Expand Up @@ -190,14 +194,14 @@ impl Neg for FieldElement {
}

#[derive(Clone, Copy, Default, Debug)]
pub struct Ed448FieldElement(FieldElement);
pub struct FieldElementU84(pub(crate) FieldElement);

impl MapToCurve for Ed448 {
type CurvePoint = EdwardsPoint;
type FieldElement = Ed448FieldElement;
type FieldElement = FieldElementU84;

fn map_to_curve(element: Ed448FieldElement) -> Self::CurvePoint {
element.0.map_to_curve_elligator2().isogeny().to_edwards()
fn map_to_curve(element: FieldElementU84) -> Self::CurvePoint {
AffinePoint::from(element.0.map_to_curve_elligator2_curve448()).to_edwards()
}

fn map_to_subgroup(point: EdwardsPoint) -> EdwardsPoint {
Expand All @@ -210,13 +214,13 @@ impl MapToCurve for Ed448 {
}

#[derive(Clone, Copy, Default, Debug)]
pub struct Decaf448FieldElement(FieldElement);
pub struct FieldElementU56(pub(crate) FieldElement);

impl MapToCurve for Decaf448 {
type CurvePoint = DecafPoint;
type FieldElement = Decaf448FieldElement;
type FieldElement = FieldElementU56;

fn map_to_curve(element: Decaf448FieldElement) -> DecafPoint {
fn map_to_curve(element: FieldElementU56) -> DecafPoint {
DecafPoint(element.0.map_to_curve_decaf448())
}

Expand All @@ -225,6 +229,26 @@ impl MapToCurve for Decaf448 {
}
}

impl MapToCurve for Curve448 {
type CurvePoint = ProjectiveMontgomeryPoint;
type FieldElement = FieldElementU84;

fn map_to_curve(element: FieldElementU84) -> Self::CurvePoint {
element.0.map_to_curve_elligator2_curve448().into()
}

fn map_to_subgroup(point: ProjectiveMontgomeryPoint) -> ProjectiveMontgomeryPoint {
point.clear_cofactor()
}

fn add_and_map_to_subgroup(
lhs: ProjectiveMontgomeryPoint,
rhs: ProjectiveMontgomeryPoint,
) -> ProjectiveMontgomeryPoint {
(lhs + rhs).clear_cofactor()
}
}

impl FieldElement {
pub const A_PLUS_TWO_OVER_FOUR: Self = Self(ConstMontyType::new(&U448::from_be_hex(
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000098aa",
Expand Down Expand Up @@ -316,10 +340,20 @@ impl FieldElement {
Self(ConstMontyType::new(&U448::from_le_slice(bytes)))
}

pub fn from_repr(bytes: &[u8; 56]) -> CtOption<Self> {
let integer = U448::from_le_slice(bytes);
let is_some = integer.ct_lt(MODULUS::PARAMS.modulus());
CtOption::new(Self(ConstMontyType::new(&integer)), is_some)
}

pub fn double(&self) -> Self {
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 @@ -369,7 +403,7 @@ impl FieldElement {
(inv_sqrt_x * u, zero_u | is_res)
}

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 @@ -389,7 +423,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 @@ -466,16 +519,14 @@ mod tests {
.unwrap();
let mut data = Array::<u8, U84>::default();
expander.fill_bytes(&mut data);
// TODO: This should be `Curve448FieldElement`.
let u0 = Ed448FieldElement::from_okm(&data).0;
let u0 = FieldElementU84::from_okm(&data).0;
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);
// TODO: This should be `Curve448FieldElement`.
let u1 = Ed448FieldElement::from_okm(&data).0;
let u1 = FieldElementU84::from_okm(&data).0;
assert_eq!(u1.to_bytes(), e_u1);
}
}
Expand All @@ -500,14 +551,14 @@ mod tests {
.unwrap();
let mut data = Array::<u8, U84>::default();
expander.fill_bytes(&mut data);
let u0 = Ed448FieldElement::from_okm(&data).0;
let u0 = FieldElementU84::from_okm(&data).0;
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);
let u1 = Ed448FieldElement::from_okm(&data).0;
let u1 = FieldElementU84::from_okm(&data).0;
assert_eq!(u1.to_bytes(), e_u1);
}
}
Expand Down
Loading