From 543554813587b64321e576d860bfac11a3d6f9e3 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Wed, 24 Sep 2025 11:20:00 +0200 Subject: [PATCH] Implement `BatchNormalize` for `DecafPoint` --- ed448-goldilocks/src/decaf/points.rs | 107 ++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 2 deletions(-) diff --git a/ed448-goldilocks/src/decaf/points.rs b/ed448-goldilocks/src/decaf/points.rs index a090cb30a..83f125c78 100644 --- a/ed448-goldilocks/src/decaf/points.rs +++ b/ed448-goldilocks/src/decaf/points.rs @@ -1,13 +1,14 @@ +use crate::curve::twedwards::affine::AffinePoint as InnerAffinePoint; use crate::curve::twedwards::extended::ExtendedPoint; use crate::field::FieldElement; use crate::*; use elliptic_curve::{ - CurveGroup, Error, Group, + BatchNormalize, CurveGroup, Error, Group, array::Array, consts::U56, group::{GroupEncoding, cofactor::CofactorGroup, prime::PrimeGroup}, - ops::LinearCombination, + ops::{BatchInvert, LinearCombination}, point::NonIdentity, }; @@ -249,6 +250,14 @@ impl CurveGroup for DecafPoint { fn to_affine(&self) -> Self::AffineRepr { DecafAffinePoint(self.0.to_extensible().to_affine()) } + + #[cfg(feature = "alloc")] + #[inline] + fn batch_normalize(projective: &[Self], affine: &mut [Self::AffineRepr]) { + assert_eq!(projective.len(), affine.len()); + let mut zs = alloc::vec![FieldElement::ONE; projective.len()]; + batch_normalize_generic(projective, zs.as_mut_slice(), affine); + } } impl From for DecafPoint { @@ -595,11 +604,76 @@ impl From> for DecafPoint { } } +impl BatchNormalize<[DecafPoint; N]> for DecafPoint { + type Output = [::AffineRepr; N]; + + #[inline] + fn batch_normalize(points: &[Self; N]) -> [::AffineRepr; N] { + let zs = [FieldElement::ONE; N]; + let mut affine_points = [DecafAffinePoint::IDENTITY; N]; + batch_normalize_generic(points, zs, &mut affine_points); + affine_points + } +} + +#[cfg(feature = "alloc")] +impl BatchNormalize<[DecafPoint]> for DecafPoint { + type Output = Vec<::AffineRepr>; + + #[inline] + fn batch_normalize(points: &[Self]) -> Vec<::AffineRepr> { + use alloc::vec; + + let mut zs = vec![FieldElement::ONE; points.len()]; + let mut affine_points = vec![DecafAffinePoint::IDENTITY; points.len()]; + batch_normalize_generic(points, zs.as_mut_slice(), &mut affine_points); + affine_points + } +} + +/// Generic implementation of batch normalization. +fn batch_normalize_generic(points: &P, mut zs: Z, out: &mut O) +where + FieldElement: BatchInvert>, + P: AsRef<[DecafPoint]> + ?Sized, + Z: AsMut<[FieldElement]>, + I: AsRef<[FieldElement]>, + O: AsMut<[DecafAffinePoint]> + ?Sized, +{ + let points = points.as_ref(); + let out = out.as_mut(); + + for (i, point) in points.iter().enumerate() { + // Even a single zero value will fail inversion for the entire batch. + // Put a dummy value (above `FieldElement::ONE`) so inversion succeeds + // and treat that case specially later-on. + zs.as_mut()[i].conditional_assign(&point.0.Z, !point.0.Z.ct_eq(&FieldElement::ZERO)); + } + + // This is safe to unwrap since we assured that all elements are non-zero + let zs_inverses = >::batch_invert(zs) + .expect("all elements should be non-zero"); + + for i in 0..out.len() { + // If the `z` coordinate is non-zero, we can use it to invert; + // otherwise it defaults to the `IDENTITY` value. + out[i] = DecafAffinePoint::conditional_select( + &DecafAffinePoint(InnerAffinePoint { + x: points[i].0.X * zs_inverses.as_ref()[i], + y: points[i].0.Y * zs_inverses.as_ref()[i], + }), + &DecafAffinePoint::IDENTITY, + points[i].0.Z.ct_eq(&FieldElement::ZERO), + ); + } +} + #[cfg(test)] mod test { use super::*; use crate::TWISTED_EDWARDS_BASE_POINT; use hash2curve::ExpandMsgXof; + use rand_core::OsRng; use sha3::Shake256; #[test] @@ -797,4 +871,33 @@ mod test { // assert_eq!(rhs, expected); // } // } + + #[test] + fn batch_normalize() { + let points: [DecafPoint; 2] = [ + DecafPoint::try_from_rng(&mut OsRng).unwrap(), + DecafPoint::try_from_rng(&mut OsRng).unwrap(), + ]; + + let affine_points = >::batch_normalize(&points); + + for (point, affine_point) in points.into_iter().zip(affine_points) { + assert_eq!(affine_point, point.to_affine()); + } + } + + #[test] + #[cfg(feature = "alloc")] + fn batch_normalize_alloc() { + let points = alloc::vec![ + DecafPoint::try_from_rng(&mut OsRng).unwrap(), + DecafPoint::try_from_rng(&mut OsRng).unwrap(), + ]; + + let affine_points = >::batch_normalize(points.as_slice()); + + for (point, affine_point) in points.into_iter().zip(affine_points) { + assert_eq!(affine_point, point.to_affine()); + } + } }