Skip to content

Commit f283f78

Browse files
committed
Use AffinePoint where appropriate
1 parent b9a0167 commit f283f78

File tree

8 files changed

+326
-287
lines changed

8 files changed

+326
-287
lines changed

ed448-goldilocks/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ assert_eq!(public_key, EdwardsPoint::GENERATOR + EdwardsPoint::GENERATOR);
2929

3030
let secret_key = EdwardsScalar::try_from_rng(&mut OsRng).unwrap();
3131
let public_key = EdwardsPoint::GENERATOR * &secret_key;
32-
let compressed_public_key = public_key.compress();
32+
let compressed_public_key = public_key.to_affine().compress();
3333

3434
assert_eq!(compressed_public_key.to_bytes().len(), 57);
3535

@@ -38,12 +38,12 @@ let input = hex_literal::hex!("c8c6c8f584e0c25efdb6af5ad234583c56dedd7c33e0c8934
3838
let expected_scalar = EdwardsScalar::from_canonical_bytes(&input.into()).unwrap();
3939
assert_eq!(hashed_scalar, expected_scalar);
4040

41-
let hashed_point = Ed448::hash_from_bytes::<ExpandMsgXof<Shake256>>(&[b"test"], &[b"edwards448_XOF:SHAKE256_ELL2_RO_"]).unwrap();
41+
let hashed_point = Ed448::hash_from_bytes::<ExpandMsgXof<Shake256>>(&[b"test"], &[b"edwards448_XOF:SHAKE256_ELL2_RO_"]).unwrap().to_affine();
4242
let expected = hex_literal::hex!("d15c4427b5c5611a53593c2be611fd3635b90272d331c7e6721ad3735e95dd8b9821f8e4e27501ce01aa3c913114052dce2e91e8ca050f4980");
4343
let expected_point = CompressedEdwardsY(expected).decompress().unwrap();
4444
assert_eq!(hashed_point, expected_point);
4545

46-
let hashed_point = EdwardsPoint::hash_with_defaults(b"test");
46+
let hashed_point = EdwardsPoint::hash_with_defaults(b"test").to_affine();
4747
assert_eq!(hashed_point, expected_point);
4848
```
4949

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: 279 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,34 @@ impl AffinePoint {
100117
}
101118
}
102119

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

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

0 commit comments

Comments
 (0)