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

View file

@ -31,6 +31,12 @@ impl SwPoint {
} }
/// Convert an Edwards point into the short Weierstrass model. /// 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 { pub fn from_edwards(point: &EdwardsPoint) -> Self {
if point.is_identity() { if point.is_identity() {
return SwPoint::Identity; return SwPoint::Identity;
@ -54,10 +60,17 @@ impl SwPoint {
} }
/// Convert this point into an Edwards point, if defined. /// 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> { pub fn to_edwards(&self) -> Option<EdwardsPoint> {
match self { match self {
SwPoint::Identity => Some(EdwardsPoint::identity()), SwPoint::Identity => Some(EdwardsPoint::identity()),
SwPoint::Affine { x, y } => { SwPoint::Affine { x, y } => {
if !affine_coordinates_on_curve(x, y) {
return None;
}
if *y == FieldElement::ZERO { if *y == FieldElement::ZERO {
return None; return None;
} }
@ -89,14 +102,18 @@ impl SwPoint {
} }
/// Return affine coordinates as little-endian byte arrays. /// 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 { match self {
SwPoint::Identity => None, SwPoint::Identity => ([0u8; 32], [0u8; 32]),
SwPoint::Affine { x, y } => Some((x.to_bytes(), y.to_bytes())), SwPoint::Affine { x, y } => (x.to_bytes(), y.to_bytes()),
} }
} }
/// Build a point from affine little-endian byte arrays. /// 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> { pub fn from_affine_le_bytes(x: [u8; 32], y: [u8; 32]) -> Option<Self> {
if x == [0u8; 32] && y == [0u8; 32] { if x == [0u8; 32] && y == [0u8; 32] {
return Some(SwPoint::Identity); return Some(SwPoint::Identity);
@ -142,17 +159,19 @@ impl SwPoint {
pub fn is_on_curve(&self) -> bool { pub fn is_on_curve(&self) -> bool {
match self { match self {
SwPoint::Identity => true, SwPoint::Identity => true,
SwPoint::Affine { x, y } => { SwPoint::Affine { x, y } => affine_coordinates_on_curve(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 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 { fn double_affine(x: &FieldElement, y: &FieldElement) -> SwPoint {
if *y == FieldElement::ZERO { if *y == FieldElement::ZERO {
return SwPoint::Identity; return SwPoint::Identity;
@ -219,6 +238,8 @@ mod tests {
use super::SwPoint; use super::SwPoint;
use super::{sw_a, sw_b}; use super::{sw_a, sw_b};
use crate::constants; use crate::constants;
use crate::edwards::EdwardsPoint;
use crate::field::FieldElement;
use crate::scalar::Scalar; use crate::scalar::Scalar;
use rand::Rng; use rand::Rng;
@ -338,12 +359,12 @@ mod tests {
#[test] #[test]
fn sw_constants_match_expected() { 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, 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, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0x2a, 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, 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, 0x09, 0xed, 0x25, 0xb4, 0x97, 0xd0, 0x5e, 0x42, 0x7b, 0x09, 0xed, 0x25, 0xb4, 0x97,
0xd0, 0x5e, 0x42, 0x7b, 0xd0, 0x5e, 0x42, 0x7b,
@ -352,4 +373,44 @@ mod tests {
assert_eq!(sw_a(), expected_a); assert_eq!(sw_a(), expected_a);
assert_eq!(sw_b(), expected_b); 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)
);
}
} }