From 8c53a8f10b146a2fd65069437e3576e49b390e7a Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sat, 7 Jun 2025 23:23:59 -0600 Subject: [PATCH] curve: extract `AffinePoint` type (#769) * curve: extract `AffinePoint` type Based on discussions about `elliptic-curve` trait impls in #746, and observing a similar type in `ed448-goldilocks` which inspired this one (not to mention in all of the @RustCrypto elliptic curve crates), adds an `AffinePoint` type with `x` and `y` coordinates. For now, the type is kept out of the public API, and used as an implementation detail for point compression. However, it's been written with the intent of eventually stabilizing and exposing it. It's been marked `pub` so unused functionality doesn't automatically trigger dead code lints. Further work could include refactoring point decompression to first produce an `AffinePoint` and then convert to extended twisted Edwards coordinates (i.e. `EdwardsPoint`), which is more or less what the existing `step_1` and `step_2` functions do (`step_1` technically produces projective coordinates, but `Z` is always set to `ONE`). * Update curve25519-dalek/src/edwards.rs --- curve25519-dalek/src/edwards.rs | 29 ++++--- curve25519-dalek/src/edwards/affine.rs | 114 +++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 curve25519-dalek/src/edwards/affine.rs diff --git a/curve25519-dalek/src/edwards.rs b/curve25519-dalek/src/edwards.rs index 7838a34..7da4678 100644 --- a/curve25519-dalek/src/edwards.rs +++ b/curve25519-dalek/src/edwards.rs @@ -93,6 +93,8 @@ // affine and projective cakes and eat both of them too. #![allow(non_snake_case)] +mod affine; + use cfg_if::cfg_if; use core::array::TryFromSliceError; use core::borrow::Borrow; @@ -146,6 +148,8 @@ use crate::traits::BasepointTable; use crate::traits::ValidityCheck; use crate::traits::{Identity, IsIdentity}; +use affine::AffinePoint; + #[cfg(feature = "alloc")] use crate::traits::MultiscalarMul; #[cfg(feature = "alloc")] @@ -528,7 +532,7 @@ impl EdwardsPoint { } } - /// Dehomogenize to a AffineNielsPoint. + /// Dehomogenize to a `AffineNielsPoint`. /// Mainly for testing. pub(crate) fn as_affine_niels(&self) -> AffineNielsPoint { let recip = self.Z.invert(); @@ -542,6 +546,14 @@ impl EdwardsPoint { } } + /// Dehomogenize to `AffinePoint`. + pub(crate) fn to_affine(self) -> AffinePoint { + let recip = self.Z.invert(); + let x = &self.X * &recip; + let y = &self.Y * &recip; + AffinePoint { x, y } + } + /// Convert this `EdwardsPoint` on the Edwards model to the /// corresponding `MontgomeryPoint` on the Montgomery model. /// @@ -587,10 +599,7 @@ impl EdwardsPoint { /// Compress this point to `CompressedEdwardsY` format. pub fn compress(&self) -> CompressedEdwardsY { - let recip = self.Z.invert(); - let x = &self.X * &recip; - let y = &self.Y * &recip; - Self::compress_affine(x, y) + self.to_affine().compress() } /// Compress several `EdwardsPoint`s into `CompressedEdwardsY` format, using a batch inversion @@ -606,19 +615,11 @@ impl EdwardsPoint { .map(|(input, recip)| { let x = &input.X * recip; let y = &input.Y * recip; - Self::compress_affine(x, y) + AffinePoint { x, y }.compress() }) .collect() } - /// Compress affine Edwards coordinates into `CompressedEdwardsY` format. - #[inline] - fn compress_affine(x: FieldElement, y: FieldElement) -> CompressedEdwardsY { - let mut s = y.to_bytes(); - s[31] ^= x.is_negative().unwrap_u8() << 7; - CompressedEdwardsY(s) - } - #[cfg(feature = "digest")] /// Maps the digest of the input bytes to the curve. This is NOT a hash-to-curve function, as /// it produces points with a non-uniform distribution. Rather, it performs something that diff --git a/curve25519-dalek/src/edwards/affine.rs b/curve25519-dalek/src/edwards/affine.rs new file mode 100644 index 0000000..aa68d8e --- /dev/null +++ b/curve25519-dalek/src/edwards/affine.rs @@ -0,0 +1,114 @@ +use super::{CompressedEdwardsY, EdwardsPoint}; +use crate::traits::Identity; +use crate::{field::FieldElement, Scalar}; +use core::ops::Mul; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; + +#[cfg(feature = "zeroize")] +use zeroize::DefaultIsZeroes; + +/// Affine Edwards point on untwisted curve. +#[derive(Copy, Clone, Debug)] +pub struct AffinePoint { + pub(super) x: FieldElement, + pub(super) y: FieldElement, +} + +impl ConstantTimeEq for AffinePoint { + fn ct_eq(&self, other: &Self) -> Choice { + self.x.ct_eq(&other.x) & self.y.ct_eq(&other.y) + } +} + +impl ConditionallySelectable for AffinePoint { + 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 Default for AffinePoint { + fn default() -> AffinePoint { + AffinePoint::identity() + } +} + +impl Identity for AffinePoint { + fn identity() -> AffinePoint { + AffinePoint { + x: FieldElement::ZERO, + y: FieldElement::ONE, + } + } +} + +impl PartialEq for AffinePoint { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl Eq for AffinePoint {} + +#[cfg(feature = "zeroize")] +impl DefaultIsZeroes for AffinePoint {} + +impl AffinePoint { + /// Convert to extended coordinates. + pub fn to_edwards(self) -> EdwardsPoint { + EdwardsPoint { + X: self.x, + Y: self.y, + Z: FieldElement::ONE, + T: &self.x * &self.y, + } + } + + /// Compress affine Edwards coordinates into `CompressedEdwardsY` format. + #[inline] + pub fn compress(self) -> CompressedEdwardsY { + let mut s = self.y.to_bytes(); + s[31] ^= self.x.is_negative().unwrap_u8() << 7; + CompressedEdwardsY(s) + } +} + +impl Mul for Scalar { + type Output = EdwardsPoint; + + #[inline] + fn mul(self, rhs: AffinePoint) -> EdwardsPoint { + self * &rhs + } +} + +impl Mul<&AffinePoint> for Scalar { + type Output = EdwardsPoint; + + #[inline] + fn mul(self, rhs: &AffinePoint) -> EdwardsPoint { + rhs.to_edwards() * self + } +} + +#[cfg(test)] +mod tests { + use super::{AffinePoint, EdwardsPoint, Identity}; + use crate::constants; + + #[test] + fn identity_conversion() { + assert_eq!( + AffinePoint::identity().to_edwards(), + EdwardsPoint::identity() + ); + } + + #[test] + fn generator_round_trip() { + let basepoint = constants::ED25519_BASEPOINT_POINT; + assert_eq!(basepoint.to_affine().to_edwards(), basepoint); + } +}