From 52e9c698fb95cd9756f974fdceddcafcd05371f1 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 +- CHANGELOG.md | 8 +++ Cargo.toml | 16 ++++++ src/dusk.rs | 3 ++ src/dusk/serde_support.rs | 98 +++++++++++++++++++++++++++++++++++ src/fr/dusk.rs | 68 ++++++++++++++++++++++++ 6 files changed, 194 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/CHANGELOG.md b/CHANGELOG.md index a0cbbb1..3f67f09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add serde `Serialize` and `Deserialize` implementations for `Fr`, `AffinePoint` and `ExtendedPoint` [#143] +- Add `serde`, `hex` and `serde_json` optional dependencies [#143] +- Add `serde` feature [#143] + ## [0.14.1] - 2024-04-24 ### Added @@ -222,6 +228,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Initial fork from [`zkcrypto/jubjub`] +[#143]: https://github.com/dusk-network/jubjub/issues/143 [#137]: https://github.com/dusk-network/jubjub/issues/137 [#135]: https://github.com/dusk-network/jubjub/issues/135 [#129]: https://github.com/dusk-network/jubjub/issues/129 @@ -244,6 +251,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#31]: https://github.com/dusk-network/jubjub/issues/31 [#25]: https://github.com/dusk-network/jubjub/issues/25 + [Unreleased]: https://github.com/dusk-network/jubjub/compare/v0.14.1...HEAD [0.14.1]: https://github.com/dusk-network/jubjub/compare/v0.14.0...v0.14.1 diff --git a/Cargo.toml b/Cargo.toml index 4a3645e..2e8147c 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] @@ -81,11 +93,15 @@ default-features = false [dev-dependencies.blake2] version = "0.9" +[dev-dependencies.rand] +version = "0.8" + [features] 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..8168520 --- /dev/null +++ b/src/dusk/serde_support.rs @@ -0,0 +1,98 @@ +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 rand::rngs::StdRng; + use rand::SeedableRng; + + use crate::{AffinePoint, ExtendedPoint}; + + #[test] + fn serde_affine_point() { + let mut rng = StdRng::seed_from_u64(0xdead); + let point = ExtendedPoint::random(&mut rng); + let point = AffinePoint::from(point); + let ser = serde_json::to_string(&point).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(point, deser); + } + + #[test] + fn serde_wrong_encoded() { + let wrong_encoded = "wrong-encoded"; + + let affine_point: Result = + serde_json::from_str(&wrong_encoded); + assert!(affine_point.is_err()); + } + + #[test] + fn serde_too_long_encoded() { + let length_33_enc = "\"e4ab9de40283a85d6ea0cd0120500697d8b01c71b7b4b520292252d20937000631\""; + + let affine_point: Result = + serde_json::from_str(&length_33_enc); + assert!(affine_point.is_err()); + } + + #[test] + fn serde_too_short_encoded() { + let length_31_enc = "\"1751c37a1dca7aa4c048fcc6177194243edc3637bae042e167e4285945e046\""; + + let affine_point: Result = + serde_json::from_str(&length_31_enc); + assert!(affine_point.is_err()); + } +} diff --git a/src/fr/dusk.rs b/src/fr/dusk.rs index 3be2f1c..9b8b940 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,33 @@ fn test_zeroize() { scalar.zeroize(); assert_eq!(scalar, Fr::zero()); } + +#[cfg(feature = "serde")] +#[test] +fn serde_fr() { + use ff::Field; + use rand::rngs::StdRng; + use rand::SeedableRng; + + let mut rng = StdRng::seed_from_u64(0xdead); + 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()); +}