From 6dc2c2a67f18c0ecb7f5d04d1ddb622664f0e5e4 Mon Sep 17 00:00:00 2001 From: Demilade Sonuga Date: Fri, 6 Dec 2024 13:47:33 +0100 Subject: [PATCH] Added serde feature --- .github/workflows/dusk_ci.yml | 2 +- Cargo.toml | 13 ++++ src/dusk.rs | 3 + src/dusk/serde_support.rs | 115 ++++++++++++++++++++++++++++++++++ src/fr/dusk.rs | 66 +++++++++++++++++++ 5 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 src/dusk/serde_support.rs diff --git a/.github/workflows/dusk_ci.yml b/.github/workflows/dusk_ci.yml index 86b5b79..02ac233 100644 --- a/.github/workflows/dusk_ci.yml +++ b/.github/workflows/dusk_ci.yml @@ -19,7 +19,7 @@ jobs: name: Nightly tests std uses: dusk-network/.github/.github/workflows/run-tests.yml@main with: - test_flags: --features=zeroize + test_flags: --features=zeroize,serde test_nightly_no_std: name: Nightly tests no_std diff --git a/Cargo.toml b/Cargo.toml index 4a3645e..c9193a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,18 @@ default-features = false version = "1" optional = true default-features = false + +[dependencies.serde] +version = "1.0" +optional = true + +[dependencies.serde_json] +version = "1.0" +optional = true + +[dependencies.hex] +version = "0.4" +optional = true # End Dusk dependendencies [dev-dependencies] @@ -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", "hex"] [[bench]] name = "fq_bench" diff --git a/src/dusk.rs b/src/dusk.rs index f311142..84d2ead 100644 --- a/src/dusk.rs +++ b/src/dusk.rs @@ -7,6 +7,9 @@ #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "serde")] +mod serde_support; + use core::ops::Mul; use ff::Field; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; diff --git a/src/dusk/serde_support.rs b/src/dusk/serde_support.rs new file mode 100644 index 0000000..a1df0c4 --- /dev/null +++ b/src/dusk/serde_support.rs @@ -0,0 +1,115 @@ +extern crate alloc; + +use alloc::string::String; + +use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; + +use crate::{AffinePoint, ExtendedPoint}; + +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)?; + let decoded = hex::decode(&s).map_err(Error::custom)?; + let decoded_len = decoded.len(); + let bytes: [u8; 32] = decoded + .try_into() + .map_err(|_| Error::invalid_length(decoded_len, &"32"))?; + AffinePoint::from_bytes(bytes) + .into_option() + .ok_or(Error::custom( + "Failed to deserialize AffinePoint: invalid AffinePoint", + )) + } +} + +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(Into::into) + } +} + +#[cfg(test)] +mod tests { + use group::Group; + + use crate::{AffinePoint, ExtendedPoint}; + + #[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 = rand_core::OsRng; + 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 wrong_encoded() { + let wrong_encoded = "wrong-encoded"; + + let affine_point: Result = + serde_json::from_str(&wrong_encoded); + assert!(affine_point.is_err()); + + let extended_point: Result = + serde_json::from_str(&wrong_encoded); + assert!(extended_point.is_err()); + } + + #[test] + fn too_long_encoded() { + let length_33_enc = "\"e4ab9de40283a85d6ea0cd0120500697d8b01c71b7b4b520292252d20937000631\""; + + let affine_point: Result = + serde_json::from_str(&length_33_enc); + assert!(affine_point.is_err()); + + let extended_point: Result = + serde_json::from_str(&length_33_enc); + assert!(extended_point.is_err()); + } + + #[test] + fn too_short_encoded() { + let length_31_enc = "\"1751c37a1dca7aa4c048fcc6177194243edc3637bae042e167e4285945e046\""; + + let affine_point: Result = + serde_json::from_str(&length_31_enc); + assert!(affine_point.is_err()); + + let extended_point: Result = + serde_json::from_str(&length_31_enc); + assert!(extended_point.is_err()); + } +} diff --git a/src/fr/dusk.rs b/src/fr/dusk.rs index 3be2f1c..de3ec4d 100644 --- a/src/fr/dusk.rs +++ b/src/fr/dusk.rs @@ -275,6 +275,44 @@ impl Serializable<32> for Fr { } } +#[cfg(feature = "serde")] +mod serde_support { + extern crate alloc; + + use alloc::string::String; + + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use super::Fr; + + 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)?; + let decoded = hex::decode(s).map_err(Error::custom)?; + let decoded_len = decoded.len(); + let bytes: [u8; 32] = decoded + .try_into() + .map_err(|_| Error::invalid_length(decoded_len, &"32"))?; + Fr::from_bytes(&bytes) + .into_option() + .ok_or(Error::custom("Failed to deserialize Fr: invalid Fr")) + } + } +} + #[test] fn w_naf_3() { let scalar = Fr::from(1122334455u64); @@ -366,3 +404,31 @@ fn test_zeroize() { scalar.zeroize(); assert_eq!(scalar, Fr::zero()); } + +#[cfg(feature = "serde")] +#[test] +fn serde_fr() { + use ff::Field; + + let mut rng = rand_core::OsRng; + let fr = Fr::random(&mut rng); + let ser = serde_json::to_string(&fr).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(fr, deser); + + // Should error when the encoding is wrong + let wrong_encoded = "wrong-encoded"; + let fr: Result = serde_json::from_str(&wrong_encoded); + assert!(fr.is_err()); + + // Should error when the input is too long + let length_33_enc = "\"e4ab9de40283a85d6ea0cd0120500697d8b01c71b7b4b520292252d20937000631\""; + let fr: Result = serde_json::from_str(&length_33_enc); + assert!(fr.is_err()); + + // Should error when the input is too short + let length_31_enc = + "\"1751c37a1dca7aa4c048fcc6177194243edc3637bae042e167e4285945e046\""; + let fr: Result = serde_json::from_str(&length_31_enc); + assert!(fr.is_err()); +}