diff --git a/ed448-goldilocks/src/edwards/affine.rs b/ed448-goldilocks/src/edwards/affine.rs index acaf5f8b0..3f15e6b9d 100644 --- a/ed448-goldilocks/src/edwards/affine.rs +++ b/ed448-goldilocks/src/edwards/affine.rs @@ -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 { diff --git a/ed448-goldilocks/src/edwards/extended.rs b/ed448-goldilocks/src/edwards/extended.rs index eb7fe03ec..041210972 100644 --- a/ed448-goldilocks/src/edwards/extended.rs +++ b/ed448-goldilocks/src/edwards/extended.rs @@ -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(); @@ -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 @@ -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); @@ -1122,7 +1145,7 @@ mod tests { ]; for (msg, x, y) in MSGS { - let p = Ed448::hash_from_bytes::>(&[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]; @@ -1159,8 +1182,7 @@ mod tests { ]; for (msg, x, y) in MSGS { - let p = - Ed448::encode_from_bytes::>(&[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]; @@ -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::>(&[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::>(&[msg], &[DST]) + .unwrap() + .to_affine(), + ); + assert_eq!(conv_p.x, p.x); + assert_eq!(conv_p.y, p.y); } } diff --git a/ed448-goldilocks/src/edwards/scalar.rs b/ed448-goldilocks/src/edwards/scalar.rs index fb316a2be..320ed9992 100644 --- a/ed448-goldilocks/src/edwards/scalar.rs +++ b/ed448-goldilocks/src/edwards/scalar.rs @@ -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}; @@ -86,17 +86,7 @@ impl FromOkm for EdwardsScalar { type Length = U84; fn from_okm(data: &Array) -> Self { - const SEMI_WIDE_MODULUS: NonZero = NonZero::::new_unwrap(U704::from_be_hex( - "00000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3", - )); - let mut tmp = Array::::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) } } diff --git a/ed448-goldilocks/src/field/element.rs b/ed448-goldilocks/src/field/element.rs index 7e96a5367..0bfea30e1 100644 --- a/ed448-goldilocks/src/field/element.rs +++ b/ed448-goldilocks/src/field/element.rs @@ -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); @@ -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) -> Self { @@ -85,7 +89,7 @@ impl FromOkm for Ed448FieldElement { } } -impl FromOkm for Decaf448FieldElement { +impl FromOkm for FieldElementU56 { type Length = U56; fn from_okm(data: &Array) -> Self { @@ -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 { @@ -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()) } @@ -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", @@ -316,10 +340,20 @@ impl FieldElement { Self(ConstMontyType::new(&U448::from_le_slice(bytes))) } + pub fn from_repr(bytes: &[u8; 56]) -> CtOption { + 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 @@ -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 @@ -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. @@ -466,16 +519,14 @@ mod tests { .unwrap(); let mut data = Array::::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); } } @@ -500,14 +551,14 @@ mod tests { .unwrap(); let mut data = Array::::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); } } diff --git a/ed448-goldilocks/src/field/scalar.rs b/ed448-goldilocks/src/field/scalar.rs index 1407022d8..7c9668b78 100644 --- a/ed448-goldilocks/src/field/scalar.rs +++ b/ed448-goldilocks/src/field/scalar.rs @@ -8,13 +8,13 @@ use core::ops::{ Add, AddAssign, Index, IndexMut, Mul, MulAssign, Neg, Shr, ShrAssign, Sub, SubAssign, }; use elliptic_curve::{ - CurveArithmetic, PrimeField, + PrimeField, array::{ Array, ArraySize, typenum::{Prod, Unsigned}, }, - bigint::{Limb, NonZero, U448, U896, Word, Zero}, - consts::U2, + bigint::{Limb, NonZero, U448, U704, U896, Word, Zero}, + consts::{U2, U84, U88}, ff::{Field, helpers}, ops::{Invert, Reduce, ReduceNonZero}, scalar::{FromUintUnchecked, IsHigh}, @@ -26,7 +26,7 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreate use elliptic_curve::ff::{FieldBits, PrimeFieldBits}; /// Shared scalar for [`Ed448`] and [`Decaf448`]. -/// Use [`EdwardsScalar`] and [`DecafScalar`] directly. +/// Use [`EdwardsScalar`], [`DecafScalar`] and [`MontgomeryScalar`] directly. /// /// This is the scalar field /// size = 4q = 2^446 - 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d @@ -41,7 +41,7 @@ pub type ScalarBytes = Array::ReprSize>; /// The number of bytes needed to represent the safely create a scalar from a random bytes pub type WideScalarBytes = Array::ReprSize, U2>>; -pub trait CurveWithScalar: 'static + CurveArithmetic + Send + Sync { +pub trait CurveWithScalar: 'static + Sized + Send + Sync { type ReprSize: ArraySize: Copy> + Mul: Copy>>; fn from_bytes_mod_order_wide(input: &WideScalarBytes) -> Scalar; @@ -821,4 +821,23 @@ impl Scalar { rng.fill_bytes(&mut scalar_bytes); C::from_bytes_mod_order_wide(&scalar_bytes) } + + /// Convert to other [`Scalar`] type + pub fn to_scalar(&self) -> Scalar { + Scalar::new(self.scalar) + } + + pub(crate) fn from_okm_u84(data: &Array) -> Self { + const SEMI_WIDE_MODULUS: NonZero = NonZero::::new_unwrap(U704::from_be_hex( + "00000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3", + )); + let mut tmp = Array::::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)) + } } diff --git a/ed448-goldilocks/src/lib.rs b/ed448-goldilocks/src/lib.rs index 1c05ccad5..e98781104 100644 --- a/ed448-goldilocks/src/lib.rs +++ b/ed448-goldilocks/src/lib.rs @@ -60,12 +60,15 @@ pub use edwards::{ WideEdwardsScalarBytes, }; pub use field::{MODULUS_LIMBS, ORDER, Scalar, WIDE_ORDER}; -pub use montgomery::{MontgomeryPoint, ProjectiveMontgomeryPoint}; +pub use montgomery::{ + AffineMontgomeryPoint, MontgomeryScalar, MontgomeryScalarBytes, MontgomeryXpoint, + ProjectiveMontgomeryPoint, ProjectiveMontgomeryXpoint, WideMontgomeryScalarBytes, +}; #[cfg(feature = "signing")] pub use sign::*; use elliptic_curve::{ - Curve, FieldBytesEncoding, PrimeCurve, + Curve, CurveArithmetic, FieldBytes, FieldBytesEncoding, NonZeroScalar, PrimeCurve, array::typenum::{U28, U56, U57}, bigint::{ArrayEncoding, U448}, point::PointCompression, @@ -77,14 +80,14 @@ use hash2curve::GroupDigest; pub struct Ed448; /// Bytes of the Ed448 field -pub type Ed448FieldBytes = elliptic_curve::FieldBytes; +pub type Ed448FieldBytes = FieldBytes; /// Scalar bits of the Ed448 scalar #[cfg(feature = "bits")] pub type Ed448ScalarBits = elliptic_curve::scalar::ScalarBits; /// Non-zero scalar of the Ed448 scalar -pub type Ed448NonZeroScalar = elliptic_curve::NonZeroScalar; +pub type Ed448NonZeroScalar = NonZeroScalar; impl Curve for Ed448 { type FieldBytesSize = U57; @@ -111,7 +114,7 @@ impl FieldBytesEncoding for U448 { } } -impl elliptic_curve::CurveArithmetic for Ed448 { +impl CurveArithmetic for Ed448 { type AffinePoint = AffinePoint; type ProjectivePoint = EdwardsPoint; type Scalar = EdwardsScalar; @@ -126,14 +129,14 @@ impl GroupDigest for Ed448 { pub struct Decaf448; /// Bytes of the Decaf448 field -pub type Decaf448FieldBytes = elliptic_curve::FieldBytes; +pub type Decaf448FieldBytes = FieldBytes; /// Scalar bits of the Decaf448 scalar #[cfg(feature = "bits")] pub type Decaf448ScalarBits = elliptic_curve::scalar::ScalarBits; /// Non-zero scalar of the Decaf448 scalar -pub type Decaf448NonZeroScalar = elliptic_curve::NonZeroScalar; +pub type Decaf448NonZeroScalar = NonZeroScalar; impl Curve for Decaf448 { type FieldBytesSize = U56; @@ -160,7 +163,7 @@ impl FieldBytesEncoding for U448 { } } -impl elliptic_curve::CurveArithmetic for Decaf448 { +impl CurveArithmetic for Decaf448 { type AffinePoint = DecafAffinePoint; type ProjectivePoint = DecafPoint; type Scalar = DecafScalar; @@ -169,3 +172,52 @@ impl elliptic_curve::CurveArithmetic for Decaf448 { impl GroupDigest for Decaf448 { type K = U28; } + +/// Curve448 curve. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Curve448; + +/// Bytes of the Curve448 field +pub type Curve448FieldBytes = FieldBytes; + +/// Scalar bits of the Curve448 scalar +#[cfg(feature = "bits")] +pub type Curve448ScalarBits = elliptic_curve::scalar::ScalarBits; + +/// Non-zero scalar of the Curve448 scalar +pub type Curve448NonZeroScalar = NonZeroScalar; + +impl Curve for Curve448 { + type FieldBytesSize = U56; + type Uint = U448; + + const ORDER: U448 = ORDER; +} + +impl PrimeCurve for Curve448 {} + +impl PointCompression for Curve448 { + const COMPRESS_POINTS: bool = true; +} + +impl FieldBytesEncoding for U448 { + fn decode_field_bytes(field_bytes: &Curve448FieldBytes) -> Self { + U448::from_le_slice(field_bytes) + } + + fn encode_field_bytes(&self) -> Curve448FieldBytes { + let mut data = Curve448FieldBytes::default(); + data.copy_from_slice(&self.to_le_byte_array()[..]); + data + } +} + +impl CurveArithmetic for Curve448 { + type AffinePoint = AffineMontgomeryPoint; + type ProjectivePoint = ProjectiveMontgomeryPoint; + type Scalar = MontgomeryScalar; +} + +impl GroupDigest for Curve448 { + type K = U28; +} diff --git a/ed448-goldilocks/src/montgomery.rs b/ed448-goldilocks/src/montgomery.rs index 3fef4d895..f7dcd23aa 100644 --- a/ed448-goldilocks/src/montgomery.rs +++ b/ed448-goldilocks/src/montgomery.rs @@ -10,231 +10,16 @@ #![allow(non_snake_case)] -// use crate::constants::A_PLUS_TWO_OVER_FOUR; -use crate::EdwardsScalar; -use crate::edwards::extended::EdwardsPoint; -use crate::field::FieldElement; -use core::fmt; -use core::ops::Mul; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; - -impl MontgomeryPoint { - /// First low order point on Curve448 and it's twist - pub const LOW_A: MontgomeryPoint = MontgomeryPoint([ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]); - /// Second low order point on Curve448 and it's twist - pub const LOW_B: MontgomeryPoint = MontgomeryPoint([ - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]); - /// Third low order point on Curve448 and it's twist - pub const LOW_C: MontgomeryPoint = MontgomeryPoint([ - 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - ]); -} - -/// A point in Montgomery form -#[derive(Copy, Clone)] -pub struct MontgomeryPoint(pub [u8; 56]); - -impl Default for MontgomeryPoint { - fn default() -> MontgomeryPoint { - Self([0u8; 56]) - } -} - -impl elliptic_curve::zeroize::DefaultIsZeroes for MontgomeryPoint {} - -impl fmt::Debug for MontgomeryPoint { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - self.0[..].fmt(formatter) - } -} - -impl ConstantTimeEq for MontgomeryPoint { - fn ct_eq(&self, other: &MontgomeryPoint) -> Choice { - self.0.ct_eq(&other.0) - } -} - -impl PartialEq for MontgomeryPoint { - fn eq(&self, other: &MontgomeryPoint) -> bool { - self.ct_eq(other).into() - } -} -impl Eq for MontgomeryPoint {} - -/// A Projective point in Montgomery form -#[derive(Copy, Clone, Debug)] -pub struct ProjectiveMontgomeryPoint { - U: FieldElement, - W: FieldElement, -} - -impl Mul<&EdwardsScalar> for &MontgomeryPoint { - type Output = MontgomeryPoint; - - #[allow(clippy::suspicious_arithmetic_impl)] - fn mul(self, scalar: &EdwardsScalar) -> MontgomeryPoint { - // Algorithm 8 of Costello-Smith 2017 - let affine_u = FieldElement::from_bytes(&self.0); - let mut x0 = ProjectiveMontgomeryPoint::identity(); - let mut x1 = ProjectiveMontgomeryPoint { - U: affine_u, - W: FieldElement::ONE, - }; - - let bits = scalar.bits(); - let mut swap = 0; - for s in (0..448).rev() { - let bit = bits[s] as u8; - let choice: u8 = swap ^ bit; - - ProjectiveMontgomeryPoint::conditional_swap(&mut x0, &mut x1, Choice::from(choice)); - differential_add_and_double(&mut x0, &mut x1, &affine_u); - - swap = bit; - } - - x0.to_affine() - } -} - -impl Mul<&MontgomeryPoint> for &EdwardsScalar { - type Output = MontgomeryPoint; - - fn mul(self, point: &MontgomeryPoint) -> MontgomeryPoint { - point * self - } -} - -impl MontgomeryPoint { - /// Returns the generator specified in RFC7748 - pub const GENERATOR: Self = Self([ - 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]); - - /// Convert this point to an [`EdwardsPoint`] - pub fn to_edwards(&self, _sign: u8) -> Option { - // We use the 4-isogeny to map to the Ed448. - // This is different to Curve25519, where we use a birational map. - todo!() - } - - /// Returns true if the point is one of the low order points - pub fn is_low_order(&self) -> bool { - (*self == Self::LOW_A) || (*self == Self::LOW_B) || (*self == Self::LOW_C) - } - - /// View the point as a byte slice - pub fn as_bytes(&self) -> &[u8; 56] { - &self.0 - } - - /// Convert the point to a ProjectiveMontgomeryPoint - pub fn to_projective(&self) -> ProjectiveMontgomeryPoint { - ProjectiveMontgomeryPoint { - U: FieldElement::from_bytes(&self.0), - W: FieldElement::ONE, - } - } -} - -impl ConditionallySelectable for ProjectiveMontgomeryPoint { - fn conditional_select( - a: &ProjectiveMontgomeryPoint, - b: &ProjectiveMontgomeryPoint, - choice: Choice, - ) -> ProjectiveMontgomeryPoint { - ProjectiveMontgomeryPoint { - U: FieldElement::conditional_select(&a.U, &b.U, choice), - W: FieldElement::conditional_select(&a.W, &b.W, choice), - } - } -} - -fn differential_add_and_double( - P: &mut ProjectiveMontgomeryPoint, - Q: &mut ProjectiveMontgomeryPoint, - affine_PmQ: &FieldElement, -) { - let t0 = P.U + P.W; - let t1 = P.U - P.W; - let t2 = Q.U + Q.W; - let t3 = Q.U - Q.W; - - let t4 = t0.square(); // (U_P + W_P)^2 = U_P^2 + 2 U_P W_P + W_P^2 - let t5 = t1.square(); // (U_P - W_P)^2 = U_P^2 - 2 U_P W_P + W_P^2 - - let t6 = t4 - t5; // 4 U_P W_P - - let t7 = t0 * t3; // (U_P + W_P) (U_Q - W_Q) = U_P U_Q + W_P U_Q - U_P W_Q - W_P W_Q - let t8 = t1 * t2; // (U_P - W_P) (U_Q + W_Q) = U_P U_Q - W_P U_Q + U_P W_Q - W_P W_Q - - let t9 = t7 + t8; // 2 (U_P U_Q - W_P W_Q) - let t10 = t7 - t8; // 2 (W_P U_Q - U_P W_Q) - - let t11 = t9.square(); // 4 (U_P U_Q - W_P W_Q)^2 - let t12 = t10.square(); // 4 (W_P U_Q - U_P W_Q)^2 - let t13 = FieldElement::A_PLUS_TWO_OVER_FOUR * t6; // (A + 2) U_P U_Q - - let t14 = t4 * t5; // ((U_P + W_P)(U_P - W_P))^2 = (U_P^2 - W_P^2)^2 - let t15 = t13 + t5; // (U_P - W_P)^2 + (A + 2) U_P W_P - - let t16 = t6 * t15; // 4 (U_P W_P) ((U_P - W_P)^2 + (A + 2) U_P W_P) - let t17 = *affine_PmQ * t12; // U_D * 4 (W_P U_Q - U_P W_Q)^2 - let t18 = t11; // W_D * 4 (U_P U_Q - W_P W_Q)^2 - - P.U = t14; // U_{P'} = (U_P + W_P)^2 (U_P - W_P)^2 - P.W = t16; // W_{P'} = (4 U_P W_P) ((U_P - W_P)^2 + ((A + 2)/4) 4 U_P W_P) - Q.U = t18; // U_{Q'} = W_D * 4 (U_P U_Q - W_P W_Q)^2 - Q.W = t17; // W_{Q'} = U_D * 4 (W_P U_Q - U_P W_Q)^2 -} - -impl ProjectiveMontgomeryPoint { - /// The identity element of the group: the point at infinity. - pub fn identity() -> ProjectiveMontgomeryPoint { - ProjectiveMontgomeryPoint { - U: FieldElement::ONE, - W: FieldElement::ZERO, - } - } - - /// Convert the point to affine form - pub fn to_affine(&self) -> MontgomeryPoint { - let x = self.U * self.W.invert(); - MontgomeryPoint(x.to_bytes()) - } -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn test_montgomery_edwards() { - let scalar = EdwardsScalar::from(200u32); - use crate::GOLDILOCKS_BASE_POINT as bp; - - // Montgomery scalar mul - let montgomery_bp = bp.to_montgomery(); - let montgomery_res = &montgomery_bp * &scalar; - - // Goldilocks scalar mul - let goldilocks_point = bp.scalar_mul(&scalar); - assert_eq!(goldilocks_point.to_montgomery(), montgomery_res); - } -} +mod ops; +mod point; +mod scalar; +mod x; + +pub use point::{AffineMontgomeryPoint, ProjectiveMontgomeryPoint}; +pub use scalar::{MontgomeryScalar, MontgomeryScalarBytes, WideMontgomeryScalarBytes}; +pub use x::{MontgomeryXpoint, ProjectiveMontgomeryXpoint}; + +/// The default hash to curve domain separation tag +const DEFAULT_HASH_TO_CURVE_SUITE: &[u8] = b"curve448_XOF:SHAKE256_ELL2_RO_"; +/// The default encode to curve domain separation tag +const DEFAULT_ENCODE_TO_CURVE_SUITE: &[u8] = b"curve448_XOF:SHAKE256_ELL2_NU_"; diff --git a/ed448-goldilocks/src/montgomery/ops.rs b/ed448-goldilocks/src/montgomery/ops.rs new file mode 100644 index 000000000..70e3f7151 --- /dev/null +++ b/ed448-goldilocks/src/montgomery/ops.rs @@ -0,0 +1,324 @@ +use crate::ProjectiveMontgomeryXpoint; +use crate::field::{ConstMontyType, FieldElement}; +use core::borrow::Borrow; +use core::iter::Sum; +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use elliptic_curve::CurveGroup; +use elliptic_curve::bigint::U448; + +use super::{AffineMontgomeryPoint, MontgomeryScalar, MontgomeryXpoint, ProjectiveMontgomeryPoint}; + +impl Add<&ProjectiveMontgomeryPoint> for &ProjectiveMontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + // See Complete Addition Law for Montgomery Curves - Algorithm 1. + // With "Trade-Off Technique". + fn add(self, rhs: &ProjectiveMontgomeryPoint) -> Self::Output { + let (x1, y1, z1) = (self.U, self.V, self.W); + let (x2, y2, z2) = (rhs.U, rhs.V, rhs.W); + + let t0 = x1 * x2; + let t1 = y1 * y2; + let t2 = z1 * z2; + let t3 = x1 * y2; + let t4 = x2 * y1; + let t5 = y1 * z2; + let t6 = y2 * z1; + let t7 = x1 * z2; + let t8 = x2 * z1; + let t9 = t7 + t8; + let t10 = t9 + FieldElement::J * t0; + let R = t5 + t6; + let T = t10 - t1; + let V = FieldElement::J * t9 + t0.triple() + t2; + let S = (t3 - t4).triple() + t0 - t2; + let U = (t7 - t8).triple() - t3 - t4; + let W = (t5 - t6).triple() + t10 + t1; + let C = (R + T) * (S - U); + let D = (R - T) * (S + U); + let E = (T + V) * (W - S); + let F = (T - V) * (W + S); + let X = C + D; + let Y = E + F; + let Z = (U - W).double() * (R + V) + C - D + E - F; + + ProjectiveMontgomeryPoint { U: X, V: Y, W: Z } + } +} + +define_add_variants!( + LHS = ProjectiveMontgomeryPoint, + RHS = ProjectiveMontgomeryPoint, + Output = ProjectiveMontgomeryPoint +); + +impl Add<&AffineMontgomeryPoint> for &ProjectiveMontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + // See Complete Addition Law for Montgomery Curves - Algorithm 2. + // With "Trade-Off Technique". + fn add(self, rhs: &AffineMontgomeryPoint) -> ProjectiveMontgomeryPoint { + let (x1, y1, z1) = (self.U, self.V, self.W); + let (x2, y2) = (rhs.U, rhs.V); + + let t0 = x1 * x2; + let t1 = y1 * y2; + let t2 = z1; + let t3 = x1 * y2; + let t4 = x2 * y1; + let t5 = y1; + let t6 = y2 * z1; + let t7 = x1; + let t8 = x2 * z1; + let t9 = t7 + t8; + let t10 = t9 + FieldElement::J * t0; + let R = t5 + t6; + let T = t10 - t1; + let V = FieldElement::J * t9 + t0.triple() + t2; + let S = (t3 - t4).triple() + t0 - t2; + let U = (t7 - t8).triple() - t3 - t4; + let W = (t5 - t6).triple() + t10 + t1; + let C = (R + T) * (S - U); + let D = (R - T) * (S + U); + let E = (T + V) * (W - S); + let F = (T - V) * (W + S); + let X = C + D; + let Y = E + F; + let Z = (U - W).double() * (R + V) + C - D + E - F; + + ProjectiveMontgomeryPoint { U: X, V: Y, W: Z } + } +} + +define_add_variants!( + LHS = ProjectiveMontgomeryPoint, + RHS = AffineMontgomeryPoint, + Output = ProjectiveMontgomeryPoint +); + +impl Add<&ProjectiveMontgomeryPoint> for &AffineMontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + fn add(self, other: &ProjectiveMontgomeryPoint) -> ProjectiveMontgomeryPoint { + other + self + } +} + +define_add_variants!( + LHS = AffineMontgomeryPoint, + RHS = ProjectiveMontgomeryPoint, + Output = ProjectiveMontgomeryPoint +); + +impl<'b> AddAssign<&'b ProjectiveMontgomeryPoint> for ProjectiveMontgomeryPoint { + fn add_assign(&mut self, rhs: &'b Self) { + *self = *self + rhs; + } +} + +define_add_assign_variants!( + LHS = ProjectiveMontgomeryPoint, + RHS = ProjectiveMontgomeryPoint +); + +impl AddAssign<&AffineMontgomeryPoint> for ProjectiveMontgomeryPoint { + fn add_assign(&mut self, rhs: &AffineMontgomeryPoint) { + *self += Self::from(*rhs); + } +} + +define_add_assign_variants!(LHS = ProjectiveMontgomeryPoint, RHS = AffineMontgomeryPoint); + +impl AddAssign<&ProjectiveMontgomeryPoint> for AffineMontgomeryPoint { + fn add_assign(&mut self, rhs: &ProjectiveMontgomeryPoint) { + *self = (ProjectiveMontgomeryPoint::from(*self) + rhs).into(); + } +} + +define_add_assign_variants!(LHS = AffineMontgomeryPoint, RHS = ProjectiveMontgomeryPoint); + +impl Mul<&MontgomeryScalar> for &ProjectiveMontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + #[inline] + fn mul(self, scalar: &MontgomeryScalar) -> ProjectiveMontgomeryPoint { + self.to_affine() * scalar + } +} + +define_mul_variants!( + LHS = ProjectiveMontgomeryPoint, + RHS = MontgomeryScalar, + Output = ProjectiveMontgomeryPoint +); + +impl Mul<&MontgomeryScalar> for &AffineMontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + // Montgomery curves and their arithmetic - Algorithm 6 + // https://eprint.iacr.org/2017/212.pdf + fn mul(self, rhs: &MontgomeryScalar) -> ProjectiveMontgomeryPoint { + pub const A2: FieldElement = FieldElement(ConstMontyType::new(&U448::from_u64(312652))); + + let AffineMontgomeryPoint { U: xP, V: yP } = self; + let ( + ProjectiveMontgomeryXpoint { U: xQ, W: zQ }, + ProjectiveMontgomeryXpoint { U: xD, W: zD }, + ) = MontgomeryXpoint::from(self).mul_internal(rhs); + + let v1 = xP * zQ; + let v2 = xQ + v1; + let v3 = xQ - v1; + let v3 = v3.square(); + let v3 = v3 * xD; + let v1 = A2 * zQ; + let v2 = v2 + v1; + let v4 = xP * xQ; + let v4 = v4 + zQ; + let v2 = v2 * v4; + let v1 = v1 * zQ; + let v2 = v2 - v1; + let v2 = v2 * zD; + let y = v2 - v3; + let v1 = FieldElement::TWO * yP; + let v1 = v1 * zQ; + let v1 = v1 * zD; + let x = v1 * xQ; + let z = v1 * zQ; + + ProjectiveMontgomeryPoint { U: x, V: y, W: z } + } +} + +define_mul_variants!( + LHS = AffineMontgomeryPoint, + RHS = MontgomeryScalar, + Output = ProjectiveMontgomeryPoint +); + +impl<'b> MulAssign<&'b MontgomeryScalar> for ProjectiveMontgomeryPoint { + fn mul_assign(&mut self, scalar: &'b MontgomeryScalar) { + let result = *self * scalar; + *self = result; + } +} + +define_mul_assign_variants!(LHS = ProjectiveMontgomeryPoint, RHS = MontgomeryScalar); + +impl Neg for &ProjectiveMontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + fn neg(self) -> ProjectiveMontgomeryPoint { + ProjectiveMontgomeryPoint { + U: self.U, + V: -self.V, + W: self.W, + } + } +} + +impl Neg for ProjectiveMontgomeryPoint { + type Output = Self; + + fn neg(self) -> Self { + -&self + } +} + +impl Sub<&ProjectiveMontgomeryPoint> for &ProjectiveMontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + fn sub(self, other: &ProjectiveMontgomeryPoint) -> ProjectiveMontgomeryPoint { + self.add(&other.neg()) + } +} + +define_sub_variants!( + LHS = ProjectiveMontgomeryPoint, + RHS = ProjectiveMontgomeryPoint, + Output = ProjectiveMontgomeryPoint +); + +impl Sub<&AffineMontgomeryPoint> for &ProjectiveMontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + fn sub(self, other: &AffineMontgomeryPoint) -> ProjectiveMontgomeryPoint { + *self - ProjectiveMontgomeryPoint::from(*other) + } +} + +define_sub_variants!( + LHS = ProjectiveMontgomeryPoint, + RHS = AffineMontgomeryPoint, + Output = ProjectiveMontgomeryPoint +); + +impl Sub<&ProjectiveMontgomeryPoint> for &AffineMontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + fn sub(self, other: &ProjectiveMontgomeryPoint) -> ProjectiveMontgomeryPoint { + *self - other + } +} + +define_sub_variants!( + LHS = AffineMontgomeryPoint, + RHS = ProjectiveMontgomeryPoint, + Output = ProjectiveMontgomeryPoint +); + +impl<'b> SubAssign<&'b Self> for ProjectiveMontgomeryPoint { + fn sub_assign(&mut self, _rhs: &'b Self) { + *self = *self - _rhs; + } +} + +define_sub_assign_variants!( + LHS = ProjectiveMontgomeryPoint, + RHS = ProjectiveMontgomeryPoint +); + +impl SubAssign<&AffineMontgomeryPoint> for ProjectiveMontgomeryPoint { + fn sub_assign(&mut self, rhs: &AffineMontgomeryPoint) { + *self -= ProjectiveMontgomeryPoint::from(*rhs); + } +} + +define_sub_assign_variants!(LHS = ProjectiveMontgomeryPoint, RHS = AffineMontgomeryPoint); + +impl SubAssign<&ProjectiveMontgomeryPoint> for AffineMontgomeryPoint { + fn sub_assign(&mut self, rhs: &ProjectiveMontgomeryPoint) { + *self = (ProjectiveMontgomeryPoint::from(*self) - rhs).into(); + } +} + +define_sub_assign_variants!(LHS = AffineMontgomeryPoint, RHS = ProjectiveMontgomeryPoint); + +impl Sum for ProjectiveMontgomeryPoint +where + T: Borrow, +{ + fn sum(iter: I) -> Self + where + I: Iterator, + { + iter.fold(Self::IDENTITY, |acc, item| acc + item.borrow()) + } +} + +#[cfg(test)] +mod test { + use elliptic_curve::Group; + use rand_core::OsRng; + + use super::*; + + #[test] + fn mixed_addition() { + let p1 = ProjectiveMontgomeryPoint::try_from_rng(&mut OsRng).unwrap(); + let p2 = ProjectiveMontgomeryPoint::try_from_rng(&mut OsRng).unwrap(); + let p3 = p1 + p2; + + assert_eq!(p3.to_affine(), (p1.to_affine() + p2).into()); + } +} diff --git a/ed448-goldilocks/src/montgomery/point.rs b/ed448-goldilocks/src/montgomery/point.rs new file mode 100644 index 000000000..eb31c0315 --- /dev/null +++ b/ed448-goldilocks/src/montgomery/point.rs @@ -0,0 +1,643 @@ +use elliptic_curve::{ + CurveGroup, Error, FieldBytes, Group, + array::Array, + bigint::U448, + consts::U56, + group::GroupEncoding, + group::cofactor::CofactorGroup, + group::prime::PrimeGroup, + ops::LinearCombination, + point::{AffineCoordinates, DecompressPoint, NonIdentity}, + zeroize::DefaultIsZeroes, +}; +use hash2curve::{ExpandMsgXof, GroupDigest}; +use rand_core::TryRngCore; +use sha3::Shake256; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +use super::{ + DEFAULT_ENCODE_TO_CURVE_SUITE, DEFAULT_HASH_TO_CURVE_SUITE, MontgomeryScalar, MontgomeryXpoint, + ProjectiveMontgomeryXpoint, +}; +use crate::field::{ConstMontyType, FieldElement}; +use crate::{AffinePoint, Curve448, Curve448FieldBytes, ORDER}; + +/// A point in Montgomery form including the y-coordinate. +#[derive(Copy, Clone, Debug, Default, Eq)] +pub struct AffineMontgomeryPoint { + pub(super) U: FieldElement, + pub(super) V: FieldElement, +} + +impl AffineMontgomeryPoint { + /// The identity element of the group: the point at infinity. + pub const IDENTITY: Self = Self { + U: FieldElement::ZERO, + V: FieldElement::ONE, + }; + + pub(crate) fn new(U: FieldElement, V: FieldElement) -> Self { + Self { U, V } + } + + /// Generate a random [`AffineMontgomeryPoint`]. + pub fn try_from_rng(rng: &mut R) -> Result + where + R: TryRngCore + ?Sized, + { + let mut bytes = Array::default(); + let mut sign = 0; + + loop { + rng.try_fill_bytes(&mut bytes)?; + rng.try_fill_bytes(core::array::from_mut(&mut sign))?; + if let Some(point) = Self::decompress(&bytes, Choice::from(sign & 1)).into() { + return Ok(point); + } + } + } +} + +impl ConditionallySelectable for AffineMontgomeryPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self { + U: FieldElement::conditional_select(&a.U, &b.U, choice), + V: FieldElement::conditional_select(&a.V, &b.V, choice), + } + } +} + +impl ConstantTimeEq for AffineMontgomeryPoint { + fn ct_eq(&self, other: &Self) -> Choice { + self.U.ct_eq(&other.U) & self.V.ct_eq(&other.V) + } +} + +impl PartialEq for AffineMontgomeryPoint { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl From<&AffineMontgomeryPoint> for ProjectiveMontgomeryPoint { + fn from(value: &AffineMontgomeryPoint) -> Self { + ProjectiveMontgomeryPoint { + U: value.U, + V: value.V, + W: FieldElement::ONE, + } + } +} + +impl From for ProjectiveMontgomeryPoint { + fn from(value: AffineMontgomeryPoint) -> Self { + (&value).into() + } +} + +impl From<&AffineMontgomeryPoint> for MontgomeryXpoint { + fn from(value: &AffineMontgomeryPoint) -> Self { + MontgomeryXpoint(value.U.to_bytes()) + } +} + +impl From for MontgomeryXpoint { + fn from(value: AffineMontgomeryPoint) -> Self { + (&value).into() + } +} + +impl From<&AffineMontgomeryPoint> for AffinePoint { + // https://www.rfc-editor.org/rfc/rfc7748#section-4.2 + fn from(value: &AffineMontgomeryPoint) -> AffinePoint { + let x = value.U; + let y = value.V; + 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 + + let x = xNum * xDen.invert(); + let y = yNum * yDen.invert(); + + AffinePoint::conditional_select( + &AffinePoint { x, y }, + &AffinePoint::IDENTITY, + value.ct_eq(&AffineMontgomeryPoint::IDENTITY), + ) + } +} + +impl From for AffinePoint { + fn from(value: AffineMontgomeryPoint) -> Self { + (&value).into() + } +} + +impl DefaultIsZeroes for AffineMontgomeryPoint {} + +impl AffineCoordinates for AffineMontgomeryPoint { + type FieldRepr = Curve448FieldBytes; + + fn x(&self) -> Self::FieldRepr { + self.U.to_bytes().into() + } + + fn y(&self) -> Self::FieldRepr { + self.V.to_bytes().into() + } + + fn x_is_odd(&self) -> Choice { + self.U.is_negative() + } + + fn y_is_odd(&self) -> Choice { + self.V.is_negative() + } +} + +impl DecompressPoint for AffineMontgomeryPoint { + fn decompress(x: &FieldBytes, y_is_odd: Choice) -> CtOption { + FieldElement::from_repr(&x.0).and_then(|_| MontgomeryXpoint(x.0).to_extended(y_is_odd)) + } +} + +impl From> for AffineMontgomeryPoint { + fn from(affine: NonIdentity) -> Self { + affine.to_point() + } +} + +/// The constant-time alternative is available at [`NonIdentity::new()`]. +impl TryFrom for NonIdentity { + type Error = Error; + + fn try_from(affine_point: AffineMontgomeryPoint) -> Result { + NonIdentity::new(affine_point).into_option().ok_or(Error) + } +} + +/// A Projective point in Montgomery form including the y-coordinate. +#[derive(Copy, Clone, Debug, Eq)] +pub struct ProjectiveMontgomeryPoint { + pub(super) U: FieldElement, + pub(super) V: FieldElement, + pub(super) W: FieldElement, +} + +impl ProjectiveMontgomeryPoint { + /// The identity element of the group: the point at infinity. + pub const IDENTITY: Self = Self { + U: FieldElement::ZERO, + V: FieldElement::ONE, + W: FieldElement::ZERO, + }; + + /// The generator point + pub const GENERATOR: Self = Self { + U: FieldElement(ConstMontyType::new(&U448::from_u64(5))), + V: FieldElement(ConstMontyType::new(&U448::from_be_hex( + "7d235d1295f5b1f66c98ab6e58326fcecbae5d34f55545d060f75dc28df3f6edb8027e2346430d211312c4b150677af76fd7223d457b5b1a", + ))), + W: FieldElement::ONE, + }; + + pub(crate) fn new(U: FieldElement, V: FieldElement, W: FieldElement) -> Self { + Self { U, V, W } + } + + /// Hash a message to a point on the curve + /// + /// Hash using the default domain separation tag and hash function. + /// For more control see [`GroupDigest::hash_from_bytes()`]. + pub fn hash_with_defaults(msg: &[u8]) -> Self { + Curve448::hash_from_bytes::>(&[msg], &[DEFAULT_HASH_TO_CURVE_SUITE]) + .expect("should never fail with the given `ExpandMsg` and `dst`") + } + + /// Encode a message to a point on the curve + /// + /// Encode using the default domain separation tag and hash function. + /// For more control see [`GroupDigest::encode_from_bytes()`]. + pub fn encode_with_defaults(msg: &[u8]) -> Self { + Curve448::encode_from_bytes::>( + &[msg], + &[DEFAULT_ENCODE_TO_CURVE_SUITE], + ) + .expect("should never fail with the given `ExpandMsg` and `dst`") + } +} + +impl ConditionallySelectable for ProjectiveMontgomeryPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self { + U: FieldElement::conditional_select(&a.U, &b.U, choice), + V: FieldElement::conditional_select(&a.V, &b.V, choice), + W: FieldElement::conditional_select(&a.W, &b.W, choice), + } + } +} + +impl ConstantTimeEq for ProjectiveMontgomeryPoint { + fn ct_eq(&self, other: &Self) -> Choice { + let UW = self.U * other.W; + let WU = self.W * other.U; + + let VW = self.V * other.W; + let WV = self.W * other.V; + + (UW.ct_eq(&WU)) & (VW.ct_eq(&WV)) + } +} + +impl Default for ProjectiveMontgomeryPoint { + fn default() -> Self { + Self::IDENTITY + } +} + +impl PartialEq for ProjectiveMontgomeryPoint { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl From<&ProjectiveMontgomeryPoint> for AffineMontgomeryPoint { + fn from(value: &ProjectiveMontgomeryPoint) -> Self { + let W_inv = value.W.invert(); + let U = value.U * W_inv; + let V = value.V * W_inv; + + AffineMontgomeryPoint { U, V } + } +} + +impl From for AffineMontgomeryPoint { + fn from(value: ProjectiveMontgomeryPoint) -> Self { + (&value).into() + } +} + +impl From<&ProjectiveMontgomeryPoint> for ProjectiveMontgomeryXpoint { + fn from(value: &ProjectiveMontgomeryPoint) -> Self { + ProjectiveMontgomeryXpoint::conditional_select( + &ProjectiveMontgomeryXpoint { + U: value.U, + W: value.W, + }, + &ProjectiveMontgomeryXpoint::IDENTITY, + value.ct_eq(&ProjectiveMontgomeryPoint::IDENTITY), + ) + } +} + +impl From for ProjectiveMontgomeryXpoint { + fn from(value: ProjectiveMontgomeryPoint) -> Self { + (&value).into() + } +} + +impl From<&ProjectiveMontgomeryPoint> for MontgomeryXpoint { + fn from(value: &ProjectiveMontgomeryPoint) -> Self { + ProjectiveMontgomeryXpoint::from(value).to_affine() + } +} + +impl From for MontgomeryXpoint { + fn from(value: ProjectiveMontgomeryPoint) -> Self { + (&value).into() + } +} + +impl DefaultIsZeroes for ProjectiveMontgomeryPoint {} + +impl LinearCombination<[(ProjectiveMontgomeryPoint, MontgomeryScalar); N]> + for ProjectiveMontgomeryPoint +{ +} + +impl LinearCombination<[(ProjectiveMontgomeryPoint, MontgomeryScalar)]> + for ProjectiveMontgomeryPoint +{ +} + +impl CofactorGroup for ProjectiveMontgomeryPoint { + type Subgroup = ProjectiveMontgomeryPoint; + + fn clear_cofactor(&self) -> Self::Subgroup { + self.double().double() + } + + fn into_subgroup(self) -> CtOption { + CtOption::new(self.clear_cofactor(), self.is_torsion_free()) + } + + fn is_torsion_free(&self) -> Choice { + (self * MontgomeryScalar::new(ORDER)).ct_eq(&Self::IDENTITY) + } +} + +impl Group for ProjectiveMontgomeryPoint { + type Scalar = MontgomeryScalar; + + fn try_from_rng(rng: &mut R) -> Result + where + R: TryRngCore + ?Sized, + { + loop { + let point = AffineMontgomeryPoint::try_from_rng(rng)?; + if point != AffineMontgomeryPoint::IDENTITY { + break Ok(point.into()); + } + } + } + + fn identity() -> Self { + Self::IDENTITY + } + + fn generator() -> Self { + Self::GENERATOR + } + + fn is_identity(&self) -> Choice { + self.ct_eq(&Self::IDENTITY) + } + + // See Complete Addition Law for Montgomery Curves - Algorithm 3. + // Slightly corrected from the derivation in the same paper. + fn double(&self) -> Self { + const A_MINUS_1: FieldElement = FieldElement(ConstMontyType::new(&U448::from_u64(156325))); + + let (x, y, z) = (self.U, self.V, self.W); + + let t0 = x.square(); + let t1 = y.square(); + let t2 = z.square(); + let t3 = (x + y).square(); + let t4 = (y + z).square(); + let t5 = (x + z).square(); + let t6 = t1 + t2; + let t7 = (t0 - t2).double(); + let t8 = A_MINUS_1 * t0; + let t9 = t0 - t1; + let t10 = FieldElement::J * (t5 - t2) + t0 + t9; + let t11 = t5 + t8; + let t13 = t6.double(); // corrected - replaces t12 + + let S_MINUS_U = t3 - t6; + let S_PLUS_U = -S_MINUS_U + t7; + let R_MINUS_T = t4 - t11; + let R_PLUS_T = t4 - t13 + t11; // corrected + let W_MINUS_S = t11 - t9; + let W_PLUS_S = W_MINUS_S + t7; + let T_MINUS_V = t11 - t10 - t13 + t8; // corrected + let T_PLUS_V = t5 + t10; + let U_MINUS_W = S_PLUS_U - W_PLUS_S; + let R_PLUS_V = R_MINUS_T + T_PLUS_V; + + let C = R_PLUS_T * S_MINUS_U; + let D = R_MINUS_T * S_PLUS_U; + let E = T_PLUS_V * W_MINUS_S; + let F = T_MINUS_V * W_PLUS_S; + let X = C + D; + let Y = E + F; + let Z = U_MINUS_W.double() * R_PLUS_V + C - D + E - F; + + Self { U: X, V: Y, W: Z } + } +} + +impl CurveGroup for ProjectiveMontgomeryPoint { + type AffineRepr = AffineMontgomeryPoint; + + fn to_affine(&self) -> Self::AffineRepr { + let W_inv = self.W.invert(); + let U = self.U * W_inv; + let V = self.V * W_inv; + + AffineMontgomeryPoint { U, V } + } +} + +impl GroupEncoding for ProjectiveMontgomeryPoint { + type Repr = Array; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + let mut bytes = bytes.0; + let sign = bytes[0] & 1; + bytes[0] &= 0xfe; + + FieldElement::from_repr(&bytes).and_then(|U| { + ProjectiveMontgomeryXpoint { + U, + W: FieldElement::ONE, + } + .to_extended(Choice::from(sign)) + }) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + // No unchecked conversion possible for compressed points + Self::from_bytes(bytes) + } + + fn to_bytes(&self) -> Self::Repr { + let affine = self.to_affine(); + let mut bytes = affine.U.to_bytes(); + + if affine.V.is_negative().unwrap_u8() == 1 { + bytes[0] |= 0x01; + } + + bytes.into() + } +} + +impl PrimeGroup for ProjectiveMontgomeryPoint {} + +impl From> for ProjectiveMontgomeryPoint { + fn from(affine: NonIdentity) -> Self { + affine.to_point() + } +} + +/// The constant-time alternative is available at [`NonIdentity::new()`]. +impl TryFrom for NonIdentity { + type Error = Error; + + fn try_from(point: ProjectiveMontgomeryPoint) -> Result { + NonIdentity::new(point).into_option().ok_or(Error) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::EdwardsPoint; + use crate::field::MODULUS; + use elliptic_curve::bigint::modular::ConstMontyParams; + use elliptic_curve::bigint::{ArrayEncoding, CheckedSub, Uint}; + use hex_literal::hex; + + #[test] + fn decode() { + let max_x = MODULUS::PARAMS.modulus().checked_sub(&Uint::ONE).unwrap(); + let y_positive = FieldElement(ConstMontyType::new(&Uint::from_le_hex( + "6c4eae8a1ede852ca59c4154edf766c0a4bdddfae9cd34077529182d01af45c996aa714b714fe34341d5445fe41aed77d0ee49d6f7c5b245", + ))); + let y_negative = -y_positive; + + let point = ProjectiveMontgomeryPoint::from_bytes(&max_x.to_le_byte_array()).unwrap(); + assert_eq!(point.U, FieldElement(ConstMontyType::new(&max_x))); + assert_eq!(point.V, y_positive); + assert_eq!(point.W, FieldElement::ONE); + + let mut bytes = max_x.to_le_byte_array(); + assert_eq!(bytes[0] & 1, 0x00); + bytes[0] |= 0x01; + let point = ProjectiveMontgomeryPoint::from_bytes(&bytes).unwrap(); + assert_eq!(point.U, FieldElement(ConstMontyType::new(&max_x))); + assert_eq!(point.V, y_negative); + assert_eq!(point.W, FieldElement::ONE); + } + + #[test] + fn encode() { + let max_x = MODULUS::PARAMS.modulus().checked_sub(&Uint::ONE).unwrap(); + let y_positive = FieldElement(ConstMontyType::new(&Uint::from_le_hex( + "6c4eae8a1ede852ca59c4154edf766c0a4bdddfae9cd34077529182d01af45c996aa714b714fe34341d5445fe41aed77d0ee49d6f7c5b245", + ))); + let y_negative = -y_positive; + + let point = ProjectiveMontgomeryPoint { + U: FieldElement(ConstMontyType::new(&max_x)), + V: y_positive, + W: FieldElement::ONE, + }; + assert_eq!(point.to_bytes(), max_x.to_le_byte_array()); + + let point = ProjectiveMontgomeryPoint { + U: FieldElement(ConstMontyType::new(&max_x)), + V: y_negative, + W: FieldElement::ONE, + }; + let mut bytes = max_x.to_le_byte_array(); + assert_eq!(bytes[0] & 1, 0x00); + bytes[0] |= 0x01; + assert_eq!(point.to_bytes(), bytes); + } + + #[test] + fn to_edwards() { + let scalar = MontgomeryScalar::from(200u32); + + // Montgomery scalar mul + let montgomery_res = ProjectiveMontgomeryPoint::GENERATOR * scalar * scalar; + // Goldilocks scalar mul + let goldilocks_point = EdwardsPoint::GENERATOR * scalar.to_scalar() * scalar.to_scalar(); + + assert_eq!(goldilocks_point.to_montgomery(), montgomery_res.into()); + } + + #[test] + fn identity_to_edwards() { + let edwards = AffinePoint::IDENTITY; + let montgomery = AffineMontgomeryPoint::IDENTITY; + + assert_eq!(AffinePoint::from(montgomery), edwards); + } + + #[test] + fn identity_from_montgomery() { + let edwards = EdwardsPoint::IDENTITY; + let montgomery = AffineMontgomeryPoint::IDENTITY; + + assert_eq!(edwards.to_montgomery(), montgomery); + } + + #[test] + fn to_projective_x() { + let x_identity = ProjectiveMontgomeryXpoint::IDENTITY; + let identity = ProjectiveMontgomeryPoint::IDENTITY; + + assert_eq!(ProjectiveMontgomeryXpoint::from(identity), x_identity); + } + + #[test] + fn to_affine_x() { + let x_identity = ProjectiveMontgomeryXpoint::IDENTITY.to_affine(); + let identity = MontgomeryXpoint::from(ProjectiveMontgomeryPoint::IDENTITY); + + assert_eq!(identity, x_identity); + } + + #[test] + fn hash_with_test_vectors() { + const DST: &[u8] = b"QUUX-V01-CS02-with-curve448_XOF:SHAKE256_ELL2_RO_"; + const MSGS: &[(&[u8], [u8; 56], [u8; 56])] = &[ + (b"", hex!("5ea5ff623d27c75e73717514134e73e419f831a875ca9e82915fdfc7069d0a9f8b532cfb32b1d8dd04ddeedbe3fa1d0d681c01e825d6a9ea"), hex!("afadd8de789f8f8e3516efbbe313a7eba364c939ecba00dabf4ced5c563b18e70a284c17d8f46b564c4e6ce11784a3825d941116622128c1")), + (b"abc", hex!("9b2f7ce34878d7cebf34c582db14958308ea09366d1ec71f646411d3de0ae564d082b06f40cd30dfc08d9fb7cb21df390cf207806ad9d0e4"), hex!("138a0eef0a4993ea696152ed7db61f7ddb4e8100573591e7466d61c0c568ecaec939e36a84d276f34c402526d8989a96e99760c4869ed633")), + (b"abcdef0123456789", hex!("f54ecd14b85a50eeeee0618452df3a75be7bfba11da5118774ae4ea55ac204e153f77285d780c4acee6c96abe3577a0c0b00be6e790cf194"), hex!("935247a64bf78c107069943c7e3ecc52acb27ce4a3230407c8357341685ea2152e8c3da93f8cd77da1bddb5bb759c6e7ae7d516dced42850")), + (b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", hex!("5bd67c4f88adf6beb10f7e0d0054659776a55c97b809ec8b3101729e104fd0f684e103792f267fd87cc4afc25a073956ef4f268fb02824d5"), hex!("da1f5cb16a352719e4cb064cf47ba72aeba7752d03e8ca2c56229f419b4ef378785a5af1a53dd7ab4d467c1f92f7b139b3752faf29c96432")), + (b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", hex!("ea441c10b3636ecedd5c0dfcae96384cc40de8390a0ab648765b4508da12c586d55dc981275776507ebca0e4d1bcaa302bb69dcfa31b3451"), hex!("fee0192d49bcc0c28d954763c2cbe739b9265c4bebe3883803c64971220cfda60b9ac99ad986cd908c0534b260b5cfca46f6c2b0f3f21bda")), + ]; + + for (msg, x, y) in MSGS { + let p = Curve448::hash_from_bytes::>(&[msg], &[DST]) + .unwrap() + .to_affine(); + let mut xx = [0u8; 56]; + xx.copy_from_slice(&x[..]); + xx.reverse(); + let mut yy = [0u8; 56]; + yy.copy_from_slice(&y[..]); + yy.reverse(); + assert_eq!(p.x(), xx); + assert_eq!(p.y(), yy); + } + } + + #[test] + fn encode_with_test_vectors() { + const DST: &[u8] = b"QUUX-V01-CS02-with-curve448_XOF:SHAKE256_ELL2_NU_"; + const MSGS: &[(&[u8], [u8; 56], [u8; 56])] = &[ + (b"", hex!("b65e8dbb279fd656f926f68d463b13ca7a982b32f5da9c7cc58afcf6199e4729863fb75ca9ae3c95c6887d95a5102637a1c5c40ff0aafadc"), hex!("ea1ea211cf29eca11c057fe8248181591a19f6ac51d45843a65d4bb8b71bc83a64c771ed7686218a278ef1c5d620f3d26b53162188645453")), + (b"abc", hex!("51aceca4fa95854bbaba58d8a5e17a86c07acadef32e1188cafda26232131800002cc2f27c7aec454e5e0c615bddffb7df6a5f7f0f14793f"), hex!("c590c9246eb28b08dee816d608ef233ea5d76e305dc458774a1e1bd880387e6734219e2018e4aa50a49486dce0ba8740065da37e6cf5212c")), + (b"abcdef0123456789", hex!("c6d65987f146b8d0cb5d2c44e1872ac3af1f458f6a8bd8c232ffe8b9d09496229a5a27f350eb7d97305bcc4e0f38328718352e8e3129ed71"), hex!("4d2f901bf333fdc4135b954f20d59207e9f6a4ecf88ce5af11c892b44f79766ec4ecc9f60d669b95ca8940f39b1b7044140ac2040c1bf659")), + (b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", hex!("9b8d008863beb4a02fb9e4efefd2eba867307fb1c7ce01746115d32e1db551bb254e8e3e4532d5c74a83949a69a60519ecc9178083cbe943"), hex!("346a1fca454d1e67c628437c270ec0f0c4256bb774fe6c0e49de7004ff6d9199e2cd99d8f7575a96aafc4dc8db1811ba0a44317581f41371")), + (b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", hex!("8746dc34799112d1f20acda9d7f722c9abb29b1fb6b7e9e566983843c20bd7c9bfad21b45c5166b808d2f5d44e188f1fdaf29cdee8a72e4c"), hex!("7c1293484c9287c298a1a0600c64347eee8530acf563cd8705e05728274d8cd8101835f8003b6f3b78b5beb28f5be188a3d7bce1ec5a36b1")), + ]; + + for (msg, x, y) in MSGS { + let p = Curve448::encode_from_bytes::>(&[msg], &[DST]) + .unwrap() + .to_affine(); + let mut xx = [0u8; 56]; + xx.copy_from_slice(&x[..]); + xx.reverse(); + let mut yy = [0u8; 56]; + yy.copy_from_slice(&y[..]); + yy.reverse(); + assert_eq!(p.x(), xx); + assert_eq!(p.y(), yy); + } + } +} diff --git a/ed448-goldilocks/src/montgomery/scalar.rs b/ed448-goldilocks/src/montgomery/scalar.rs new file mode 100644 index 000000000..ed450b15f --- /dev/null +++ b/ed448-goldilocks/src/montgomery/scalar.rs @@ -0,0 +1,290 @@ +use elliptic_curve::array::Array; +use elliptic_curve::bigint::{Limb, U448}; +use elliptic_curve::consts::{U56, U84}; +use elliptic_curve::scalar::FromUintUnchecked; +use hash2curve::FromOkm; +use subtle::{Choice, CtOption}; + +use crate::field::{CurveWithScalar, NZ_ORDER, ScalarBytes, WideScalarBytes}; +use crate::{Curve448, ORDER, Scalar}; + +impl CurveWithScalar for Curve448 { + type ReprSize = U56; + + fn from_bytes_mod_order_wide(input: &WideScalarBytes) -> Scalar { + let value = ( + U448::from_le_slice(&input[..56]), + U448::from_le_slice(&input[56..112]), + ); + Scalar::new(U448::rem_wide_vartime(value, &NZ_ORDER)) + } + + fn from_canonical_bytes(bytes: &ScalarBytes) -> subtle::CtOption> { + fn is_zero(b: u8) -> Choice { + let res = b as i8; + Choice::from((((res | -res) >> 7) + 1) as u8) + } + + // Check that the 10 high bits are not set + let is_valid = is_zero(bytes[55] >> 6); + let bytes: [u8; 56] = core::array::from_fn(|i| bytes[i]); + let candidate = Scalar::new(U448::from_le_slice(&bytes)); + + // underflow means candidate < ORDER, thus canonical + let (_, underflow) = candidate.scalar.borrowing_sub(&ORDER, Limb::ZERO); + let underflow = Choice::from((underflow.0 >> (Limb::BITS - 1)) as u8); + CtOption::new(candidate, underflow & is_valid) + } + + fn to_repr(scalar: &Scalar) -> ScalarBytes { + scalar.to_bytes().into() + } +} + +/// [`Curve448`] scalar field. +pub type MontgomeryScalar = Scalar; + +impl MontgomeryScalar { + /// Construct a `Scalar` by reducing a 896-bit little-endian integer + /// modulo the group order ℓ. + pub fn from_bytes_mod_order_wide(input: &WideMontgomeryScalarBytes) -> MontgomeryScalar { + Curve448::from_bytes_mod_order_wide(input) + } +} + +elliptic_curve::scalar_impls!(Curve448, MontgomeryScalar); + +/// The number of bytes needed to represent the scalar field +pub type MontgomeryScalarBytes = ScalarBytes; +/// The number of bytes needed to represent the safely create a scalar from a random bytes +pub type WideMontgomeryScalarBytes = WideScalarBytes; + +#[cfg(feature = "bits")] +impl From<&MontgomeryScalar> for elliptic_curve::scalar::ScalarBits { + fn from(scalar: &MontgomeryScalar) -> Self { + scalar.scalar.to_words().into() + } +} + +impl FromOkm for MontgomeryScalar { + type Length = U84; + + fn from_okm(data: &Array) -> Self { + Self::from_okm_u84(data) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::montgomery::DEFAULT_HASH_TO_CURVE_SUITE; + use elliptic_curve::PrimeField; + use hash2curve::ExpandMsgXof; + use hash2curve::GroupDigest; + use hex_literal::hex; + use sha3::Shake256; + + #[test] + fn test_basic_add() { + let five = MontgomeryScalar::from(5u8); + let six = MontgomeryScalar::from(6u8); + + assert_eq!(five + six, MontgomeryScalar::from(11u8)) + } + + #[test] + fn test_basic_sub() { + let ten = MontgomeryScalar::from(10u8); + let five = MontgomeryScalar::from(5u8); + assert_eq!(ten - five, MontgomeryScalar::from(5u8)) + } + + #[test] + fn test_basic_mul() { + let ten = MontgomeryScalar::from(10u8); + let five = MontgomeryScalar::from(5u8); + + assert_eq!(ten * five, MontgomeryScalar::from(50u8)) + } + + #[test] + fn test_mul() { + let a = MontgomeryScalar::new(U448::from_be_hex( + "1e63e8073b089f0747cf8cac2c3dc2732aae8688a8fa552ba8cb0ae8c0be082e74d657641d9ac30a087b8fb97f8ed27dc96a3c35ffb823a3", + )); + + let b = MontgomeryScalar::new(U448::from_be_hex( + "16c5450acae1cb680a92de2d8e59b30824e8d4991adaa0e7bc343bcbd099595b188c6b1a1e30b38b17aa6d9be416b899686eb329d8bedc42", + )); + + let exp = MontgomeryScalar::new(U448::from_be_hex( + "31e055c14ca389edfccd61b3203d424bb9036ff6f2d89c1e07bcd93174e9335f36a1492008a3a0e46abd26f5994c9c2b1f5b3197a18d010a", + )); + + assert_eq!(a * b, exp) + } + #[test] + fn test_basic_square() { + let a = MontgomeryScalar::new(U448::from_be_hex( + "3162081604b3273b930392e5d2391f9d21cc3078f22c69514bb395e08dccc4866f08f3311370f8b83fa50692f640922b7e56a34bcf5fac3d", + )); + let expected_a_squared = MontgomeryScalar::new(U448::from_be_hex( + "1c1e32fc66b21c9c42d6e8e20487193cf6d49916421b290098f30de3713006cfe8ee9d21eeef7427f82a1fe036630c74b9acc2c2ede40f04", + )); + + assert_eq!(a.square(), expected_a_squared) + } + + #[test] + fn test_sanity_check_index_mut() { + let mut x = MontgomeryScalar::ONE; + x[0] = 2; + assert_eq!(x, MontgomeryScalar::from(2u8)) + } + #[test] + fn test_basic_halving() { + let eight = MontgomeryScalar::from(8u8); + let four = MontgomeryScalar::from(4u8); + let two = MontgomeryScalar::from(2u8); + assert_eq!(eight.halve(), four); + assert_eq!(four.halve(), two); + assert_eq!(two.halve(), MontgomeryScalar::ONE); + } + + #[test] + fn test_equals() { + let a = MontgomeryScalar::from(5u8); + let b = MontgomeryScalar::from(5u8); + let c = MontgomeryScalar::from(10u8); + assert_eq!(a, b); + assert_ne!(a, c); + } + + #[test] + fn test_basic_inversion() { + // Test inversion from 2 to 100 + for i in 1..=100u8 { + let x = MontgomeryScalar::from(i); + let x_inv = x.invert(); + assert_eq!(x_inv * x, MontgomeryScalar::ONE) + } + + // Inversion of zero is zero + let zero = MontgomeryScalar::ZERO; + let expected_zero = zero.invert(); + assert_eq!(expected_zero, zero) + } + #[test] + fn test_serialise() { + let scalar = MontgomeryScalar::new(U448::from_be_hex( + "0d79f6e375d3395ed9a6c4c3c49a1433fd7c58aa38363f74e9ab2c22a22347d79988f8e01e8a309f862a9f1052fcd042b9b1ed7115598f62", + )); + let got = MontgomeryScalar::from_canonical_bytes(&scalar.into()).unwrap(); + assert_eq!(scalar, got) + } + #[test] + fn test_from_canonical_bytes() { + // ff..ff should fail + let mut bytes = MontgomeryScalarBytes::from(hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )); + bytes.reverse(); + let s = MontgomeryScalar::from_canonical_bytes(&bytes); + assert!(>::into(s.is_none())); + + // n should fail + let mut bytes = MontgomeryScalarBytes::from(hex!( + "3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3" + )); + bytes.reverse(); + let s = MontgomeryScalar::from_canonical_bytes(&bytes); + assert!(>::into(s.is_none())); + + // n-1 should work + let mut bytes = MontgomeryScalarBytes::from(hex!( + "3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f2" + )); + bytes.reverse(); + let s = MontgomeryScalar::from_canonical_bytes(&bytes); + match Option::::from(s) { + Some(s) => assert_eq!(s, MontgomeryScalar::ZERO - MontgomeryScalar::ONE), + None => panic!("should not return None"), + }; + } + + #[test] + fn test_from_bytes_mod_order_wide() { + // n should become 0 + let mut bytes = WideMontgomeryScalarBytes::from(hex!( + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3" + )); + bytes.reverse(); + let s = MontgomeryScalar::from_bytes_mod_order_wide(&bytes); + assert_eq!(s, MontgomeryScalar::ZERO); + + // n-1 should stay the same + let mut bytes = WideMontgomeryScalarBytes::from(hex!( + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f2" + )); + bytes.reverse(); + let s = MontgomeryScalar::from_bytes_mod_order_wide(&bytes); + assert_eq!(s, MontgomeryScalar::ZERO - MontgomeryScalar::ONE); + + // n+1 should become 1 + let mut bytes = WideMontgomeryScalarBytes::from(hex!( + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f4" + )); + bytes.reverse(); + let s = MontgomeryScalar::from_bytes_mod_order_wide(&bytes); + assert_eq!(s, MontgomeryScalar::ONE); + + // 2^896-1 should become 0x3402a939f823b7292052bcb7e4d070af1a9cc14ba3c47c44ae17cf725ee4d8380d66de2388ea18597af32c4bc1b195d9e3539257049b9b5f + let bytes = WideMontgomeryScalarBytes::from(hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )); + let s = MontgomeryScalar::from_bytes_mod_order_wide(&bytes); + let mut bytes = MontgomeryScalarBytes::from(hex!( + "3402a939f823b7292052bcb7e4d070af1a9cc14ba3c47c44ae17cf725ee4d8380d66de2388ea18597af32c4bc1b195d9e3539257049b9b5f" + )); + bytes.reverse(); + let reduced = MontgomeryScalar::from_canonical_bytes(&bytes).unwrap(); + assert_eq!(s, reduced); + } + + #[cfg(all(feature = "alloc", feature = "serde"))] + #[test] + fn serde() { + use elliptic_curve::PrimeField; + + let res = serde_json::to_string(&MontgomeryScalar::TWO_INV); + assert!(res.is_ok()); + let sj = res.unwrap(); + + let res = serde_json::from_str::(&sj); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), MontgomeryScalar::TWO_INV); + + let res = serde_bare::to_vec(&MontgomeryScalar::TWO_INV); + assert!(res.is_ok()); + let sb = res.unwrap(); + assert_eq!(sb.len(), 57); + + let res = serde_bare::from_slice::(&sb); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), MontgomeryScalar::TWO_INV); + } + + #[test] + fn scalar_hash() { + let msg = b"hello world"; + let res = Curve448::hash_to_scalar::>( + &[msg], + &[DEFAULT_HASH_TO_CURVE_SUITE], + ) + .unwrap(); + let expected: [u8; 56] = hex_literal::hex!( + "287e2dd03a61fe8c38304326442016e9dab1b12c9fd7fe2e4cff4170fc7893f06746c27c35fe6fe43d350aab1d63baef8e3c99a25ab43e1e" + ); + assert_eq!(res.to_repr(), Array::from(expected)); + } +} diff --git a/ed448-goldilocks/src/montgomery/x.rs b/ed448-goldilocks/src/montgomery/x.rs new file mode 100644 index 000000000..9578c1941 --- /dev/null +++ b/ed448-goldilocks/src/montgomery/x.rs @@ -0,0 +1,484 @@ +// use crate::constants::A_PLUS_TWO_OVER_FOUR; +use super::{ + AffineMontgomeryPoint, DEFAULT_ENCODE_TO_CURVE_SUITE, DEFAULT_HASH_TO_CURVE_SUITE, + MontgomeryScalar, ProjectiveMontgomeryPoint, +}; +use crate::field::{ConstMontyType, FieldElement, FieldElementU84}; +use crate::{AffinePoint, Curve448}; +use core::fmt; +use core::ops::Mul; +use elliptic_curve::array::Array; +use elliptic_curve::bigint::U448; +use elliptic_curve::consts::{U28, U84}; +use hash2curve::{ExpandMsg, ExpandMsgXof, Expander, FromOkm, MapToCurve}; +use sha3::Shake256; +use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, CtOption}; + +impl MontgomeryXpoint { + /// First low order point on Curve448 and it's twist + pub const LOW_A: MontgomeryXpoint = MontgomeryXpoint([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + /// Second low order point on Curve448 and it's twist + pub const LOW_B: MontgomeryXpoint = MontgomeryXpoint([ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + /// Third low order point on Curve448 and it's twist + pub const LOW_C: MontgomeryXpoint = MontgomeryXpoint([ + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ]); +} + +/// A point in Montgomery form +#[derive(Copy, Clone)] +pub struct MontgomeryXpoint(pub [u8; 56]); + +impl Default for MontgomeryXpoint { + fn default() -> MontgomeryXpoint { + Self([0u8; 56]) + } +} + +impl elliptic_curve::zeroize::DefaultIsZeroes for MontgomeryXpoint {} + +impl fmt::Debug for MontgomeryXpoint { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + self.0[..].fmt(formatter) + } +} + +impl ConstantTimeEq for MontgomeryXpoint { + fn ct_eq(&self, other: &MontgomeryXpoint) -> Choice { + self.0.ct_eq(&other.0) + } +} + +impl PartialEq for MontgomeryXpoint { + fn eq(&self, other: &MontgomeryXpoint) -> bool { + self.ct_eq(other).into() + } +} +impl Eq for MontgomeryXpoint {} + +/// A Projective point in Montgomery form +#[derive(Copy, Clone, Debug, Eq)] +pub struct ProjectiveMontgomeryXpoint { + pub(super) U: FieldElement, + pub(super) W: FieldElement, +} + +impl Mul<&MontgomeryScalar> for &MontgomeryXpoint { + type Output = ProjectiveMontgomeryXpoint; + + fn mul(self, scalar: &MontgomeryScalar) -> ProjectiveMontgomeryXpoint { + self.mul_internal(scalar).0 + } +} + +impl Mul<&MontgomeryXpoint> for &MontgomeryScalar { + type Output = ProjectiveMontgomeryXpoint; + + fn mul(self, point: &MontgomeryXpoint) -> ProjectiveMontgomeryXpoint { + point * self + } +} + +impl MontgomeryXpoint { + /// Returns the generator specified in RFC7748 + pub const GENERATOR: Self = Self([ + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + + /// Returns true if the point is one of the low order points + pub fn is_low_order(&self) -> bool { + (*self == Self::LOW_A) || (*self == Self::LOW_B) || (*self == Self::LOW_C) + } + + /// View the point as a byte slice + pub fn as_bytes(&self) -> &[u8; 56] { + &self.0 + } + + /// Compute the Y-coordinate + pub fn y(&self, sign: Choice) -> CtOption<[u8; 56]> { + Self::y_internal(&FieldElement::from_bytes(&self.0), sign).map(FieldElement::to_bytes) + } + + // See https://www.rfc-editor.org/rfc/rfc7748#section-1. + pub(super) fn y_internal(u: &FieldElement, sign: Choice) -> CtOption { + // v^2 = u^3 + A*u^2 + u + let uu = u.square(); + let vv = uu * u + FieldElement::J * uu + u; + + let mut v = vv.sqrt(); + v.conditional_negate(v.is_negative() ^ sign); + CtOption::new(v, v.square().ct_eq(&vv)) + } + + pub(super) fn mul_internal( + &self, + scalar: &MontgomeryScalar, + ) -> (ProjectiveMontgomeryXpoint, ProjectiveMontgomeryXpoint) { + // Algorithm 8 of Costello-Smith 2017 + let mut x0 = ProjectiveMontgomeryXpoint::IDENTITY; + let mut x1 = self.to_projective(); + let diff = x1.U; + + let bits = scalar.bits(); + let mut swap = 0; + for s in (0..448).rev() { + let bit = bits[s] as u8; + let choice: u8 = swap ^ bit; + + ProjectiveMontgomeryXpoint::conditional_swap(&mut x0, &mut x1, Choice::from(choice)); + differential_add_and_double(&mut x0, &mut x1, &diff); + + swap = bit; + } + + (x0, x1) + } + + /// Convert the point to a ProjectiveMontgomeryPoint + pub fn to_projective(&self) -> ProjectiveMontgomeryXpoint { + ProjectiveMontgomeryXpoint { + U: FieldElement::from_bytes(&self.0), + W: FieldElement::ONE, + } + } + + /// Convert the point to projective form including the y-coordinate + pub fn to_extended_projective(&self, sign: Choice) -> CtOption { + self.to_projective().to_extended(sign) + } + + /// Convert the point to its form including the y-coordinate + pub fn to_extended(&self, sign: Choice) -> CtOption { + let x = FieldElement::from_bytes(&self.0); + let y = Self::y_internal(&x, sign); + + y.map(|y| AffineMontgomeryPoint::new(x, y)) + } + + /// Convert this point to an [`AffinePoint`] + pub fn to_edwards(&self, sign: Choice) -> CtOption { + self.to_extended(sign).map(AffinePoint::from) + } +} + +impl ConstantTimeEq for ProjectiveMontgomeryXpoint { + fn ct_eq(&self, other: &Self) -> Choice { + (self.U * other.W).ct_eq(&(other.U * self.W)) + } +} + +impl ConditionallySelectable for ProjectiveMontgomeryXpoint { + fn conditional_select( + a: &ProjectiveMontgomeryXpoint, + b: &ProjectiveMontgomeryXpoint, + choice: Choice, + ) -> ProjectiveMontgomeryXpoint { + ProjectiveMontgomeryXpoint { + U: FieldElement::conditional_select(&a.U, &b.U, choice), + W: FieldElement::conditional_select(&a.W, &b.W, choice), + } + } +} + +impl PartialEq for ProjectiveMontgomeryXpoint { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl Mul<&MontgomeryScalar> for &ProjectiveMontgomeryXpoint { + type Output = ProjectiveMontgomeryXpoint; + + fn mul(self, scalar: &MontgomeryScalar) -> ProjectiveMontgomeryXpoint { + &self.to_affine() * scalar + } +} + +impl Mul<&ProjectiveMontgomeryXpoint> for &MontgomeryScalar { + type Output = ProjectiveMontgomeryXpoint; + + fn mul(self, point: &ProjectiveMontgomeryXpoint) -> ProjectiveMontgomeryXpoint { + point * self + } +} + +// (1987 Montgomery) Speeding the Pollard and elliptic curve methods of factorization +// fifth and sixth displays, plus common-subexpression elimination, plus assumption Z1=1 +fn differential_add_and_double( + P: &mut ProjectiveMontgomeryXpoint, + Q: &mut ProjectiveMontgomeryXpoint, + affine_PmQ: &FieldElement, +) { + let t0 = P.U + P.W; + let t1 = P.U - P.W; + let t2 = Q.U + Q.W; + let t3 = Q.U - Q.W; + + let t4 = t0.square(); // (U_P + W_P)^2 = U_P^2 + 2 U_P W_P + W_P^2 + let t5 = t1.square(); // (U_P - W_P)^2 = U_P^2 - 2 U_P W_P + W_P^2 + + let t6 = t4 - t5; // 4 U_P W_P + + let t7 = t0 * t3; // (U_P + W_P) (U_Q - W_Q) = U_P U_Q + W_P U_Q - U_P W_Q - W_P W_Q + let t8 = t1 * t2; // (U_P - W_P) (U_Q + W_Q) = U_P U_Q - W_P U_Q + U_P W_Q - W_P W_Q + + let t9 = t7 + t8; // 2 (U_P U_Q - W_P W_Q) + let t10 = t7 - t8; // 2 (W_P U_Q - U_P W_Q) + + let t11 = t9.square(); // 4 (U_P U_Q - W_P W_Q)^2 + let t12 = t10.square(); // 4 (W_P U_Q - U_P W_Q)^2 + let t13 = FieldElement::A_PLUS_TWO_OVER_FOUR * t6; // (A + 2) U_P U_Q + + let t14 = t4 * t5; // ((U_P + W_P)(U_P - W_P))^2 = (U_P^2 - W_P^2)^2 + let t15 = t13 + t5; // (U_P - W_P)^2 + (A + 2) U_P W_P + + let t16 = t6 * t15; // 4 (U_P W_P) ((U_P - W_P)^2 + (A + 2) U_P W_P) + let t17 = *affine_PmQ * t12; // U_D * 4 (W_P U_Q - U_P W_Q)^2 + let t18 = t11; // W_D * 4 (U_P U_Q - W_P W_Q)^2 + + P.U = t14; // U_{P'} = (U_P + W_P)^2 (U_P - W_P)^2 + P.W = t16; // W_{P'} = (4 U_P W_P) ((U_P - W_P)^2 + ((A + 2)/4) 4 U_P W_P) + Q.U = t18; // U_{Q'} = W_D * 4 (U_P U_Q - W_P W_Q)^2 + Q.W = t17; // W_{Q'} = U_D * 4 (W_P U_Q - U_P W_Q)^2 +} + +impl ProjectiveMontgomeryXpoint { + /// The identity element of the group: the point at infinity. + pub const IDENTITY: Self = Self { + U: FieldElement::ONE, + W: FieldElement::ZERO, + }; + + /// The generator point + pub const GENERATOR: Self = Self { + U: FieldElement(ConstMontyType::new(&U448::from_u64(5))), + W: FieldElement::ONE, + }; + + // See https://www.rfc-editor.org/rfc/rfc7748#section-1. + fn y(&self, sign: Choice) -> CtOption { + // v^2 = u^3 + A*u^2 + u + let u_sq = self.U.square(); + let v_sq = u_sq * self.U + FieldElement::J * u_sq + self.U; + + let mut v = v_sq.sqrt(); + v.conditional_negate(v.is_negative() ^ sign); + CtOption::new(v, v.square().ct_eq(&v_sq)) + } + + /// Double this point + // https://eprint.iacr.org/2020/1338.pdf (2.2) + pub fn double(&self) -> Self { + let v1 = (self.U + self.W).square(); + let v2 = (self.U - self.W).square(); + let U = v1 * v2; + let v3 = v1 - v2; + let v4 = FieldElement::A_PLUS_TWO_OVER_FOUR * v3; + let v5 = v2 + v4; + let W = v3 * v5; + + Self { U, W } + } + + /// Hash a message to a point on the curve + /// + /// Hash using the default domain separation tag and hash function. + /// For more control see [`Self::hash()`]. + pub fn hash_with_defaults(msg: &[u8]) -> Self { + Self::hash::>(&[msg], &[DEFAULT_HASH_TO_CURVE_SUITE]) + } + + /// Hash a message to a point on the curve + /// + /// Implements hash to curve according + /// see + pub fn hash(msg: &[&[u8]], dst: &[&[u8]]) -> Self + where + X: ExpandMsg, + { + let mut expander = + X::expand_message(msg, dst, (84 * 2).try_into().expect("should never fail")) + .expect("should never fail with the given `ExpandMsg` and `dst`"); + let mut data = Array::::default(); + expander.fill_bytes(&mut data); + let u0 = FieldElementU84::from_okm(&data).0; + expander.fill_bytes(&mut data); + let u1 = FieldElementU84::from_okm(&data).0; + + let q0 = Curve448::map_to_curve(FieldElementU84(u0)); + let q1 = Curve448::map_to_curve(FieldElementU84(u1)); + + Self::from(q0 + q1).double().double() + } + + /// Encode a message to a point on the curve + /// + /// Encode using the default domain separation tag and hash function. + /// For more control see [`Self::encode()`]. + pub fn encode_with_defaults(msg: &[u8]) -> Self { + Self::encode::>(&[msg], &[DEFAULT_ENCODE_TO_CURVE_SUITE]) + } + + /// Encode a message to a point on the curve + /// + /// Implements encode to curve according + /// see + pub fn encode(msg: &[&[u8]], dst: &[&[u8]]) -> Self + where + X: ExpandMsg, + { + let mut expander = X::expand_message(msg, dst, 84.try_into().expect("should never fail")) + .expect("should never fail with the given `ExpandMsg` and `dst`"); + let mut data = Array::::default(); + expander.fill_bytes(&mut data); + let u = FieldElementU84::from_okm(&data).0; + + Self { + U: u.map_to_curve_elligator2_curve448_x(), + W: FieldElement::ONE, + } + .double() + .double() + } + + /// Convert the point to affine form + pub fn to_affine(&self) -> MontgomeryXpoint { + let x = self.U * self.W.invert(); + MontgomeryXpoint(x.to_bytes()) + } + + /// Convert the point to affine form including the y-coordinate + pub fn to_extended_affine(&self, sign: Choice) -> CtOption { + let x = self.U * self.W.invert(); + let y = self.y(sign); + + y.map(|y| AffineMontgomeryPoint::new(x, y)) + } + + /// Convert the point to its form including the y-coordinate + pub fn to_extended(&self, sign: Choice) -> CtOption { + CtOption::new( + ProjectiveMontgomeryPoint::IDENTITY, + self.ct_eq(&Self::IDENTITY), + ) + .or_else(|| { + let y = self.y(sign); + + y.map(|y| { + ProjectiveMontgomeryPoint::conditional_select( + &ProjectiveMontgomeryPoint::new(self.U, y, self.W), + &ProjectiveMontgomeryPoint::IDENTITY, + self.ct_eq(&Self::IDENTITY), + ) + }) + }) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::EdwardsPoint; + use elliptic_curve::CurveGroup; + use hex_literal::hex; + use sha3::Shake256; + + #[test] + fn test_montgomery_edwards() { + let scalar = MontgomeryScalar::from(200u32); + + // Montgomery scalar mul + let montgomery_res = &(&ProjectiveMontgomeryXpoint::GENERATOR * &scalar) * &scalar; + + // Goldilocks scalar mul + let goldilocks_point = EdwardsPoint::GENERATOR * scalar.to_scalar() * scalar.to_scalar(); + assert_eq!( + goldilocks_point.to_montgomery_x(), + montgomery_res.to_affine() + ); + } + + #[test] + fn to_extended() { + let x_identity = ProjectiveMontgomeryXpoint::IDENTITY; + let identity = ProjectiveMontgomeryPoint::IDENTITY; + + assert_eq!(x_identity.to_extended(Choice::from(1)).unwrap(), identity); + } + + #[test] + fn to_extended_affine() { + let x_identity = ProjectiveMontgomeryXpoint::IDENTITY.to_affine(); + let identity = ProjectiveMontgomeryPoint::IDENTITY.to_affine(); + + assert_eq!(x_identity.to_extended(Choice::from(1)).unwrap(), identity); + } + + #[test] + fn hash_with_test_vectors() { + const DST: &[u8] = b"QUUX-V01-CS02-with-curve448_XOF:SHAKE256_ELL2_RO_"; + const MSGS: &[(&[u8], [u8; 56], [u8; 56])] = &[ + (b"", hex!("5ea5ff623d27c75e73717514134e73e419f831a875ca9e82915fdfc7069d0a9f8b532cfb32b1d8dd04ddeedbe3fa1d0d681c01e825d6a9ea"), hex!("afadd8de789f8f8e3516efbbe313a7eba364c939ecba00dabf4ced5c563b18e70a284c17d8f46b564c4e6ce11784a3825d941116622128c1")), + (b"abc", hex!("9b2f7ce34878d7cebf34c582db14958308ea09366d1ec71f646411d3de0ae564d082b06f40cd30dfc08d9fb7cb21df390cf207806ad9d0e4"), hex!("138a0eef0a4993ea696152ed7db61f7ddb4e8100573591e7466d61c0c568ecaec939e36a84d276f34c402526d8989a96e99760c4869ed633")), + (b"abcdef0123456789", hex!("f54ecd14b85a50eeeee0618452df3a75be7bfba11da5118774ae4ea55ac204e153f77285d780c4acee6c96abe3577a0c0b00be6e790cf194"), hex!("935247a64bf78c107069943c7e3ecc52acb27ce4a3230407c8357341685ea2152e8c3da93f8cd77da1bddb5bb759c6e7ae7d516dced42850")), + (b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", hex!("5bd67c4f88adf6beb10f7e0d0054659776a55c97b809ec8b3101729e104fd0f684e103792f267fd87cc4afc25a073956ef4f268fb02824d5"), hex!("da1f5cb16a352719e4cb064cf47ba72aeba7752d03e8ca2c56229f419b4ef378785a5af1a53dd7ab4d467c1f92f7b139b3752faf29c96432")), + (b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", hex!("ea441c10b3636ecedd5c0dfcae96384cc40de8390a0ab648765b4508da12c586d55dc981275776507ebca0e4d1bcaa302bb69dcfa31b3451"), hex!("fee0192d49bcc0c28d954763c2cbe739b9265c4bebe3883803c64971220cfda60b9ac99ad986cd908c0534b260b5cfca46f6c2b0f3f21bda")), + ]; + + for (msg, x, y) in MSGS { + let p = ProjectiveMontgomeryXpoint::hash::>(&[msg], &[DST]) + .to_affine(); + let mut xx = [0u8; 56]; + xx.copy_from_slice(&x[..]); + xx.reverse(); + let mut yy = [0u8; 56]; + yy.copy_from_slice(&y[..]); + yy.reverse(); + assert_eq!(p.0, xx); + assert!(p.y(Choice::from(0)).unwrap() == yy || p.y(Choice::from(1)).unwrap() == yy); + } + } + + #[test] + fn encode_with_test_vectors() { + const DST: &[u8] = b"QUUX-V01-CS02-with-curve448_XOF:SHAKE256_ELL2_NU_"; + const MSGS: &[(&[u8], [u8; 56], [u8; 56])] = &[ + (b"", hex!("b65e8dbb279fd656f926f68d463b13ca7a982b32f5da9c7cc58afcf6199e4729863fb75ca9ae3c95c6887d95a5102637a1c5c40ff0aafadc"), hex!("ea1ea211cf29eca11c057fe8248181591a19f6ac51d45843a65d4bb8b71bc83a64c771ed7686218a278ef1c5d620f3d26b53162188645453")), + (b"abc", hex!("51aceca4fa95854bbaba58d8a5e17a86c07acadef32e1188cafda26232131800002cc2f27c7aec454e5e0c615bddffb7df6a5f7f0f14793f"), hex!("c590c9246eb28b08dee816d608ef233ea5d76e305dc458774a1e1bd880387e6734219e2018e4aa50a49486dce0ba8740065da37e6cf5212c")), + (b"abcdef0123456789", hex!("c6d65987f146b8d0cb5d2c44e1872ac3af1f458f6a8bd8c232ffe8b9d09496229a5a27f350eb7d97305bcc4e0f38328718352e8e3129ed71"), hex!("4d2f901bf333fdc4135b954f20d59207e9f6a4ecf88ce5af11c892b44f79766ec4ecc9f60d669b95ca8940f39b1b7044140ac2040c1bf659")), + (b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", hex!("9b8d008863beb4a02fb9e4efefd2eba867307fb1c7ce01746115d32e1db551bb254e8e3e4532d5c74a83949a69a60519ecc9178083cbe943"), hex!("346a1fca454d1e67c628437c270ec0f0c4256bb774fe6c0e49de7004ff6d9199e2cd99d8f7575a96aafc4dc8db1811ba0a44317581f41371")), + (b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", hex!("8746dc34799112d1f20acda9d7f722c9abb29b1fb6b7e9e566983843c20bd7c9bfad21b45c5166b808d2f5d44e188f1fdaf29cdee8a72e4c"), hex!("7c1293484c9287c298a1a0600c64347eee8530acf563cd8705e05728274d8cd8101835f8003b6f3b78b5beb28f5be188a3d7bce1ec5a36b1")), + ]; + + for (msg, x, y) in MSGS { + let p = ProjectiveMontgomeryXpoint::encode::>(&[msg], &[DST]) + .to_affine(); + let mut xx = [0u8; 56]; + xx.copy_from_slice(&x[..]); + xx.reverse(); + let mut yy = [0u8; 56]; + yy.copy_from_slice(&y[..]); + yy.reverse(); + assert_eq!(p.0, xx); + assert!(p.y(Choice::from(0)).unwrap() == yy || p.y(Choice::from(1)).unwrap() == yy); + } + } +}