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
This commit is contained in:
Tony Arcieri 2025-06-07 23:23:59 -06:00 committed by GitHub
parent cf7b099585
commit 8c53a8f10b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 129 additions and 14 deletions

View file

@ -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

View file

@ -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<AffinePoint> 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);
}
}