From d7ce16352d293be2a8cae8a714f26012a1553d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Wed, 31 May 2023 10:32:41 -0400 Subject: [PATCH 1/3] feat: implement UncompressedEncoding - Update Rust toolchain to version 1.59.0 to satisfy the requirement of constant_time_eq v0.2.5, - Implement UncompressedEncoding for affine representations, - Add `paste` dependency at version 1.0.12 in order to give a specific struct for that representation for each of Pallas, Vesta. --- Cargo.toml | 1 + src/curves.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 7add469..8fb476c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ ec-gpu = { version = "0.2.0", optional = true } # serde dependencies serde_crate = { version = "1.0.16", optional = true, default-features = false, features = ["alloc"], package = "serde" } hex = { version = "0.4", optional = true, default-features = false, features = ["alloc", "serde"] } +paste = "1.0.12" [features] default = ["bits", "sqrt-table"] diff --git a/src/curves.rs b/src/curves.rs index 41ccb9a..bea158c 100644 --- a/src/curves.rs +++ b/src/curves.rs @@ -269,6 +269,114 @@ macro_rules! new_curve_impl { } } + paste::paste! { + + /// Uncompressed encoding of the affine representation of a point on the elliptic curve $name. + #[derive(Copy, Clone)] + pub struct [< $name Uncompressed >]([u8; 64]); + + impl fmt::Debug for [< $name Uncompressed >] { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0[..].fmt(f) + } + } + + impl Default for [< $name Uncompressed >] { + fn default() -> Self { + [< $name Uncompressed >]([0; 64]) + } + } + + impl AsRef<[u8]> for [< $name Uncompressed >] { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl AsMut<[u8]> for [< $name Uncompressed >] { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + + impl ConstantTimeEq for [< $name Uncompressed >] { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } + } + + impl cmp::Eq for [< $name Uncompressed >] {} + + impl PartialEq for [< $name Uncompressed >] { + #[inline] + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } + } + + impl group::UncompressedEncoding for $name_affine{ + type Uncompressed = [< $name Uncompressed >]; + + fn from_uncompressed(bytes: &Self::Uncompressed) -> CtOption { + Self::from_uncompressed_unchecked(bytes).and_then(|p| CtOption::new(p, p.is_on_curve())) + } + + fn from_uncompressed_unchecked(bytes: &Self::Uncompressed) -> CtOption { + let bytes = &bytes.0; + let infinity_flag_set = Choice::from((bytes[64 - 1] >> 6) & 1); + // Attempt to obtain the x-coordinate + let x = { + let mut tmp = [0; 32]; + tmp.copy_from_slice(&bytes[0..32]); + $base::from_repr(tmp) + }; + + // Attempt to obtain the y-coordinate + let y = { + let mut tmp = [0; 32]; + tmp.copy_from_slice(&bytes[32..2*32]); + $base::from_repr(tmp) + }; + + x.and_then(|x| { + y.and_then(|y| { + // Create a point representing this value + let p = $name_affine::conditional_select( + &$name_affine{ + x, + y, + }, + &$name_affine::identity(), + infinity_flag_set, + ); + + CtOption::new( + p, + // If the infinity flag is set, the x and y coordinates should have been zero. + ((!infinity_flag_set) | (x.is_zero() & y.is_zero())) + ) + }) + }) + } + + fn to_uncompressed(&self) -> Self::Uncompressed { + let mut res = [0; 64]; + + res[0..32].copy_from_slice( + &$base::conditional_select(&self.x, &$base::zero(), self.is_identity()).to_repr()[..], + ); + res[32.. 2*32].copy_from_slice( + &$base::conditional_select(&self.y, &$base::zero(), self.is_identity()).to_repr()[..], + ); + + res[64 - 1] |= u8::conditional_select(&0u8, &(1u8 << 6), self.is_identity()); + + [< $name Uncompressed >](res) + } + } + + } + impl<'a> From<&'a $name_affine> for $name { fn from(p: &'a $name_affine) -> $name { p.to_curve() From f28fb4d60a7641f2daac9cdad1e545391602b44e Mon Sep 17 00:00:00 2001 From: Hanting Zhang Date: Sat, 3 Jun 2023 15:09:37 -0700 Subject: [PATCH 2/3] add serde for uncompressed --- src/curves.rs | 2 +- src/serde_impl.rs | 136 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 135 insertions(+), 3 deletions(-) diff --git a/src/curves.rs b/src/curves.rs index bea158c..73b0b94 100644 --- a/src/curves.rs +++ b/src/curves.rs @@ -273,7 +273,7 @@ macro_rules! new_curve_impl { /// Uncompressed encoding of the affine representation of a point on the elliptic curve $name. #[derive(Copy, Clone)] - pub struct [< $name Uncompressed >]([u8; 64]); + pub struct [< $name Uncompressed >](pub(crate) [u8; 64]); impl fmt::Debug for [< $name Uncompressed >] { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/serde_impl.rs b/src/serde_impl.rs index c26201b..4dd80e0 100644 --- a/src/serde_impl.rs +++ b/src/serde_impl.rs @@ -1,13 +1,17 @@ +use core::fmt; use ff::PrimeField; use group::GroupEncoding; use serde_crate::{ - de::Error as DeserializeError, Deserialize, Deserializer, Serialize, Serializer, + de::{Error as DeserializeError, SeqAccess, Visitor}, + ser::SerializeTuple, + Deserialize, Deserializer, Serialize, Serializer, }; use crate::{ curves::{Ep, EpAffine, Eq, EqAffine}, fields::{Fp, Fq}, group::Curve, + EpUncompressed, EqUncompressed, }; /// Serializes bytes to human readable or compact representation. @@ -130,6 +134,83 @@ impl<'de> Deserialize<'de> for Eq { } } +struct ByteArrayVisitor {} + +impl<'de> Visitor<'de> for ByteArrayVisitor { + type Value = [u8; 64]; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(concat!("an array of length ", 64)) + } + + fn visit_seq(self, mut seq: A) -> Result<[u8; 64], A::Error> + where + A: SeqAccess<'de>, + { + let mut arr = [u8::default(); 64]; + for i in 0..64 { + arr[i] = seq + .next_element()? + .ok_or_else(|| DeserializeError::invalid_length(i, &self))?; + } + Ok(arr) + } +} + +impl Serialize for EpUncompressed { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + hex::serde::serialize(self.0, s) + } else { + let mut seq = s.serialize_tuple(64)?; + for elem in self.0 { + seq.serialize_element(&elem)?; + } + seq.end() + } + } +} + +impl<'de> Deserialize<'de> for EpUncompressed { + fn deserialize>(d: D) -> Result { + let array = if d.is_human_readable() { + hex::serde::deserialize(d)? + } else { + let visitor = ByteArrayVisitor {}; + d.deserialize_tuple(64, visitor)? + }; + + Ok(Self(array)) + } +} + +impl Serialize for EqUncompressed { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + hex::serde::serialize(self.0, s) + } else { + let mut seq = s.serialize_tuple(64)?; + for elem in self.0 { + seq.serialize_element(&elem)?; + } + seq.end() + } + } +} + +impl<'de> Deserialize<'de> for EqUncompressed { + fn deserialize>(d: D) -> Result { + let array = if d.is_human_readable() { + hex::serde::deserialize(d)? + } else { + let visitor = ByteArrayVisitor {}; + d.deserialize_tuple(64, visitor)? + }; + + Ok(Self(array)) + } +} + #[cfg(test)] mod tests { use super::*; @@ -137,7 +218,7 @@ mod tests { use core::fmt::Debug; use ff::Field; - use group::{prime::PrimeCurveAffine, Curve, Group}; + use group::{prime::PrimeCurveAffine, Curve, Group, UncompressedEncoding}; use rand::SeedableRng; use rand_xorshift::XorShiftRng; @@ -444,4 +525,55 @@ mod tests { f ); } + + #[test] + fn serde_ep_uncompressed() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for _ in 0..100 { + let f = Ep::random(&mut rng).to_affine().to_uncompressed(); + test_roundtrip(&f); + } + + let f = Ep::identity().to_affine().to_uncompressed(); + test_roundtrip(&f); + assert_eq!( + serde_json::from_slice::( + br#""00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040""# + ) + .unwrap(), + f + ); + assert_eq!( + bincode::deserialize::(&[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 64 + ]) + .unwrap(), + f + ); + + let f = Ep::generator().to_affine().to_uncompressed(); + test_roundtrip(&f); + assert_eq!( + serde_json::from_slice::( + br#""00000000ed302d991bf94c09fc984622000000000000000000000000000000400200000000000000000000000000000000000000000000000000000000000000""# + ) + .unwrap(), + f + ); + assert_eq!( + bincode::deserialize::(&[ + 0, 0, 0, 0, 237, 48, 45, 153, 27, 249, 76, 9, 252, 152, 70, 34, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]) + .unwrap(), + f + ); + } } From dd19db3939788498ede41a304afb19b0185fc040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Mon, 5 Jun 2023 09:20:56 -0400 Subject: [PATCH 3/3] test: test serde on Eq field as well --- src/serde_impl.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/serde_impl.rs b/src/serde_impl.rs index 4dd80e0..6b6f388 100644 --- a/src/serde_impl.rs +++ b/src/serde_impl.rs @@ -576,4 +576,55 @@ mod tests { f ); } + + #[test] + fn serde_eq_uncompressed() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for _ in 0..100 { + let f = Eq::random(&mut rng).to_affine().to_uncompressed(); + test_roundtrip(&f); + } + + let f = Eq::identity().to_affine().to_uncompressed(); + test_roundtrip(&f); + assert_eq!( + serde_json::from_slice::( + br#""00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040""# + ) + .unwrap(), + f + ); + assert_eq!( + bincode::deserialize::(&[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 64 + ]) + .unwrap(), + f + ); + + let f = Eq::generator().to_affine().to_uncompressed(); + test_roundtrip(&f); + assert_eq!( + serde_json::from_slice::( + br#""0000000021eb468cdda89409fc984622000000000000000000000000000000400200000000000000000000000000000000000000000000000000000000000000""# + ) + .unwrap(), + f + ); + assert_eq!( + bincode::deserialize::(&[ + 0, 0, 0, 0, 33, 235, 70, 140, 221, 168, 148, 9, 252, 152, 70, 34, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]) + .unwrap(), + f + ); + } }