Skip to content

Commit 2f32dd2

Browse files
authored
Move function requiring AffinePoint from EdwardsPoint to AffinePoint (#1333)
* Use `AffinePoint` where appropriate * Implement `BatchNormalize` for `EdwardsPoint`
1 parent 42c74d4 commit 2f32dd2

File tree

9 files changed

+513
-293
lines changed

9 files changed

+513
-293
lines changed

ed448-goldilocks/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ It is intended to be portable, fast, and safe.
1919
```rust
2020
use ed448_goldilocks::{Ed448, EdwardsPoint, CompressedEdwardsY, EdwardsScalar, sha3::Shake256};
2121
use elliptic_curve::Field;
22+
use elliptic_curve::group::GroupEncoding;
2223
use hash2curve::{ExpandMsgXof, GroupDigest};
2324
use rand_core::OsRng;
2425

@@ -29,9 +30,9 @@ assert_eq!(public_key, EdwardsPoint::GENERATOR + EdwardsPoint::GENERATOR);
2930

3031
let secret_key = EdwardsScalar::try_from_rng(&mut OsRng).unwrap();
3132
let public_key = EdwardsPoint::GENERATOR * &secret_key;
32-
let compressed_public_key = public_key.compress();
33+
let compressed_public_key = public_key.to_bytes();
3334

34-
assert_eq!(compressed_public_key.to_bytes().len(), 57);
35+
assert_eq!(compressed_public_key.len(), 57);
3536

3637
let hashed_scalar = Ed448::hash_to_scalar::<ExpandMsgXof<Shake256>>(&[b"test"], &[b"edwards448_XOF:SHAKE256_ELL2_RO_"]).unwrap();
3738
let input = hex_literal::hex!("c8c6c8f584e0c25efdb6af5ad234583c56dedd7c33e0c893468e96740fa0cf7f1a560667da40b7bde340a39252e89262fcf707d1180fd43400");
@@ -40,7 +41,7 @@ assert_eq!(hashed_scalar, expected_scalar);
4041

4142
let hashed_point = Ed448::hash_from_bytes::<ExpandMsgXof<Shake256>>(&[b"test"], &[b"edwards448_XOF:SHAKE256_ELL2_RO_"]).unwrap();
4243
let expected = hex_literal::hex!("d15c4427b5c5611a53593c2be611fd3635b90272d331c7e6721ad3735e95dd8b9821f8e4e27501ce01aa3c913114052dce2e91e8ca050f4980");
43-
let expected_point = CompressedEdwardsY(expected).decompress().unwrap();
44+
let expected_point = CompressedEdwardsY(expected).decompress().unwrap().to_edwards();
4445
assert_eq!(hashed_point, expected_point);
4546

4647
let hashed_point = EdwardsPoint::hash_with_defaults(b"test");

ed448-goldilocks/src/edwards.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
pub(crate) mod affine;
1313
pub(crate) mod extended;
1414
mod scalar;
15-
pub use affine::AffinePoint;
16-
pub use extended::{CompressedEdwardsY, EdwardsPoint};
15+
pub use affine::{AffinePoint, CompressedEdwardsY};
16+
pub use extended::EdwardsPoint;
1717
pub use scalar::{EdwardsScalar, EdwardsScalarBytes, WideEdwardsScalarBytes};

ed448-goldilocks/src/edwards/affine.rs

Lines changed: 278 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use crate::field::FieldElement;
22
use crate::*;
3+
use core::fmt::{Display, Formatter, LowerHex, Result as FmtResult, UpperHex};
34
use core::ops::Mul;
4-
use elliptic_curve::{Error, Result, point::NonIdentity, zeroize::DefaultIsZeroes};
5-
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
5+
use elliptic_curve::{Error, point::NonIdentity, zeroize::DefaultIsZeroes};
6+
use rand_core::TryRngCore;
7+
use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, CtOption};
68

79
/// Affine point on untwisted curve
810
#[derive(Copy, Clone, Debug)]
@@ -69,6 +71,21 @@ impl AffinePoint {
6971
y: FieldElement::ONE,
7072
};
7173

74+
/// Generate a random [`AffinePoint`].
75+
pub fn try_from_rng<R>(rng: &mut R) -> Result<Self, R::Error>
76+
where
77+
R: TryRngCore + ?Sized,
78+
{
79+
let mut bytes = CompressedEdwardsY::default();
80+
81+
loop {
82+
rng.try_fill_bytes(&mut bytes.0)?;
83+
if let Some(point) = bytes.decompress().into() {
84+
return Ok(point);
85+
}
86+
}
87+
}
88+
7289
pub(crate) fn isogeny(&self) -> Self {
7390
let x = self.x;
7491
let y = self.y;
@@ -100,6 +117,33 @@ impl AffinePoint {
100117
}
101118
}
102119

120+
/// Standard compression; store Y and sign of X
121+
pub fn compress(&self) -> CompressedEdwardsY {
122+
let affine_x = self.x;
123+
let affine_y = self.y;
124+
125+
let mut compressed_bytes = [0u8; 57];
126+
127+
let sign = affine_x.is_negative().unwrap_u8();
128+
129+
let y_bytes = affine_y.to_bytes();
130+
compressed_bytes[..y_bytes.len()].copy_from_slice(&y_bytes[..]);
131+
*compressed_bytes.last_mut().expect("at least one byte") = sign << 7;
132+
CompressedEdwardsY(compressed_bytes)
133+
}
134+
135+
/// Check if this point is on the curve
136+
pub fn is_on_curve(&self) -> Choice {
137+
// X^2 + Y^2 == 1 + D * X^2 * Y^2
138+
139+
let XX = self.x.square();
140+
let YY = self.y.square();
141+
let lhs = YY + XX;
142+
let rhs = FieldElement::ONE + FieldElement::EDWARDS_D * XX * YY;
143+
144+
lhs.ct_eq(&rhs)
145+
}
146+
103147
/// Convert to edwards extended point
104148
pub fn to_edwards(&self) -> EdwardsPoint {
105149
EdwardsPoint {
@@ -130,7 +174,7 @@ impl From<NonIdentity<AffinePoint>> for AffinePoint {
130174
impl TryFrom<AffinePoint> for NonIdentity<AffinePoint> {
131175
type Error = Error;
132176

133-
fn try_from(affine_point: AffinePoint) -> Result<Self> {
177+
fn try_from(affine_point: AffinePoint) -> Result<Self, Error> {
134178
NonIdentity::new(affine_point).into_option().ok_or(Error)
135179
}
136180
}
@@ -149,3 +193,234 @@ define_mul_variants!(
149193
RHS = EdwardsScalar,
150194
Output = EdwardsPoint
151195
);
196+
197+
/// The compressed internal representation of a point on the Twisted Edwards Curve
198+
pub type PointBytes = [u8; 57];
199+
200+
/// Represents a point on the Compressed Twisted Edwards Curve
201+
/// in little endian format where the most significant bit is the sign bit
202+
/// and the remaining 448 bits represent the y-coordinate
203+
#[derive(Copy, Clone, Debug)]
204+
pub struct CompressedEdwardsY(pub PointBytes);
205+
206+
impl elliptic_curve::zeroize::Zeroize for CompressedEdwardsY {
207+
fn zeroize(&mut self) {
208+
self.0.zeroize()
209+
}
210+
}
211+
212+
impl Display for CompressedEdwardsY {
213+
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
214+
for b in &self.0[..] {
215+
write!(f, "{b:02x}")?;
216+
}
217+
Ok(())
218+
}
219+
}
220+
221+
impl LowerHex for CompressedEdwardsY {
222+
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
223+
for b in &self.0[..] {
224+
write!(f, "{b:02x}")?;
225+
}
226+
Ok(())
227+
}
228+
}
229+
230+
impl UpperHex for CompressedEdwardsY {
231+
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
232+
for b in &self.0[..] {
233+
write!(f, "{b:02X}")?;
234+
}
235+
Ok(())
236+
}
237+
}
238+
239+
impl Default for CompressedEdwardsY {
240+
fn default() -> Self {
241+
Self([0u8; 57])
242+
}
243+
}
244+
245+
impl ConditionallySelectable for CompressedEdwardsY {
246+
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
247+
let mut bytes = [0u8; 57];
248+
for (i, byte) in bytes.iter_mut().enumerate() {
249+
*byte = u8::conditional_select(&a.0[i], &b.0[i], choice);
250+
}
251+
Self(bytes)
252+
}
253+
}
254+
255+
impl ConstantTimeEq for CompressedEdwardsY {
256+
fn ct_eq(&self, other: &Self) -> Choice {
257+
self.0.ct_eq(&other.0)
258+
}
259+
}
260+
261+
impl PartialEq for CompressedEdwardsY {
262+
fn eq(&self, other: &CompressedEdwardsY) -> bool {
263+
self.ct_eq(other).into()
264+
}
265+
}
266+
267+
impl Eq for CompressedEdwardsY {}
268+
269+
impl AsRef<[u8]> for CompressedEdwardsY {
270+
fn as_ref(&self) -> &[u8] {
271+
&self.0[..]
272+
}
273+
}
274+
275+
impl AsRef<PointBytes> for CompressedEdwardsY {
276+
fn as_ref(&self) -> &PointBytes {
277+
&self.0
278+
}
279+
}
280+
281+
#[cfg(feature = "alloc")]
282+
impl From<CompressedEdwardsY> for Vec<u8> {
283+
fn from(value: CompressedEdwardsY) -> Self {
284+
Self::from(&value)
285+
}
286+
}
287+
288+
#[cfg(feature = "alloc")]
289+
impl From<&CompressedEdwardsY> for Vec<u8> {
290+
fn from(value: &CompressedEdwardsY) -> Self {
291+
value.0.to_vec()
292+
}
293+
}
294+
295+
#[cfg(feature = "alloc")]
296+
impl TryFrom<Vec<u8>> for CompressedEdwardsY {
297+
type Error = &'static str;
298+
299+
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
300+
Self::try_from(&value)
301+
}
302+
}
303+
304+
#[cfg(feature = "alloc")]
305+
impl TryFrom<&Vec<u8>> for CompressedEdwardsY {
306+
type Error = &'static str;
307+
308+
fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
309+
Self::try_from(value.as_slice())
310+
}
311+
}
312+
313+
impl TryFrom<&[u8]> for CompressedEdwardsY {
314+
type Error = &'static str;
315+
316+
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
317+
let bytes = <PointBytes>::try_from(value).map_err(|_| "Invalid length")?;
318+
Ok(CompressedEdwardsY(bytes))
319+
}
320+
}
321+
322+
#[cfg(feature = "alloc")]
323+
impl TryFrom<Box<[u8]>> for CompressedEdwardsY {
324+
type Error = &'static str;
325+
326+
fn try_from(value: Box<[u8]>) -> Result<Self, Self::Error> {
327+
Self::try_from(value.as_ref())
328+
}
329+
}
330+
331+
impl From<CompressedEdwardsY> for PointBytes {
332+
fn from(value: CompressedEdwardsY) -> Self {
333+
value.0
334+
}
335+
}
336+
337+
impl From<&CompressedEdwardsY> for PointBytes {
338+
fn from(value: &CompressedEdwardsY) -> Self {
339+
Self::from(*value)
340+
}
341+
}
342+
343+
#[cfg(feature = "serde")]
344+
impl serdect::serde::Serialize for CompressedEdwardsY {
345+
fn serialize<S: serdect::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
346+
serdect::array::serialize_hex_lower_or_bin(&self.0, s)
347+
}
348+
}
349+
350+
#[cfg(feature = "serde")]
351+
impl<'de> serdect::serde::Deserialize<'de> for CompressedEdwardsY {
352+
fn deserialize<D>(d: D) -> Result<Self, D::Error>
353+
where
354+
D: serdect::serde::Deserializer<'de>,
355+
{
356+
let mut arr = [0u8; 57];
357+
serdect::array::deserialize_hex_or_bin(&mut arr, d)?;
358+
Ok(CompressedEdwardsY(arr))
359+
}
360+
}
361+
362+
impl From<PointBytes> for CompressedEdwardsY {
363+
fn from(point: PointBytes) -> Self {
364+
Self(point)
365+
}
366+
}
367+
368+
impl CompressedEdwardsY {
369+
/// The compressed generator point
370+
pub const GENERATOR: Self = Self([
371+
20, 250, 48, 242, 91, 121, 8, 152, 173, 200, 215, 78, 44, 19, 189, 253, 196, 57, 124, 230,
372+
28, 255, 211, 58, 215, 194, 160, 5, 30, 156, 120, 135, 64, 152, 163, 108, 115, 115, 234,
373+
75, 98, 199, 201, 86, 55, 32, 118, 136, 36, 188, 182, 110, 113, 70, 63, 105, 0,
374+
]);
375+
/// The compressed identity point
376+
pub const IDENTITY: Self = Self([0u8; 57]);
377+
378+
/// Attempt to decompress to an `AffinePoint`.
379+
///
380+
/// Returns `None` if the input is not the \\(y\\)-coordinate of a
381+
/// curve point.
382+
pub fn decompress_unchecked(&self) -> CtOption<AffinePoint> {
383+
// Safe to unwrap here as the underlying data structure is a slice
384+
let (sign, b) = self.0.split_last().expect("slice is non-empty");
385+
386+
let mut y_bytes: [u8; 56] = [0; 56];
387+
y_bytes.copy_from_slice(b);
388+
389+
// Recover x using y
390+
let y = FieldElement::from_bytes(&y_bytes);
391+
let yy = y.square();
392+
let dyy = FieldElement::EDWARDS_D * yy;
393+
let numerator = FieldElement::ONE - yy;
394+
let denominator = FieldElement::ONE - dyy;
395+
396+
let (mut x, is_res) = FieldElement::sqrt_ratio(&numerator, &denominator);
397+
398+
// Compute correct sign of x
399+
let compressed_sign_bit = Choice::from(sign >> 7);
400+
let is_negative = x.is_negative();
401+
x.conditional_negate(compressed_sign_bit ^ is_negative);
402+
403+
CtOption::new(AffinePoint { x, y }, is_res)
404+
}
405+
406+
/// Attempt to decompress to an `AffinePoint`.
407+
///
408+
/// Returns `None`:
409+
/// - if the input is not the \\(y\\)-coordinate of a curve point.
410+
/// - if the input point is not on the curve.
411+
/// - if the input point has nonzero torsion component.
412+
pub fn decompress(&self) -> CtOption<AffinePoint> {
413+
self.decompress_unchecked()
414+
.and_then(|pt| CtOption::new(pt, pt.is_on_curve() & pt.to_edwards().is_torsion_free()))
415+
}
416+
417+
/// View this `CompressedEdwardsY` as an array of bytes.
418+
pub const fn as_bytes(&self) -> &PointBytes {
419+
&self.0
420+
}
421+
422+
/// Copy this `CompressedEdwardsY` to an array of bytes.
423+
pub const fn to_bytes(&self) -> PointBytes {
424+
self.0
425+
}
426+
}

0 commit comments

Comments
 (0)