[ed25519] fix point conversions (#46)

* Update short_weierstrass.rs

* remove option for to_affine_le_bytes
This commit is contained in:
zz-sol 2026-06-16 09:05:18 -04:00 committed by GitHub
parent 53383206b8
commit befbe09d36
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 79 additions and 28 deletions

View file

@ -59,15 +59,10 @@ fn le_bytes_to_u64x4(bytes: [u8; 32]) -> [u64; 4] {
///
/// The identity is encoded as (0, 0) to match SPPARK `affine_t` rules.
pub fn sw_point_to_sppark_affine(point: &SwPoint) -> SpparkAffine {
match point.to_affine_le_bytes() {
Some((x, y)) => SpparkAffine {
x: le_bytes_to_u64x4(x),
y: le_bytes_to_u64x4(y),
},
None => SpparkAffine {
x: [0u64; 4],
y: [0u64; 4],
},
let (x, y) = point.to_affine_le_bytes();
SpparkAffine {
x: le_bytes_to_u64x4(x),
y: le_bytes_to_u64x4(y),
}
}
@ -81,13 +76,8 @@ pub fn scalar_to_sppark_scalar(scalar: &Scalar) -> SpparkScalar {
/// Convert a short Weierstrass point into byte-serialized affine layout.
pub fn sw_point_to_sppark_affine_bytes(point: &SwPoint) -> SpparkAffineBytes {
match point.to_affine_le_bytes() {
Some((x, y)) => SpparkAffineBytes { x, y },
None => SpparkAffineBytes {
x: [0u8; 32],
y: [0u8; 32],
},
}
let (x, y) = point.to_affine_le_bytes();
SpparkAffineBytes { x, y }
}
/// Convert a scalar into byte-serialized layout.

View file

@ -31,6 +31,12 @@ impl SwPoint {
}
/// Convert an Edwards point into the short Weierstrass model.
///
/// The Edwards and short-Weierstrass models are related by a birational
/// map, so there are exceptional points that do not round-trip through
/// both directions. In particular, the non-identity Edwards point
/// `(0, -1)` maps to a short-Weierstrass affine point with `y == 0`;
/// [`Self::to_edwards`] rejects that exceptional affine point.
pub fn from_edwards(point: &EdwardsPoint) -> Self {
if point.is_identity() {
return SwPoint::Identity;
@ -54,10 +60,17 @@ impl SwPoint {
}
/// Convert this point into an Edwards point, if defined.
///
/// Returns `None` for affine coordinates that are not on the short-Weierstrass
/// curve and for exceptional affine points where the birational map is
/// undefined.
pub fn to_edwards(&self) -> Option<EdwardsPoint> {
match self {
SwPoint::Identity => Some(EdwardsPoint::identity()),
SwPoint::Affine { x, y } => {
if !affine_coordinates_on_curve(x, y) {
return None;
}
if *y == FieldElement::ZERO {
return None;
}
@ -89,14 +102,18 @@ impl SwPoint {
}
/// Return affine coordinates as little-endian byte arrays.
pub fn to_affine_le_bytes(&self) -> Option<([u8; 32], [u8; 32])> {
///
/// The point at infinity is encoded as the reserved all-zero pair.
pub fn to_affine_le_bytes(&self) -> ([u8; 32], [u8; 32]) {
match self {
SwPoint::Identity => None,
SwPoint::Affine { x, y } => Some((x.to_bytes(), y.to_bytes())),
SwPoint::Identity => ([0u8; 32], [0u8; 32]),
SwPoint::Affine { x, y } => (x.to_bytes(), y.to_bytes()),
}
}
/// Build a point from affine little-endian byte arrays.
///
/// The all-zero pair is reserved as the point-at-infinity encoding.
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);
@ -142,17 +159,19 @@ impl SwPoint {
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()
}
SwPoint::Affine { x, y } => affine_coordinates_on_curve(x, y),
}
}
}
fn affine_coordinates_on_curve(x: &FieldElement, y: &FieldElement) -> bool {
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;
@ -219,6 +238,8 @@ mod tests {
use super::SwPoint;
use super::{sw_a, sw_b};
use crate::constants;
use crate::edwards::EdwardsPoint;
use crate::field::FieldElement;
use crate::scalar::Scalar;
use rand::Rng;
@ -338,12 +359,12 @@ mod tests {
#[test]
fn sw_constants_match_expected() {
let expected_a = crate::field::FieldElement::from_bytes(&[
let expected_a = 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(&[
let expected_b = 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,
@ -352,4 +373,44 @@ mod tests {
assert_eq!(sw_a(), expected_a);
assert_eq!(sw_b(), expected_b);
}
#[test]
fn sw_to_edwards_rejects_off_curve_affine_input() {
let malformed = SwPoint::Affine {
x: FieldElement::ZERO,
y: FieldElement::ONE,
};
assert!(!malformed.is_on_curve());
assert!(malformed.to_edwards().is_none());
}
#[test]
fn sw_to_edwards_rejects_order_two_exception() {
let order_two = EdwardsPoint {
X: FieldElement::ZERO,
Y: FieldElement::MINUS_ONE,
Z: FieldElement::ONE,
T: FieldElement::ZERO,
};
let sw = SwPoint::from_edwards(&order_two);
assert!(sw.is_on_curve());
let SwPoint::Affine { y, .. } = sw else {
panic!("order-two point should map to affine");
};
assert_eq!(y, FieldElement::ZERO);
assert!(sw.to_edwards().is_none());
}
#[test]
fn sw_identity_affine_bytes_round_trip() {
let encoded = SwPoint::Identity.to_affine_le_bytes();
assert_eq!(encoded, ([0u8; 32], [0u8; 32]));
assert_eq!(
SwPoint::from_affine_le_bytes(encoded.0, encoded.1),
Some(SwPoint::Identity)
);
}
}