diff --git a/.github/workflows/dusk_ci.yml b/.github/workflows/dusk_ci.yml index 86b5b79..e65f3b8 100644 --- a/.github/workflows/dusk_ci.yml +++ b/.github/workflows/dusk_ci.yml @@ -26,3 +26,9 @@ jobs: uses: dusk-network/.github/.github/workflows/run-tests.yml@main with: test_flags: --no-default-features + + test_serde: + name: Serde tests + uses: dusk-network/.github/.github/workflows/run-tests.yml@main + with: + test_flags: --features=serde diff --git a/Cargo.toml b/Cargo.toml index 4a3645e..6994758 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,6 +68,18 @@ optional = true default-features = false # End Dusk dependendencies +[dependencies.serde] +version = "1.0" +optional = true + +[dependencies.serde_json] +version = "1.0" +optional = true + +[dependencies.hex] +version = "0.4" +optional = true + [dev-dependencies] criterion = "0.3" csv = ">= 1.0, < 1.2" # csv 1.2 has MSRV 1.60 @@ -86,6 +98,7 @@ default = ["alloc", "bits"] alloc = ["ff/alloc", "group/alloc"] bits = ["ff/bits"] rkyv-impl = ["bytecheck", "dusk-bls12_381/rkyv-impl", "rkyv"] +serde = ["dep:serde", "serde_json", "dusk-bls12_381/serde", "hex"] [[bench]] name = "fq_bench" diff --git a/src/lib.rs b/src/lib.rs index 867d7a5..ab4479d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,11 +40,15 @@ extern crate alloc; #[macro_use] extern crate std; +#[cfg(feature = "serde")] +mod serde_support; + use bitvec::{order::Lsb0, view::AsBits}; use core::borrow::Borrow; use core::fmt; use core::iter::Sum; use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use dusk_bytes::{DeserializableSlice, Serializable}; use ff::{BatchInverter, Field}; use group::{ cofactor::{CofactorCurve, CofactorCurveAffine, CofactorGroup}, @@ -361,6 +365,49 @@ impl ConditionallySelectable for AffineNielsPoint { } } +impl ConstantTimeEq for AffineNielsPoint { + fn ct_eq(&self, other: &Self) -> Choice { + self.t2d.ct_eq(&other.t2d) + & self.v_minus_u.ct_eq(&other.v_minus_u) + & self.v_plus_u.ct_eq(&other.v_plus_u) + } +} + +impl PartialEq for AffineNielsPoint { + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} + +impl Serializable<{ Fq::SIZE * 3 }> for AffineNielsPoint { + type Error = dusk_bytes::Error; + + fn to_bytes(&self) -> [u8; 96] { + let v_plus_u = self.v_plus_u.to_bytes(); + let v_minus_u = self.v_minus_u.to_bytes(); + let t2d = self.t2d.to_bytes(); + + let mut bytes = [0; Self::SIZE]; + + bytes[..32].copy_from_slice(&v_plus_u); + bytes[32..64].copy_from_slice(&v_minus_u); + bytes[64..].copy_from_slice(&t2d); + + bytes + } + + fn from_bytes(bytes: &[u8; 96]) -> Result { + let v_plus_u = Fq::from_slice(&bytes[..32])?; + let v_minus_u = Fq::from_slice(&bytes[32..64])?; + let t2d = Fq::from_slice(&bytes[64..])?; + Ok(Self { + v_plus_u, + v_minus_u, + t2d, + }) + } +} + /// This is a pre-processed version of an extended point `(U, V, Z, T1, T2)` /// in the form `(V + U, V - U, Z, T1 * T2 * 2d)`. #[derive(Clone, Copy, Debug)] @@ -444,6 +491,54 @@ impl<'a, 'b> Mul<&'b Fr> for &'a ExtendedNielsPoint { } } +impl Serializable<{ Fq::SIZE * 4 }> for ExtendedNielsPoint { + type Error = dusk_bytes::Error; + + fn to_bytes(&self) -> [u8; 128] { + let v_plus_u = self.v_plus_u.to_bytes(); + let v_minus_u = self.v_minus_u.to_bytes(); + let t2d = self.t2d.to_bytes(); + let z = self.z.to_bytes(); + + let mut bytes = [0; Self::SIZE]; + + bytes[..32].copy_from_slice(&v_plus_u); + bytes[32..64].copy_from_slice(&v_minus_u); + bytes[64..96].copy_from_slice(&t2d); + bytes[96..].copy_from_slice(&z); + + bytes + } + + fn from_bytes(bytes: &[u8; 128]) -> Result { + let v_plus_u = Fq::from_slice(&bytes[..32])?; + let v_minus_u = Fq::from_slice(&bytes[32..64])?; + let t2d = Fq::from_slice(&bytes[64..96])?; + let z = Fq::from_slice(&bytes[96..])?; + Ok(Self { + v_plus_u, + v_minus_u, + t2d, + z, + }) + } +} + +impl ConstantTimeEq for ExtendedNielsPoint { + fn ct_eq(&self, other: &Self) -> Choice { + self.t2d.ct_eq(&other.t2d) + & self.v_minus_u.ct_eq(&other.v_minus_u) + & self.v_plus_u.ct_eq(&other.v_plus_u) + & self.z.ct_eq(&other.z) + } +} + +impl PartialEq for ExtendedNielsPoint { + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} + impl_binops_multiplicative_mixed!(ExtendedNielsPoint, Fr, ExtendedPoint); /// `d = -(10240/10241)` @@ -1214,6 +1309,12 @@ impl From for ExtendedPoint { } } +impl From for SubgroupPoint { + fn from(val: ExtendedPoint) -> Self { + Self(val) + } +} + impl<'a> From<&'a SubgroupPoint> for &'a ExtendedPoint { fn from(val: &'a SubgroupPoint) -> &'a ExtendedPoint { &val.0 diff --git a/src/serde_support.rs b/src/serde_support.rs new file mode 100644 index 0000000..e758dee --- /dev/null +++ b/src/serde_support.rs @@ -0,0 +1,167 @@ +extern crate alloc; + +use alloc::format; +use alloc::string::String; + +use dusk_bytes::Serializable; +use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; + +use crate::{ + AffineNielsPoint, AffinePoint, ExtendedNielsPoint, ExtendedPoint, Fr, + SubgroupPoint, +}; + +impl Serialize for AffinePoint { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = hex::encode(self.to_bytes()); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for AffinePoint { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + match hex::decode(s) { + Ok(bytes) => { + let bytes: [u8; 32] = bytes.try_into().map_err(|_| Error::custom(format!("Failed to deserialize AffinePoint: invalid byte length")))?; + AffinePoint::from_bytes(bytes) + .into_option() + .ok_or(Error::custom( + "Failed to deserialize AffinePoint: invalid AffinePoint", + )) + } + Err(e) => Err(Error::custom(format!( + "Failed to deserialize AffinePoint: {e}" + ))), + } + } +} + +impl Serialize for ExtendedPoint { + fn serialize( + &self, + serializer: S, + ) -> Result { + AffinePoint::from(self).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for ExtendedPoint { + fn deserialize>( + deserializer: D, + ) -> Result { + AffinePoint::deserialize(deserializer).map(|point| point.into()) + } +} + +impl Serialize for Fr { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = hex::encode(self.to_bytes()); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for Fr { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + match hex::decode(s) { + Ok(bytes) => { + let bytes: [u8; 32] = bytes.try_into().map_err(|_| { + Error::custom( + "Failed to deserialize Fr: invalid byte length", + ) + })?; + Fr::from_bytes(&bytes).into_option().ok_or(Error::custom( + "Failed to deserialize Fr: invalid Fr", + )) + } + Err(e) => { + Err(Error::custom(format!("Failed to deserialize Fr: {e}"))) + } + } + } +} + +impl Serialize for AffineNielsPoint { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = hex::encode(self.to_bytes()); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for AffineNielsPoint { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + match hex::decode(s) { + Ok(bytes) => { + let bytes: [u8; AffineNielsPoint::SIZE] = bytes.try_into().map_err(|_| Error::custom(format!("Failed to deserialize AffineNielsPoint: invalid byte length")))?; + AffineNielsPoint::from_bytes(&bytes) + .map_err(|_| Error::custom("Failed to deserialize AffineNielsPoint: invalid AffineNielsPoint")) + } + Err(e) => Err(Error::custom(format!( + "Failed to deserialize AffineNielsPoint: {e}" + ))), + } + } +} + +impl Serialize for ExtendedNielsPoint { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = hex::encode(self.to_bytes()); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for ExtendedNielsPoint { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + match hex::decode(s) { + Ok(bytes) => { + let bytes: [u8; 128] = bytes.try_into().map_err(|_| Error::custom("Failed to deserialize ExtendedNielsPoint: invalid byte length"))?; + ExtendedNielsPoint::from_bytes(&bytes) + .map_err(|_| Error::custom("Failed to deserialize ExtendedNielsPoint: invalid ExtendedNielsPoint")) + } + Err(e) => Err(Error::custom(format!( + "Failed to deserialize ExtendedNielsPoint: {e}" + ))), + } + } +} + +impl Serialize for SubgroupPoint { + fn serialize( + &self, + serializer: S, + ) -> Result { + self.0.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for SubgroupPoint { + fn deserialize>( + deserializer: D, + ) -> Result { + ExtendedPoint::deserialize(deserializer) + .map(|point| SubgroupPoint::from(point)) + } +} diff --git a/tests/points.rs b/tests/points.rs new file mode 100644 index 0000000..4da0ab1 --- /dev/null +++ b/tests/points.rs @@ -0,0 +1,20 @@ +use dusk_bytes::Serializable; +use dusk_jubjub::{AffineNielsPoint, ExtendedNielsPoint}; + +#[test] +fn affine_niels_point_encoding() { + let point = AffineNielsPoint::identity(); + assert_eq!( + point, + AffineNielsPoint::from_bytes(&point.to_bytes()).unwrap() + ); +} + +#[test] +fn extended_niels_point_encoding() { + let point = ExtendedNielsPoint::identity(); + assert_eq!( + point, + ExtendedNielsPoint::from_bytes(&point.to_bytes()).unwrap() + ); +} diff --git a/tests/serde.rs b/tests/serde.rs new file mode 100644 index 0000000..039947a --- /dev/null +++ b/tests/serde.rs @@ -0,0 +1,64 @@ +#[cfg(feature = "serde")] +mod common; + +#[cfg(feature = "serde")] +mod serde { + use dusk_jubjub::{ + AffineNielsPoint, AffinePoint, ExtendedNielsPoint, ExtendedPoint, Fr, + SubgroupPoint, + }; + use group::Group; + + use crate::common::{new_rng, MyRandom}; + + #[test] + fn affine_point() { + let point = AffinePoint::identity(); + let ser = serde_json::to_string(&point).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(point, deser); + } + + #[test] + fn extended_point() { + let mut rng = new_rng(); + let point = ExtendedPoint::random(&mut rng); + let ser = serde_json::to_string(&point).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(point, deser); + } + + #[test] + fn fr() { + let mut rng = new_rng(); + let fr = Fr::new_random(&mut rng); + let ser = serde_json::to_string(&fr).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(fr, deser); + } + + #[test] + fn affine_neils_point() { + let point = AffineNielsPoint::identity(); + let ser = serde_json::to_string(&point).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(point, deser); + } + + #[test] + fn extended_neils_point() { + let point = ExtendedNielsPoint::identity(); + let ser = serde_json::to_string(&point).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(point, deser); + } + + #[test] + fn subgroup_point() { + let mut rng = new_rng(); + let point = SubgroupPoint::random(&mut rng); + let ser = serde_json::to_string(&point).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(point, deser); + } +}