Move embedwards25519 over to short-weierstrass

This commit is contained in:
Luke Parker
2025-08-28 21:56:28 -04:00
parent f2d399ba1e
commit da190759a9
17 changed files with 252 additions and 536 deletions

View File

@@ -21,7 +21,10 @@ rand_core = { version = "0.6", default-features = false }
ff = { version = "0.13", default-features = false, features = ["bits"] }
group = { version = "0.13", default-features = false }
generic-array = { version = "1", default-features = false, optional = true }
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
[features]
alloc = ["zeroize/alloc", "rand_core/alloc", "ff/alloc", "group/alloc"]
std = ["zeroize/std", "subtle/std", "rand_core/std", "ff/std"]
alloc = ["zeroize/alloc", "rand_core/alloc", "ff/alloc", "group/alloc", "generic-array", "ec-divisors"]
std = ["alloc", "zeroize/std", "subtle/std", "rand_core/std", "ff/std"]
default = ["std"]

View File

@@ -5,7 +5,7 @@ use zeroize::DefaultIsZeroes;
use rand_core::RngCore;
use group::ff::Field;
use group::ff::{Field, PrimeField};
use crate::{ShortWeierstrass, Projective};
@@ -23,8 +23,14 @@ impl<C: ShortWeierstrass> Clone for Affine<C> {
impl<C: ShortWeierstrass> Copy for Affine<C> {}
impl<C: ShortWeierstrass> Affine<C> {
pub fn try_from(projective: Projective<C>) -> CtOption<Self> {
projective.z.invert().map(|z_inv| Self { x: projective.x * z_inv, y: projective.y * z_inv })
pub fn try_from(projective: &Projective<C>) -> CtOption<Self> {
let z_inv = projective.z.invert().unwrap_or(C::FieldElement::ZERO);
let if_non_identity = Self { x: projective.x * z_inv, y: projective.y * z_inv };
let identity = z_inv.ct_eq(&C::FieldElement::ZERO);
// If this isn't a valid affine point, switch to a valid affine point
let value = <_>::conditional_select(&if_non_identity, &C::GENERATOR, identity);
// This is valid if the projective point wasn't the identity
CtOption::new(value, !identity)
}
}
@@ -73,24 +79,35 @@ impl<C: ShortWeierstrass> Affine<C> {
/// Create an affine point from `x, y` coordinates, without performing any checks.
///
/// This should NOT be used. It is solely intended for trusted data at compile-time. It MUST NOT
/// be used with any untrusted/unvalidated data.
pub const fn from_xy_unchecked(x: C::FieldElement, y: C::FieldElement) -> Self { Self { x, y } }
/// be used with any untrusted/unvalidated data. Providing any off-curve point may produce
/// completely undefined behavior.
pub const fn from_xy_unchecked(x: C::FieldElement, y: C::FieldElement) -> Self {
Self { x, y }
}
/// Decompress a point from it's `x`-coordinate and the sign of the `y` coordinate.
pub fn decompress(x: C::FieldElement, odd_y: Choice) -> CtOption<Self> {
let y_square = ((x.square() + C::A) * x) + C::B;
y_square.sqrt().and_then(|mut y| {
y = <_>::conditional_select(&y, &-y, odd_y.ct_ne(&y.is_odd()));
CtOption::new(Self { x, y }, 1.into())
})
}
/// The `x, y` coordinates of this point.
pub fn coordinates(self) -> (C::FieldElement, C::FieldElement) {
(self.x, self.y)
}
/// Sample a random on-curve point with an unknown discrete logarithm w.r.t. any other points.
pub fn random(mut rng: impl RngCore) -> Self {
loop {
let x = C::FieldElement::random(&mut rng);
let y_square = ((x.square() + C::A) * x) + C::B;
let Some(mut y) = Option::<C::FieldElement>::from(y_square.sqrt()) else { continue };
if (rng.next_u64() % 2) == 1 {
y = -y;
/// Sample a random on-curve point with an unknown discrete logarithm w.r.t. any other points.
///
/// This runs in time variable to the amount of samples required to find a point.
pub fn random(mut rng: impl RngCore) -> Self {
loop {
let x = C::FieldElement::random(&mut rng);
let y_is_odd = Choice::from((rng.next_u64() % 2) as u8);
let Some(res) = Option::<Self>::from(Self::decompress(x, y_is_odd)) else { continue };
return res;
}
return Self { x, y };
}
}
}

View File

@@ -5,6 +5,7 @@
use core::fmt::Debug;
use subtle::Choice;
use zeroize::Zeroize;
use group::ff::PrimeField;
@@ -14,6 +15,10 @@ mod projective;
pub use projective::Projective;
/// An elliptic curve represented in short Weierstrass form, with equation `y^2 = x^3 + A x + B`.
///
/// This elliptic curve is expected to be of prime order. If a generator of the elliptic curve has
/// a composite order, the elliptic curve is defined solely as its largest odd-prime-order
/// subgroup, further considered the entire group/elliptic curve.
pub trait ShortWeierstrass: 'static + Sized + Debug {
/// The field the elliptic curve is defined over.
type FieldElement: Zeroize + PrimeField;
@@ -27,4 +32,17 @@ pub trait ShortWeierstrass: 'static + Sized + Debug {
///
/// This may be omitted by specifying `()`.
type Scalar;
/// The type used for encoding points.
type Repr: 'static + Send + Sync + Copy + Default + AsRef<[u8]> + AsMut<[u8]>;
/// The representation of the identity point.
const IDENTITY: Self::Repr;
/// Compress an affine point its byte encoding.
///
/// The space of potential outputs MUST exclude `Self::IDENTITY`.
fn compress(x: Self::FieldElement, odd_y: Choice) -> Self::Repr;
/// Decode a compressed point.
///
/// This is expected to return the `x` coordinate and if the `y` coordinate is odd.
fn decode_compressed(bytes: &Self::Repr) -> (<Self::FieldElement as PrimeField>::Repr, Choice);
}

View File

@@ -1,13 +1,14 @@
use core::{hint::black_box, borrow::Borrow, ops::*, iter::Sum};
use subtle::{Choice, ConstantTimeEq, ConditionallySelectable, ConditionallyNegatable};
use subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable, ConditionallyNegatable};
use zeroize::{Zeroize, DefaultIsZeroes};
use rand_core::RngCore;
use group::{
ff::{Field, PrimeFieldBits},
Group,
ff::{Field, PrimeField, PrimeFieldBits},
Group, GroupEncoding,
prime::PrimeGroup,
};
use crate::{ShortWeierstrass, Affine};
@@ -280,19 +281,6 @@ impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>, S: Borrow<C::Scalar>> MulAssig
*self = *self * scalar.borrow();
}
}
/*
impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> Mul<&C::Scalar> for Projective<C> {
type Output = Self;
fn mul(self, scalar: &C::Scalar) -> Self {
self * *scalar
}
}
impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> MulAssign<&C::Scalar> for Projective<C> {
fn mul_assign(&mut self, scalar: &C::Scalar) {
*self *= *scalar;
}
}
*/
impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> Group for Projective<C> {
type Scalar = C::Scalar;
fn random(rng: impl RngCore) -> Self {
@@ -311,3 +299,73 @@ impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> Group for Projective<C> {
self.double_internal()
}
}
impl<C: ShortWeierstrass> GroupEncoding for Projective<C> {
type Repr = C::Repr;
fn from_bytes(bytes: &C::Repr) -> CtOption<Self> {
// If this point is the identity point
let identity = bytes.as_ref().ct_eq(C::IDENTITY.as_ref());
let (x, odd_y) = C::decode_compressed(bytes);
// Parse x, recover y, return the result
C::FieldElement::from_repr(x).and_then(|x| {
let non_identity_on_curve_point = Affine::decompress(x, odd_y).map(Projective::from);
let identity = CtOption::new(Projective::IDENTITY, identity);
non_identity_on_curve_point.or_else(|| identity)
})
}
fn from_bytes_unchecked(bytes: &C::Repr) -> CtOption<Self> {
Self::from_bytes(bytes)
}
fn to_bytes(&self) -> C::Repr {
let affine_on_curve = Affine::try_from(self);
let identity = affine_on_curve.is_none();
let compressed_if_not_identity = {
let affine_on_curve = affine_on_curve.unwrap_or(C::GENERATOR);
let (x, y) = affine_on_curve.coordinates();
C::compress(x, y.is_odd())
};
let mut res = C::Repr::default();
{
let res = res.as_mut();
for (dst, (if_not_identity, if_identity)) in
res.iter_mut().zip(compressed_if_not_identity.as_ref().iter().zip(C::IDENTITY.as_ref()))
{
*dst = <_>::conditional_select(if_not_identity, if_identity, identity);
}
}
res
}
}
impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> PrimeGroup for Projective<C> {}
#[cfg(feature = "alloc")]
mod alloc {
use core::borrow::Borrow;
use ff::{PrimeField, PrimeFieldBits};
use crate::{ShortWeierstrass, Affine, Projective};
impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> ec_divisors::DivisorCurve for Projective<C> {
type FieldElement = C::FieldElement;
type XyPoint = ec_divisors::Projective<Self>;
fn interpolator_for_scalar_mul() -> impl Borrow<ec_divisors::Interpolator<C::FieldElement>> {
ec_divisors::Interpolator::new((<C::Scalar as PrimeField>::NUM_BITS as usize).div_ceil(2) + 2)
}
fn a() -> C::FieldElement {
C::A
}
fn b() -> C::FieldElement {
C::B
}
fn to_xy(point: Self) -> Option<(C::FieldElement, C::FieldElement)> {
Option::<Affine<C>>::from(Affine::try_from(&point)).map(Affine::<_>::coordinates)
}
}
}