diff --git a/.github/workflows/x448.yml b/.github/workflows/x448.yml new file mode 100644 index 000000000..be86aeee4 --- /dev/null +++ b/.github/workflows/x448.yml @@ -0,0 +1,111 @@ +name: x448 + +on: + pull_request: + paths: + - ".github/workflows/x448.yml" + - "ed448-goldilocks/**" + - "x448/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: x448 + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + RUSTDOCFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.85.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} --release + + test: + runs-on: ubuntu-latest + strategy: + matrix: + include: + # 32-bit Linux + - target: i686-unknown-linux-gnu + rust: 1.85.0 # MSRV + deps: sudo apt update && sudo apt install gcc-multilib + - target: i686-unknown-linux-gnu + rust: stable + deps: sudo apt update && sudo apt install gcc-multilib + + # 64-bit Linux + - target: x86_64-unknown-linux-gnu + rust: 1.85.0 # MSRV + - target: x86_64-unknown-linux-gnu + rust: stable + + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - uses: RustCrypto/actions/cargo-hack-install@master + - run: ${{ matrix.deps }} + - run: cargo test --release --target ${{ matrix.target }} + + cross: + strategy: + matrix: + include: + # ARM32 + - target: armv7-unknown-linux-gnueabihf + rust: 1.85.0 # MSRV (cross) + - target: armv7-unknown-linux-gnueabihf + rust: stable + + # ARM64 + - target: aarch64-unknown-linux-gnu + rust: 1.85.0 # MSRV (cross) + - target: aarch64-unknown-linux-gnu + rust: stable + + # PPC32 + - target: powerpc-unknown-linux-gnu + rust: 1.85.0 # MSRV (cross) + - target: powerpc-unknown-linux-gnu + rust: stable + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: ${{ matrix.deps }} + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - uses: RustCrypto/actions/cross-install@master + - run: cross test --release --target ${{ matrix.target }} + + doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + - run: cargo doc diff --git a/Cargo.lock b/Cargo.lock index 362701079..c3a90cc00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1577,6 +1577,16 @@ dependencies = [ "tap", ] +[[package]] +name = "x448" +version = "0.7.0-pre" +dependencies = [ + "ed448-goldilocks", + "rand", + "rand_core", + "zeroize", +] + [[package]] name = "zerocopy" version = "0.8.26" @@ -1602,3 +1612,17 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] diff --git a/Cargo.toml b/Cargo.toml index b46ebc10f..b10d8c544 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,13 +14,15 @@ members = [ "p521", "primefield", "primeorder", - "sm2" + "sm2", + "x448" ] [profile.dev] opt-level = 2 [patch.crates-io] +ed448-goldilocks = { path = "ed448-goldilocks" } elliptic-curve = { git = "https://github.com/RustCrypto/traits.git" } hash2curve = { path = "hash2curve" } primefield = { path = "primefield" } 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 9b0da4adb..18204f81c 100644 --- a/ed448-goldilocks/src/edwards/extended.rs +++ b/ed448-goldilocks/src/edwards/extended.rs @@ -527,8 +527,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(); @@ -538,7 +538,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 [`MontgomeryPoint`] + // See https://www.rfc-editor.org/rfc/rfc7748#section-4.2 4-isogeny maps + pub fn to_montgomery(&self) -> MontgomeryPoint { + // 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(); + + MontgomeryPoint::conditional_select( + &MontgomeryPoint::new(u, v), + &MontgomeryPoint::IDENTITY, + self.ct_eq(&Self::IDENTITY), + ) } /// Generic scalar multiplication to compute s*P @@ -973,6 +995,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); @@ -1133,7 +1156,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]; @@ -1170,8 +1193,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]; @@ -1182,6 +1204,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)); + let conv_p2 = conv_p.to_edwards(Choice::from(1)); + 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 2a1d9d59b..641049f31 100644 --- a/ed448-goldilocks/src/field/element.rs +++ b/ed448-goldilocks/src/field/element.rs @@ -3,8 +3,8 @@ use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use super::ConstMontyType; use crate::{ - AffinePoint, Decaf448, DecafPoint, Ed448, EdwardsPoint, - curve::twedwards::extended::ExtendedPoint as TwistedExtendedPoint, + AffinePoint, Curve448, Decaf448, DecafPoint, Ed448, EdwardsPoint, MontgomeryPoint, ORDER, + ProjectiveMontgomeryPoint, curve::twedwards::extended::ExtendedPoint as TwistedExtendedPoint, }; use elliptic_curve::{ array::Array, @@ -16,7 +16,10 @@ use elliptic_curve::{ 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 +67,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 +88,7 @@ impl FromOkm for Ed448FieldElement { } } -impl FromOkm for Decaf448FieldElement { +impl FromOkm for FieldElementU56 { type Length = U56; fn from_okm(data: &Array) -> Self { @@ -190,14 +193,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 +213,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 +228,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", @@ -246,6 +269,7 @@ impl FieldElement { "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000262a8", ))); pub const ONE: Self = Self(ConstMontyType::new(&U448::ONE)); + pub const TWO: Self = Self(ConstMontyType::new(&U448::from_u64(2))); pub const TWISTED_D: Self = Self(ConstMontyType::new(&U448::from_be_hex( "fffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff6755", ))); @@ -315,10 +339,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(&ORDER); + CtOption::new(Self(ConstMontyType::from_montgomery(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 @@ -368,7 +402,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) -> MontgomeryPoint { 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 @@ -388,7 +422,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 } + MontgomeryPoint::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. @@ -465,16 +518,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); } } @@ -499,14 +550,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..9fdad01c7 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::{ + MontgomeryPoint, 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 = MontgomeryPoint; + 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..d8220bb30 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::{MontgomeryPoint, 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..719309905 --- /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::{MontgomeryPoint, 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<&MontgomeryPoint> for &ProjectiveMontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + // See Complete Addition Law for Montgomery Curves - Algorithm 2. + // With "Trade-Off Technique". + fn add(self, rhs: &MontgomeryPoint) -> ProjectiveMontgomeryPoint { + let (x1, y1, z1) = (self.U, self.V, self.W); + let (x2, y2) = (rhs.x, rhs.y); + + 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 = MontgomeryPoint, + Output = ProjectiveMontgomeryPoint +); + +impl Add<&ProjectiveMontgomeryPoint> for &MontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + fn add(self, other: &ProjectiveMontgomeryPoint) -> ProjectiveMontgomeryPoint { + other + self + } +} + +define_add_variants!( + LHS = MontgomeryPoint, + 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<&MontgomeryPoint> for ProjectiveMontgomeryPoint { + fn add_assign(&mut self, rhs: &MontgomeryPoint) { + *self += Self::from(*rhs); + } +} + +define_add_assign_variants!(LHS = ProjectiveMontgomeryPoint, RHS = MontgomeryPoint); + +impl AddAssign<&ProjectiveMontgomeryPoint> for MontgomeryPoint { + fn add_assign(&mut self, rhs: &ProjectiveMontgomeryPoint) { + *self = (ProjectiveMontgomeryPoint::from(*self) + rhs).into(); + } +} + +define_add_assign_variants!(LHS = MontgomeryPoint, 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 &MontgomeryPoint { + 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 MontgomeryPoint { x: xP, y: 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 = MontgomeryPoint, + 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<&MontgomeryPoint> for &ProjectiveMontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + fn sub(self, other: &MontgomeryPoint) -> ProjectiveMontgomeryPoint { + *self - ProjectiveMontgomeryPoint::from(*other) + } +} + +define_sub_variants!( + LHS = ProjectiveMontgomeryPoint, + RHS = MontgomeryPoint, + Output = ProjectiveMontgomeryPoint +); + +impl Sub<&ProjectiveMontgomeryPoint> for &MontgomeryPoint { + type Output = ProjectiveMontgomeryPoint; + + fn sub(self, other: &ProjectiveMontgomeryPoint) -> ProjectiveMontgomeryPoint { + *self - other + } +} + +define_sub_variants!( + LHS = MontgomeryPoint, + 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<&MontgomeryPoint> for ProjectiveMontgomeryPoint { + fn sub_assign(&mut self, rhs: &MontgomeryPoint) { + *self -= ProjectiveMontgomeryPoint::from(*rhs); + } +} + +define_sub_assign_variants!(LHS = ProjectiveMontgomeryPoint, RHS = MontgomeryPoint); + +impl SubAssign<&ProjectiveMontgomeryPoint> for MontgomeryPoint { + fn sub_assign(&mut self, rhs: &ProjectiveMontgomeryPoint) { + *self = (ProjectiveMontgomeryPoint::from(*self) - rhs).into(); + } +} + +define_sub_assign_variants!(LHS = MontgomeryPoint, 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..ee698703a --- /dev/null +++ b/ed448-goldilocks/src/montgomery/point.rs @@ -0,0 +1,574 @@ +use elliptic_curve::{ + CurveGroup, Error, Group, + array::Array, + bigint::U448, + consts::U57, + group::GroupEncoding, + group::cofactor::CofactorGroup, + group::prime::PrimeGroup, + ops::LinearCombination, + point::{AffineCoordinates, 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 MontgomeryPoint { + pub(super) x: FieldElement, + pub(super) y: FieldElement, +} + +impl MontgomeryPoint { + /// The identity element of the group: the point at infinity. + pub const IDENTITY: Self = Self { + x: FieldElement::ZERO, + y: FieldElement::ONE, + }; + + pub(crate) fn new(x: FieldElement, y: FieldElement) -> Self { + Self { x, y } + } +} + +impl ConditionallySelectable for MontgomeryPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self { + x: FieldElement::conditional_select(&a.x, &b.x, choice), + y: FieldElement::conditional_select(&a.y, &b.y, choice), + } + } +} + +impl ConstantTimeEq for MontgomeryPoint { + fn ct_eq(&self, other: &Self) -> Choice { + self.x.ct_eq(&other.x) & self.y.ct_eq(&other.y) + } +} + +impl PartialEq for MontgomeryPoint { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl From<&MontgomeryPoint> for ProjectiveMontgomeryPoint { + fn from(value: &MontgomeryPoint) -> Self { + ProjectiveMontgomeryPoint { + U: value.x, + V: value.y, + W: FieldElement::ONE, + } + } +} + +impl From for ProjectiveMontgomeryPoint { + fn from(value: MontgomeryPoint) -> Self { + (&value).into() + } +} + +impl From<&MontgomeryPoint> for MontgomeryXpoint { + fn from(value: &MontgomeryPoint) -> Self { + MontgomeryXpoint(value.x.to_bytes()) + } +} + +impl From for MontgomeryXpoint { + fn from(value: MontgomeryPoint) -> Self { + (&value).into() + } +} + +impl From<&MontgomeryPoint> for AffinePoint { + // https://www.rfc-editor.org/rfc/rfc7748#section-4.2 + fn from(value: &MontgomeryPoint) -> AffinePoint { + let x = value.x; + let y = value.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 + + let x = xNum * xDen.invert(); + let y = yNum * yDen.invert(); + + AffinePoint::conditional_select( + &AffinePoint { x, y }, + &AffinePoint::IDENTITY, + value.ct_eq(&MontgomeryPoint::IDENTITY), + ) + } +} + +impl From for AffinePoint { + fn from(value: MontgomeryPoint) -> Self { + (&value).into() + } +} + +impl DefaultIsZeroes for MontgomeryPoint {} + +impl AffineCoordinates for MontgomeryPoint { + type FieldRepr = Curve448FieldBytes; + + fn x(&self) -> Self::FieldRepr { + self.x.to_bytes().into() + } + + fn y(&self) -> Self::FieldRepr { + self.y.to_bytes().into() + } + + fn x_is_odd(&self) -> Choice { + self.x.is_negative() + } + + fn y_is_odd(&self) -> Choice { + self.y.is_negative() + } +} + +impl From> for MontgomeryPoint { + 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: MontgomeryPoint) -> 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 { + self.U.ct_eq(&other.U) & self.V.ct_eq(&other.V) & self.W.ct_eq(&other.W) + } +} + +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 MontgomeryPoint { + fn from(value: &ProjectiveMontgomeryPoint) -> Self { + let W_inv = value.W.invert(); + let x = value.U * W_inv; + let y = value.V * W_inv; + + MontgomeryPoint { x, y } + } +} + +impl From for MontgomeryPoint { + 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, + { + let mut uniform_bytes = [0u8; 112]; + rng.try_fill_bytes(&mut uniform_bytes)?; + Ok(Self::hash_with_defaults(&uniform_bytes)) + } + + 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 = MontgomeryPoint; + + fn to_affine(&self) -> Self::AffineRepr { + let W_inv = self.W.invert(); + let x = self.U * W_inv; + let y = self.V * W_inv; + + MontgomeryPoint { x, y } + } +} + +impl GroupEncoding for ProjectiveMontgomeryPoint { + type Repr = Array; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + // Safe to unwrap here as the underlying data structure is an array + let (tag, bytes) = bytes.split_first().expect("slice is non-empty"); + + let mut x_bytes: [u8; 56] = [0; 56]; + x_bytes.copy_from_slice(bytes); + + let (sign, valid) = match *tag { + 0x02 => (Choice::from(0), Choice::from(1)), + 0x03 => (Choice::from(1), Choice::from(1)), + _ => (Choice::from(0), Choice::from(0)), + }; + + FieldElement::from_repr(&x_bytes).and_then(|x| { + CtOption::new( + ProjectiveMontgomeryXpoint { + U: x, + W: FieldElement::ONE, + } + .to_extended(sign), + valid, + ) + }) + } + + 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 compressed_bytes = Array::default(); + + compressed_bytes[0] = if affine.y.is_negative().unwrap_u8() == 1 { + 0x03 + } else { + 0x02 + }; + + compressed_bytes[1..].copy_from_slice(&affine.x.to_bytes()[..]); + compressed_bytes + } +} + +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 hex_literal::hex; + + #[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 = MontgomeryPoint::IDENTITY; + + assert_eq!(AffinePoint::from(montgomery), edwards); + } + + #[test] + fn identity_from_montgomery() { + let edwards = EdwardsPoint::IDENTITY; + let montgomery = MontgomeryPoint::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..b38e12ebb --- /dev/null +++ b/ed448-goldilocks/src/montgomery/x.rs @@ -0,0 +1,461 @@ +// use crate::constants::A_PLUS_TWO_OVER_FOUR; +use super::{ + DEFAULT_ENCODE_TO_CURVE_SUITE, DEFAULT_HASH_TO_CURVE_SUITE, MontgomeryPoint, 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}; + +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) -> [u8; 56] { + self.to_projective().y(sign).to_bytes() + } + + 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) -> ProjectiveMontgomeryPoint { + self.to_projective().to_extended(sign) + } + + /// Convert the point to its form including the y-coordinate + pub fn to_extended(&self, sign: Choice) -> MontgomeryPoint { + self.to_projective().to_extended_affine(sign) + } + + /// Convert this point to an [`AffinePoint`] + pub fn to_edwards(&self, sign: Choice) -> AffinePoint { + self.to_extended(sign).into() + } +} + +impl ConstantTimeEq for ProjectiveMontgomeryXpoint { + fn ct_eq(&self, other: &Self) -> Choice { + self.U.ct_eq(&other.U) & self.W.ct_eq(&other.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, + }; + + /// Compute the Y-coordinate + // See https://www.rfc-editor.org/rfc/rfc7748#section-1. + pub fn y(&self, sign: Choice) -> FieldElement { + // 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); + v + } + + /// 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) -> MontgomeryPoint { + let x = self.U * self.W.invert(); + let y = self.y(sign); + + MontgomeryPoint::new(x, y) + } + + /// Convert the point to its form including the y-coordinate + pub fn to_extended(&self, sign: Choice) -> ProjectiveMontgomeryPoint { + ProjectiveMontgomeryPoint::conditional_select( + &ProjectiveMontgomeryPoint::new(self.U, self.y(sign), 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)), 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)), 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)) == yy || p.y(Choice::from(1)) == 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)) == yy || p.y(Choice::from(1)) == yy); + } + } +} diff --git a/x448/Cargo.toml b/x448/Cargo.toml new file mode 100644 index 000000000..50ea6b92e --- /dev/null +++ b/x448/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "x448" +version = "0.7.0-pre" +authors = ["RustCrypto Developers"] +categories = ["cryptography"] +keywords = ["cryptography", "crypto", "x448", "diffie-hellman", "curve448",] +homepage = "https://docs.rs/x448/" +repository = "https://github.com/RustCrypto/elliptic-curves/tree/master/x448" +documentation = "https://docs.rs/ed448-goldilocks" +license = "Apache-2.0 OR MIT" +edition = "2024" +rust-version = "1.85" +readme = "README.md" +description = "A pure-Rust implementation of X448." + +[dependencies] +ed448-goldilocks = { version = "0.14.0-pre.0", default-features = false } +rand_core = { version = "0.9", default-features = false } + +[dependencies.zeroize] +version = "1" +default-features = false +features = ["zeroize_derive"] + +[dev-dependencies] +rand = "0.9" diff --git a/x448/src/lib.rs b/x448/src/lib.rs new file mode 100644 index 000000000..57b9968ae --- /dev/null +++ b/x448/src/lib.rs @@ -0,0 +1,405 @@ +#![no_std] + +use ed448_goldilocks::{ + MontgomeryScalar, MontgomeryXpoint, + elliptic_curve::{bigint::U448, scalar::FromUintUnchecked}, +}; +use rand_core::{CryptoRng, RngCore}; +use zeroize::Zeroize; + +/// Computes a Scalar according to RFC7748 +/// given a byte array of length 56 +impl From<[u8; 56]> for Secret { + fn from(arr: [u8; 56]) -> Secret { + let mut secret = Secret(arr); + secret.clamp(); + secret + } +} + +/// Given a Secret Key, compute the corresponding public key +/// using the generator specified in RFC7748 +/// XXX: Waiting for upstream PR to use pre-computation +impl From<&Secret> for PublicKey { + fn from(secret: &Secret) -> PublicKey { + let secret = secret.as_scalar(); + let point = &MontgomeryXpoint::GENERATOR * &secret; + PublicKey(point.to_affine()) + } +} + +/// A PublicKey is a point on Curve448. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub struct PublicKey(MontgomeryXpoint); + +/// A Secret is a Scalar on Curve448. +#[derive(Clone, Zeroize)] +#[zeroize(drop)] +pub struct Secret([u8; 56]); + +/// A SharedSecret is a point on Curve448. +/// This point is the result of a Diffie-Hellman key exchange. +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct SharedSecret(MontgomeryXpoint); + +impl PublicKey { + /// Converts a bytes slice into a Public key + /// Returns None if: + /// - The length of the slice is not 56 + /// - The point is a low order point + pub fn from_bytes(bytes: &[u8]) -> Option { + let public_key = PublicKey::from_bytes_unchecked(bytes)?; + if public_key.0.is_low_order() { + return None; + } + Some(public_key) + } + /// Converts a bytes slice into a Public key + /// Returns None if: + /// - The length of the slice is not 56 + pub fn from_bytes_unchecked(bytes: &[u8]) -> Option { + // First check if we have 56 bytes + if bytes.len() != 56 { + return None; + } + + // Check if the point has low order + let arr = slice_to_array(bytes); + let point = MontgomeryXpoint(arr); + + Some(PublicKey(point)) + } + + /// Converts a public key into a byte slice + pub fn as_bytes(&self) -> &[u8; 56] { + self.0.as_bytes() + } +} + +impl SharedSecret { + /// Converts a shared secret into a byte slice + pub fn as_bytes(&self) -> &[u8; 56] { + self.0.as_bytes() + } +} + +impl Secret { + /// Generate a x448 `Secret` key. + // Taken from dalek-x25519 + pub fn new(csprng: &mut T) -> Self + where + T: RngCore + CryptoRng + ?Sized, + { + let mut bytes = [0u8; 56]; + + csprng.fill_bytes(&mut bytes); + + Secret::from(bytes) + } + + /// Clamps the secret key according to RFC7748 + fn clamp(&mut self) { + self.0[0] &= 252; + self.0[55] |= 128; + } + + /// Views a Secret as a Scalar + fn as_scalar(&self) -> MontgomeryScalar { + let secret = U448::from_le_slice(&self.0); + MontgomeryScalar::from_uint_unchecked(secret) + } + + /// Performs a Diffie-hellman key exchange between the secret key and an external public key + pub fn as_diffie_hellman(&self, public_key: &PublicKey) -> Option { + // Check if the point is one of the low order points + if public_key.0.is_low_order() { + return None; + } + let shared_key = &public_key.0 * &self.as_scalar(); + Some(SharedSecret(shared_key.to_affine())) + } + + /// Performs a Diffie-hellman key exchange once between the secret key and an external public key + pub fn to_diffie_hellman(self, public_key: &PublicKey) -> Option { + self.as_diffie_hellman(public_key) + } + + /// Converts a byte slice into a secret and clamp + pub fn from_bytes(bytes: &[u8]) -> Option { + // First check if we have 56 bytes + if bytes.len() != 56 { + return None; + } + + let secret = Secret::from(slice_to_array(bytes)); + Some(secret) + } + + /// Converts a secret into a byte array + pub fn as_bytes(&self) -> &[u8; 56] { + &self.0 + } +} + +fn slice_to_array(bytes: &[u8]) -> [u8; 56] { + let mut array: [u8; 56] = [0; 56]; + array.copy_from_slice(bytes); + array +} + +/// A safe version of the x448 function defined in RFC448. +/// Currently, the only reason I can think of for using the raw function is FFI. +/// Option is FFI safe[1]. So we can still maintain that the invariant that +/// we do not return a low order point. +/// +/// [1]: https://github.com/rust-lang/nomicon/issues/59 +pub fn x448(scalar_bytes: [u8; 56], point_bytes: [u8; 56]) -> Option<[u8; 56]> { + let point = PublicKey::from_bytes(&point_bytes)?; + let scalar = Secret::from(scalar_bytes).as_scalar(); + Some((&point.0 * &scalar).to_affine().0) +} +/// An unchecked version of the x448 function defined in RFC448 +/// No checks are made on the points. +pub fn x448_unchecked(scalar_bytes: [u8; 56], point_bytes: [u8; 56]) -> [u8; 56] { + let point = MontgomeryXpoint(point_bytes); + let scalar = Secret::from(scalar_bytes).as_scalar(); + (&point * &scalar).to_affine().0 +} + +pub const X448_BASEPOINT_BYTES: [u8; 56] = MontgomeryXpoint::GENERATOR.0; + +#[cfg(test)] +mod test { + extern crate alloc; + + use super::*; + use alloc::vec; + + #[test] + fn test_low_order() { + // Notice, that this is the only way to add low order points into the system + // and this is not exposed to the user. The user will use `from_bytes` which will check for low order points. + let bad_key_a = PublicKey(MontgomeryXpoint::LOW_A); + let checked_bad_key_a = PublicKey::from_bytes(&MontgomeryXpoint::LOW_A.0); + assert!(checked_bad_key_a.is_none()); + + let bad_key_b = PublicKey(MontgomeryXpoint::LOW_B); + let checked_bad_key_b = PublicKey::from_bytes(&MontgomeryXpoint::LOW_B.0); + assert!(checked_bad_key_b.is_none()); + + let bad_key_c = PublicKey(MontgomeryXpoint::LOW_C); + let checked_bad_key_c = PublicKey::from_bytes(&MontgomeryXpoint::LOW_C.0); + assert!(checked_bad_key_c.is_none()); + + let mut rng = rand::rng(); + let bob_priv = Secret::new(&mut rng); + + // If for some reason, these low order points are added to the system + // The Diffie-Hellman key exchange for the honest party will return None. + let shared_bob = bob_priv.as_diffie_hellman(&bad_key_a); + assert!(shared_bob.is_none()); + + let shared_bob = bob_priv.as_diffie_hellman(&bad_key_b); + assert!(shared_bob.is_none()); + + let shared_bob = bob_priv.as_diffie_hellman(&bad_key_c); + assert!(shared_bob.is_none()); + } + + #[test] + fn test_random_dh() { + let mut rng = rand::rng(); + let alice_priv = Secret::new(&mut rng); + let alice_pub = PublicKey::from(&alice_priv); + + let bob_priv = Secret::new(&mut rng); + let bob_pub = PublicKey::from(&bob_priv); + + // Since Alice and Bob are both using the API correctly + // If by chance, a low order point is generated, the clamping function will + // remove it. + let low_order = alice_pub.0.is_low_order() || bob_pub.0.is_low_order(); + assert!(low_order == false); + + // Both Alice and Bob perform the DH key exchange. + // As mentioned above, we unwrap because both Parties are using the API correctly. + let shared_alice = alice_priv.as_diffie_hellman(&bob_pub).unwrap(); + let shared_bob = bob_priv.as_diffie_hellman(&alice_pub).unwrap(); + + assert_eq!(shared_alice.as_bytes()[..], shared_bob.as_bytes()[..]); + } + + #[test] + fn test_rfc_test_vectors_alice_bob() { + let alice_priv = Secret::from_bytes(&[ + 0x9a, 0x8f, 0x49, 0x25, 0xd1, 0x51, 0x9f, 0x57, 0x75, 0xcf, 0x46, 0xb0, 0x4b, 0x58, + 0x0, 0xd4, 0xee, 0x9e, 0xe8, 0xba, 0xe8, 0xbc, 0x55, 0x65, 0xd4, 0x98, 0xc2, 0x8d, + 0xd9, 0xc9, 0xba, 0xf5, 0x74, 0xa9, 0x41, 0x97, 0x44, 0x89, 0x73, 0x91, 0x0, 0x63, + 0x82, 0xa6, 0xf1, 0x27, 0xab, 0x1d, 0x9a, 0xc2, 0xd8, 0xc0, 0xa5, 0x98, 0x72, 0x6b, + ]) + .unwrap(); + let got_alice_pub = PublicKey::from(&alice_priv); + + let expected_alice_pub = [ + 0x9b, 0x8, 0xf7, 0xcc, 0x31, 0xb7, 0xe3, 0xe6, 0x7d, 0x22, 0xd5, 0xae, 0xa1, 0x21, 0x7, + 0x4a, 0x27, 0x3b, 0xd2, 0xb8, 0x3d, 0xe0, 0x9c, 0x63, 0xfa, 0xa7, 0x3d, 0x2c, 0x22, + 0xc5, 0xd9, 0xbb, 0xc8, 0x36, 0x64, 0x72, 0x41, 0xd9, 0x53, 0xd4, 0xc, 0x5b, 0x12, + 0xda, 0x88, 0x12, 0xd, 0x53, 0x17, 0x7f, 0x80, 0xe5, 0x32, 0xc4, 0x1f, 0xa0, + ]; + assert_eq!(got_alice_pub.as_bytes()[..], expected_alice_pub[..]); + + let bob_priv = Secret::from_bytes(&[ + 0x1c, 0x30, 0x6a, 0x7a, 0xc2, 0xa0, 0xe2, 0xe0, 0x99, 0xb, 0x29, 0x44, 0x70, 0xcb, + 0xa3, 0x39, 0xe6, 0x45, 0x37, 0x72, 0xb0, 0x75, 0x81, 0x1d, 0x8f, 0xad, 0xd, 0x1d, + 0x69, 0x27, 0xc1, 0x20, 0xbb, 0x5e, 0xe8, 0x97, 0x2b, 0xd, 0x3e, 0x21, 0x37, 0x4c, + 0x9c, 0x92, 0x1b, 0x9, 0xd1, 0xb0, 0x36, 0x6f, 0x10, 0xb6, 0x51, 0x73, 0x99, 0x2d, + ]) + .unwrap(); + let got_bob_pub = PublicKey::from(&bob_priv); + + let expected_bob_pub = [ + 0x3e, 0xb7, 0xa8, 0x29, 0xb0, 0xcd, 0x20, 0xf5, 0xbc, 0xfc, 0xb, 0x59, 0x9b, 0x6f, + 0xec, 0xcf, 0x6d, 0xa4, 0x62, 0x71, 0x7, 0xbd, 0xb0, 0xd4, 0xf3, 0x45, 0xb4, 0x30, + 0x27, 0xd8, 0xb9, 0x72, 0xfc, 0x3e, 0x34, 0xfb, 0x42, 0x32, 0xa1, 0x3c, 0xa7, 0x6, + 0xdc, 0xb5, 0x7a, 0xec, 0x3d, 0xae, 0x7, 0xbd, 0xc1, 0xc6, 0x7b, 0xf3, 0x36, 0x9, + ]; + assert_eq!(got_bob_pub.as_bytes()[..], expected_bob_pub[..]); + + let bob_shared = bob_priv.to_diffie_hellman(&got_alice_pub).unwrap(); + let alice_shared = alice_priv.to_diffie_hellman(&got_bob_pub).unwrap(); + assert_eq!(bob_shared.as_bytes()[..], alice_shared.as_bytes()[..]); + + let expected_shared = [ + 0x7, 0xff, 0xf4, 0x18, 0x1a, 0xc6, 0xcc, 0x95, 0xec, 0x1c, 0x16, 0xa9, 0x4a, 0xf, 0x74, + 0xd1, 0x2d, 0xa2, 0x32, 0xce, 0x40, 0xa7, 0x75, 0x52, 0x28, 0x1d, 0x28, 0x2b, 0xb6, + 0xc, 0xb, 0x56, 0xfd, 0x24, 0x64, 0xc3, 0x35, 0x54, 0x39, 0x36, 0x52, 0x1c, 0x24, 0x40, + 0x30, 0x85, 0xd5, 0x9a, 0x44, 0x9a, 0x50, 0x37, 0x51, 0x4a, 0x87, 0x9d, + ]; + + assert_eq!(bob_shared.as_bytes()[..], expected_shared[..]); + } + + #[test] + fn test_rfc_test_vectors_fixed() { + struct Test { + secret: [u8; 56], + point: [u8; 56], + expected: [u8; 56], + } + + let test_vectors = vec![ + Test { + secret: [ + 0x3d, 0x26, 0x2f, 0xdd, 0xf9, 0xec, 0x8e, 0x88, 0x49, 0x52, 0x66, 0xfe, 0xa1, + 0x9a, 0x34, 0xd2, 0x88, 0x82, 0xac, 0xef, 0x4, 0x51, 0x4, 0xd0, 0xd1, 0xaa, + 0xe1, 0x21, 0x70, 0xa, 0x77, 0x9c, 0x98, 0x4c, 0x24, 0xf8, 0xcd, 0xd7, 0x8f, + 0xbf, 0xf4, 0x49, 0x43, 0xeb, 0xa3, 0x68, 0xf5, 0x4b, 0x29, 0x25, 0x9a, 0x4f, + 0x1c, 0x60, 0xa, 0xd3, + ], + point: [ + 0x6, 0xfc, 0xe6, 0x40, 0xfa, 0x34, 0x87, 0xbf, 0xda, 0x5f, 0x6c, 0xf2, 0xd5, + 0x26, 0x3f, 0x8a, 0xad, 0x88, 0x33, 0x4c, 0xbd, 0x7, 0x43, 0x7f, 0x2, 0xf, 0x8, + 0xf9, 0x81, 0x4d, 0xc0, 0x31, 0xdd, 0xbd, 0xc3, 0x8c, 0x19, 0xc6, 0xda, 0x25, + 0x83, 0xfa, 0x54, 0x29, 0xdb, 0x94, 0xad, 0xa1, 0x8a, 0xa7, 0xa7, 0xfb, 0x4e, + 0xf8, 0xa0, 0x86, + ], + expected: [ + 0xce, 0x3e, 0x4f, 0xf9, 0x5a, 0x60, 0xdc, 0x66, 0x97, 0xda, 0x1d, 0xb1, 0xd8, + 0x5e, 0x6a, 0xfb, 0xdf, 0x79, 0xb5, 0xa, 0x24, 0x12, 0xd7, 0x54, 0x6d, 0x5f, + 0x23, 0x9f, 0xe1, 0x4f, 0xba, 0xad, 0xeb, 0x44, 0x5f, 0xc6, 0x6a, 0x1, 0xb0, + 0x77, 0x9d, 0x98, 0x22, 0x39, 0x61, 0x11, 0x1e, 0x21, 0x76, 0x62, 0x82, 0xf7, + 0x3d, 0xd9, 0x6b, 0x6f, + ], + }, + Test { + secret: [ + 0x20, 0x3d, 0x49, 0x44, 0x28, 0xb8, 0x39, 0x93, 0x52, 0x66, 0x5d, 0xdc, 0xa4, + 0x2f, 0x9d, 0xe8, 0xfe, 0xf6, 0x0, 0x90, 0x8e, 0xd, 0x46, 0x1c, 0xb0, 0x21, + 0xf8, 0xc5, 0x38, 0x34, 0x5d, 0xd7, 0x7c, 0x3e, 0x48, 0x6, 0xe2, 0x5f, 0x46, + 0xd3, 0x31, 0x5c, 0x44, 0xe0, 0xa5, 0xb4, 0x37, 0x12, 0x82, 0xdd, 0x2c, 0x8d, + 0x5b, 0xe3, 0x9, 0x5f, + ], + point: [ + 0xf, 0xbc, 0xc2, 0xf9, 0x93, 0xcd, 0x56, 0xd3, 0x30, 0x5b, 0xb, 0x7d, 0x9e, + 0x55, 0xd4, 0xc1, 0xa8, 0xfb, 0x5d, 0xbb, 0x52, 0xf8, 0xe9, 0xa1, 0xe9, 0xb6, + 0x20, 0x1b, 0x16, 0x5d, 0x1, 0x58, 0x94, 0xe5, 0x6c, 0x4d, 0x35, 0x70, 0xbe, + 0xe5, 0x2f, 0xe2, 0x5, 0xe2, 0x8a, 0x78, 0xb9, 0x1c, 0xdf, 0xbd, 0xe7, 0x1c, + 0xe8, 0xd1, 0x57, 0xdb, + ], + expected: [ + 0x88, 0x4a, 0x2, 0x57, 0x62, 0x39, 0xff, 0x7a, 0x2f, 0x2f, 0x63, 0xb2, 0xdb, + 0x6a, 0x9f, 0xf3, 0x70, 0x47, 0xac, 0x13, 0x56, 0x8e, 0x1e, 0x30, 0xfe, 0x63, + 0xc4, 0xa7, 0xad, 0x1b, 0x3e, 0xe3, 0xa5, 0x70, 0xd, 0xf3, 0x43, 0x21, 0xd6, + 0x20, 0x77, 0xe6, 0x36, 0x33, 0xc5, 0x75, 0xc1, 0xc9, 0x54, 0x51, 0x4e, 0x99, + 0xda, 0x7c, 0x17, 0x9d, + ], + }, + ]; + + for vector in test_vectors { + let public_key = PublicKey::from_bytes(&vector.point).unwrap(); + let secret = Secret::from_bytes(&vector.secret).unwrap(); + + let got = secret.as_diffie_hellman(&public_key).unwrap(); + + assert_eq!(got.as_bytes()[..], vector.expected[..]) + } + } + + // This function is needed for the second set of test vectors in RFC7748 + fn swap(secret: &mut [u8; 56], public_key: &mut [u8; 56], result: &[u8; 56]) { + // set point to be the secret + *public_key = *secret; + // set the secret to be the result + *secret = *result; + } + + #[test] + #[ignore] + fn test_rfc_test_vectors_iteration() { + let one_iter = [ + 0x3f, 0x48, 0x2c, 0x8a, 0x9f, 0x19, 0xb0, 0x1e, 0x6c, 0x46, 0xee, 0x97, 0x11, 0xd9, + 0xdc, 0x14, 0xfd, 0x4b, 0xf6, 0x7a, 0xf3, 0x7, 0x65, 0xc2, 0xae, 0x2b, 0x84, 0x6a, + 0x4d, 0x23, 0xa8, 0xcd, 0xd, 0xb8, 0x97, 0x8, 0x62, 0x39, 0x49, 0x2c, 0xaf, 0x35, 0xb, + 0x51, 0xf8, 0x33, 0x86, 0x8b, 0x9b, 0xc2, 0xb3, 0xbc, 0xa9, 0xcf, 0x41, 0x13, + ]; + let one_k_iter = [ + 0xaa, 0x3b, 0x47, 0x49, 0xd5, 0x5b, 0x9d, 0xaf, 0x1e, 0x5b, 0x0, 0x28, 0x88, 0x26, + 0xc4, 0x67, 0x27, 0x4c, 0xe3, 0xeb, 0xbd, 0xd5, 0xc1, 0x7b, 0x97, 0x5e, 0x9, 0xd4, + 0xaf, 0x6c, 0x67, 0xcf, 0x10, 0xd0, 0x87, 0x20, 0x2d, 0xb8, 0x82, 0x86, 0xe2, 0xb7, + 0x9f, 0xce, 0xea, 0x3e, 0xc3, 0x53, 0xef, 0x54, 0xfa, 0xa2, 0x6e, 0x21, 0x9f, 0x38, + ]; + let one_mil_iter = [ + 0x7, 0x7f, 0x45, 0x36, 0x81, 0xca, 0xca, 0x36, 0x93, 0x19, 0x84, 0x20, 0xbb, 0xe5, + 0x15, 0xca, 0xe0, 0x0, 0x24, 0x72, 0x51, 0x9b, 0x3e, 0x67, 0x66, 0x1a, 0x7e, 0x89, + 0xca, 0xb9, 0x46, 0x95, 0xc8, 0xf4, 0xbc, 0xd6, 0x6e, 0x61, 0xb9, 0xb9, 0xc9, 0x46, + 0xda, 0x8d, 0x52, 0x4d, 0xe3, 0xd6, 0x9b, 0xd9, 0xd9, 0xd6, 0x6b, 0x99, 0x7e, 0x37, + ]; + + let mut point = MontgomeryXpoint::GENERATOR.0; + let mut scalar = MontgomeryXpoint::GENERATOR.0; + let mut result = [0u8; 56]; + + // Iterate 1 time then check value on 1st iteration + for _ in 1..=1 { + result = x448(scalar, point).unwrap(); + swap(&mut scalar, &mut point, &result); + } + assert_eq!(&result[..], &one_iter[..]); + + // Iterate 999 times then check value on 1_000th iteration + for _ in 1..=999 { + result = x448(scalar, point).unwrap(); + swap(&mut scalar, &mut point, &result); + } + assert_eq!(&result[..], &one_k_iter[..]); + + // Iterate 999_000 times then check value on 1_000_000th iteration + for _ in 1..=999_000 { + result = x448(scalar, point).unwrap(); + swap(&mut scalar, &mut point, &result); + } + assert_eq!(&result[..], &one_mil_iter[..]); + } +}