anza-cryptography-source/curve25519/solana-ed25519/src/short_weierstrass.rs
zz-sol 154b58b3f4
[ed25519] downgrade rand_core (#21)
* downgrade rand_core

* Update bench.rs

* remove `try_from_rng`
2026-05-18 21:50:26 -04:00

355 lines
11 KiB
Rust

//! Short Weierstrass form utilities for Curve25519.
//!
//! This module provides a lightweight affine representation and conversion
//! utilities for moving between the Edwards and short Weierstrass models.
use crate::edwards::EdwardsPoint;
use crate::field::FieldElement;
use crate::traits::{Identity, IsIdentity};
/// Affine point on the short Weierstrass form of Curve25519.
///
/// Note: the SW coefficient `a` is non-zero, which means SPPARK must be
/// instantiated with a valid `a4` constant matching `sw_a()`.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SwPoint {
/// The point at infinity.
Identity,
/// An affine point with coordinates (x, y).
Affine {
/// x-coordinate.
x: FieldElement,
/// y-coordinate.
y: FieldElement,
},
}
impl SwPoint {
/// Return the identity point.
pub fn identity() -> Self {
SwPoint::Identity
}
/// Convert an Edwards point into the short Weierstrass model.
pub fn from_edwards(point: &EdwardsPoint) -> Self {
if point.is_identity() {
return SwPoint::Identity;
}
let z_inv = point.Z.invert();
let x_affine = &point.X * &z_inv;
let y_affine = &point.Y * &z_inv;
let one = FieldElement::ONE;
// Montgomery u = (1 + y) / (1 - y)
let u = &(&one + &y_affine) * &(&one - &y_affine).invert();
// Montgomery v = u / x, then scale to B=1 via sqrt(-486664)
let v = &u * &x_affine.invert();
let v = &montgomery_b_sqrt() * &v;
let x = &u + &montgomery_a_over_three();
let y = v;
SwPoint::Affine { x, y }
}
/// Convert this point into an Edwards point, if defined.
pub fn to_edwards(&self) -> Option<EdwardsPoint> {
match self {
SwPoint::Identity => Some(EdwardsPoint::identity()),
SwPoint::Affine { x, y } => {
if *y == FieldElement::ZERO {
return None;
}
let one = FieldElement::ONE;
let u = x - &montgomery_a_over_three();
let v = y * &montgomery_b_sqrt().invert();
let v_inv = v.invert();
// Edwards x = u / v
let x_ed = &u * &v_inv;
// Edwards y = (u - 1) / (u + 1)
let denom = &u + &one;
if denom == FieldElement::ZERO {
return None;
}
let denom_inv = denom.invert();
let y_ed = &(&u - &one) * &denom_inv;
Some(EdwardsPoint {
X: x_ed,
Y: y_ed,
Z: FieldElement::ONE,
T: &x_ed * &y_ed,
})
}
}
}
/// Return affine coordinates as little-endian byte arrays.
pub fn to_affine_le_bytes(&self) -> Option<([u8; 32], [u8; 32])> {
match self {
SwPoint::Identity => None,
SwPoint::Affine { x, y } => Some((x.to_bytes(), y.to_bytes())),
}
}
/// Build a point from affine little-endian byte arrays.
pub fn from_affine_le_bytes(x: [u8; 32], y: [u8; 32]) -> Option<Self> {
if x == [0u8; 32] && y == [0u8; 32] {
return Some(SwPoint::Identity);
}
let x = FieldElement::from_bytes(&x);
let y = FieldElement::from_bytes(&y);
let point = SwPoint::Affine { x, y };
if point.is_on_curve() {
Some(point)
} else {
None
}
}
/// Add two points in affine coordinates.
pub fn add(&self, other: &SwPoint) -> SwPoint {
match (self, other) {
(SwPoint::Identity, _) => *other,
(_, SwPoint::Identity) => *self,
(SwPoint::Affine { x: x1, y: y1 }, SwPoint::Affine { x: x2, y: y2 }) => {
if x1 == x2 {
if y1 == &(-y2) {
return SwPoint::Identity;
}
if y1 == y2 {
return double_affine(x1, y1);
}
}
let numerator = y2 - y1;
let denominator = (x2 - x1).invert();
let slope = &numerator * &denominator;
let x3 = &(&slope.square() - x1) - x2;
let y3 = &(&slope * &(x1 - &x3)) - y1;
SwPoint::Affine { x: x3, y: y3 }
}
}
}
/// Check whether this point lies on the short Weierstrass curve.
pub fn is_on_curve(&self) -> bool {
match self {
SwPoint::Identity => true,
SwPoint::Affine { x, y } => {
let y2 = y.square();
let x2 = x.square();
let x3 = &x2 * x;
let rhs = &x3 + &(&sw_a() * x);
y2 == &rhs + &sw_b()
}
}
}
}
fn double_affine(x: &FieldElement, y: &FieldElement) -> SwPoint {
if *y == FieldElement::ZERO {
return SwPoint::Identity;
}
let two = fe_from_u64(2);
let three = fe_from_u64(3);
let numerator = &(&three * &x.square()) + &sw_a();
let denominator = (&two * y).invert();
let slope = &numerator * &denominator;
let x3 = &slope.square() - &(&two * x);
let y3 = &(&slope * &(x - &x3)) - y;
SwPoint::Affine { x: x3, y: y3 }
}
fn fe_from_u64(n: u64) -> FieldElement {
let mut bytes = [0u8; 32];
bytes[..8].copy_from_slice(&n.to_le_bytes());
FieldElement::from_bytes(&bytes)
}
fn montgomery_a_over_three() -> FieldElement {
let inv_three = fe_from_u64(3).invert();
&montgomery_a() * &inv_three
}
/// Return the short Weierstrass curve coefficient a.
///
/// This value must match the `a4` constant provided to SPPARK templates.
pub(crate) fn sw_a() -> FieldElement {
let one = FieldElement::ONE;
let inv_three = fe_from_u64(3).invert();
let a_sq = montgomery_a().square();
&one - &(&a_sq * &inv_three)
}
/// Return the short Weierstrass curve coefficient b.
pub(crate) fn sw_b() -> FieldElement {
let a = montgomery_a();
let a2 = a.square();
let a3 = &a2 * &a;
let inv_three = fe_from_u64(3).invert();
let inv_twenty_seven = &(&inv_three * &inv_three) * &inv_three;
let two = fe_from_u64(2);
&(&a3 * &(&two * &inv_twenty_seven)) - &(&a * &inv_three)
}
fn montgomery_a() -> FieldElement {
fe_from_u64(486662)
}
fn montgomery_b_sqrt() -> FieldElement {
FieldElement::from_bytes(&[
0x06, 0x7e, 0x45, 0xff, 0xaa, 0x04, 0x6e, 0xcc, 0x82, 0x1a, 0x7d, 0x4b, 0xd1, 0xd3, 0xa1,
0xc5, 0x7e, 0x4f, 0xfc, 0x03, 0xdc, 0x08, 0x7b, 0xd2, 0xbb, 0x06, 0xa0, 0x60, 0xf4, 0xed,
0x26, 0x0f,
])
}
#[cfg(test)]
mod tests {
use super::SwPoint;
use super::{sw_a, sw_b};
use crate::constants;
use crate::scalar::Scalar;
use rand::Rng;
fn sw_scalar_mul(point: &SwPoint, scalar: &Scalar) -> SwPoint {
let mut acc = SwPoint::Identity;
let mut base = *point;
let bytes = scalar.to_bytes();
for i in 0..256 {
let byte = bytes[i / 8];
if ((byte >> (i % 8)) & 1) == 1 {
acc = acc.add(&base);
}
base = base.add(&base);
}
acc
}
fn random_scalar<R: Rng + ?Sized>(rng: &mut R) -> Scalar {
let mut wide = [0u8; 64];
rng.fill(&mut wide);
Scalar::from_bytes_mod_order_wide(&wide)
}
#[test]
fn sw_round_trip_add_matches_edwards() {
let mut rng = rand::thread_rng();
for _ in 0..32 {
let a = random_scalar(&mut rng);
let b = random_scalar(&mut rng);
let p = constants::ED25519_BASEPOINT_POINT * a;
let q = constants::ED25519_BASEPOINT_POINT * b;
let ed_sum = p + q;
let sw_p = SwPoint::from_edwards(&p);
let sw_q = SwPoint::from_edwards(&q);
let sw_sum = sw_p.add(&sw_q);
assert!(sw_sum.is_on_curve());
let back = sw_sum.to_edwards().expect("sw->edwards should succeed");
assert_eq!(back, ed_sum);
let ed_double = p + p;
let sw_double = sw_p.add(&sw_p);
assert!(sw_double.is_on_curve());
let back_double = sw_double.to_edwards().expect("sw->edwards should succeed");
assert_eq!(back_double, ed_double);
}
}
#[test]
fn sw_scalar_mul_matches_edwards() {
let mut rng = rand::thread_rng();
for _ in 0..32 {
let s = random_scalar(&mut rng);
let t = random_scalar(&mut rng);
let p = constants::ED25519_BASEPOINT_POINT * s;
let ed_result = p * t;
let sw_p = SwPoint::from_edwards(&p);
let sw_result = sw_scalar_mul(&sw_p, &t);
assert!(sw_result.is_on_curve());
let back = sw_result.to_edwards().expect("sw->edwards should succeed");
assert_eq!(back, ed_result);
}
}
#[test]
fn sw_add_associativity() {
let mut rng = rand::thread_rng();
for _ in 0..32 {
let a = random_scalar(&mut rng);
let b = random_scalar(&mut rng);
let c = random_scalar(&mut rng);
let p = SwPoint::from_edwards(&(constants::ED25519_BASEPOINT_POINT * a));
let q = SwPoint::from_edwards(&(constants::ED25519_BASEPOINT_POINT * b));
let r = SwPoint::from_edwards(&(constants::ED25519_BASEPOINT_POINT * c));
let left = p.add(&q).add(&r);
let right = p.add(&q.add(&r));
assert!(left.is_on_curve());
assert!(right.is_on_curve());
assert_eq!(left, right);
}
}
#[test]
fn sw_scalar_mul_associativity_commutes() {
let mut rng = rand::thread_rng();
let base = SwPoint::from_edwards(&constants::ED25519_BASEPOINT_POINT);
for _ in 0..32 {
let a = random_scalar(&mut rng);
let b = random_scalar(&mut rng);
let ab = a * b;
let left = sw_scalar_mul(&base, &ab);
let b_p = sw_scalar_mul(&base, &b);
let a_p = sw_scalar_mul(&base, &a);
let right1 = sw_scalar_mul(&b_p, &a);
let right2 = sw_scalar_mul(&a_p, &b);
assert!(left.is_on_curve());
assert!(right1.is_on_curve());
assert!(right2.is_on_curve());
assert_eq!(left, right1);
assert_eq!(left, right2);
}
}
#[test]
fn sw_constants_match_expected() {
let expected_a = crate::field::FieldElement::from_bytes(&[
0x44, 0xa1, 0x14, 0x49, 0x98, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0x2a,
]);
let expected_b = crate::field::FieldElement::from_bytes(&[
0x64, 0xc8, 0x10, 0x77, 0x9c, 0x5e, 0x0b, 0x26, 0xb4, 0x97, 0xd0, 0x5e, 0x42, 0x7b,
0x09, 0xed, 0x25, 0xb4, 0x97, 0xd0, 0x5e, 0x42, 0x7b, 0x09, 0xed, 0x25, 0xb4, 0x97,
0xd0, 0x5e, 0x42, 0x7b,
]);
assert_eq!(sw_a(), expected_a);
assert_eq!(sw_b(), expected_b);
}
}