diff --git a/.github/workflows/dusk_ci.yml b/.github/workflows/dusk_ci.yml index d2796a6..6c655ab 100644 --- a/.github/workflows/dusk_ci.yml +++ b/.github/workflows/dusk_ci.yml @@ -26,10 +26,10 @@ jobs: name: Nightly std tests uses: dusk-network/.github/.github/workflows/run-tests.yml@main with: - test_flags: --features=rkyv-impl,rkyv/size_16 + test_flags: --features=rkyv-impl,rkyv/size_16,serde test_parallel: name: Nightly std tests parallel uses: dusk-network/.github/.github/workflows/run-tests.yml@main with: - test_flags: --features=parallel,rkyv-impl,rkyv/size_16 + test_flags: --features=parallel,rkyv-impl,rkyv/size_16,serde diff --git a/CHANGELOG.md b/CHANGELOG.md index 119eb25..1515bca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add serde `Serialize` and `Deserialize` implementations for `PublicKey`, `MultisigPublicKey`, `Signature`, +`MultisigSignature` and `SecretKey` [#21] +- Add `serde`, `bs58` and `serde_json` optional dependencies [#21] +- Add `serde` feature [#21] + ## [0.4.0] - 2024-08-01 ### Added @@ -67,6 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add initial commit, this package continues the development of [dusk-bls12_381-sign](https://github.com/dusk-network/bls12_381-sign/) at version `0.6.0` under the new name: `bls12_381-bls` and without the go related code. +[#21]: https://github.com/dusk-network/bls12_381-bls/issues/21 [#18]: https://github.com/dusk-network/bls12_381-bls/issues/18 [#8]: https://github.com/dusk-network/bls12_381-bls/issues/8 [#7]: https://github.com/dusk-network/bls12_381-bls/issues/7 diff --git a/Cargo.toml b/Cargo.toml index e072e9d..a5b013d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,9 @@ zeroize = { version = "1", default-features = false, features = ["derive"] } rkyv = { version = "0.7", optional = true, default-features = false } bytecheck = { version = "0.6", optional = true, default-features = false } rayon = { version = "1.8", optional = true } +serde = { version = "1.0", optional = true } +bs58 = { version = "0.4" , optional = true } +serde_json = { version = "1.0", optional = true } [dev-dependencies] rand = "0.8" @@ -34,3 +37,4 @@ rkyv-impl = [ "bytecheck", ] parallel = ["dep:rayon"] +serde = ["dep:serde", "bs58", "serde_json"] diff --git a/src/lib.rs b/src/lib.rs index f71e1f8..89c77c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,9 @@ pub use keys::{ }; pub use signatures::{MultisigSignature, Signature}; +#[cfg(feature = "serde")] +mod serde_support; + #[cfg(feature = "rkyv-impl")] pub use crate::keys::{ public::{ diff --git a/src/serde_support.rs b/src/serde_support.rs new file mode 100644 index 0000000..a0630cf --- /dev/null +++ b/src/serde_support.rs @@ -0,0 +1,154 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +extern crate alloc; + +use alloc::format; +use alloc::string::String; + +use bs58; +use dusk_bytes::Serializable; +use serde::de::Error as SerdeError; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::{ + MultisigPublicKey, MultisigSignature, PublicKey, SecretKey, Signature, +}; + +impl Serialize for PublicKey { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = bs58::encode(self.to_bytes()).into_string(); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + let decoded = + bs58::decode(&s).into_vec().map_err(SerdeError::custom)?; + let decoded_len = decoded.len(); + let byte_length_str = format!("{}", Self::SIZE); + let bytes: [u8; Self::SIZE] = decoded.try_into().map_err(|_| { + SerdeError::invalid_length(decoded_len, &byte_length_str.as_str()) + })?; + PublicKey::from_bytes(&bytes) + .map_err(|err| SerdeError::custom(format!("{err:?}"))) + } +} + +impl Serialize for MultisigPublicKey { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = bs58::encode(self.to_bytes()).into_string(); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for MultisigPublicKey { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + let decoded = + bs58::decode(&s).into_vec().map_err(SerdeError::custom)?; + let decoded_len = decoded.len(); + let byte_length_str = format!("{}", Self::SIZE); + let bytes: [u8; Self::SIZE] = decoded.try_into().map_err(|_| { + SerdeError::invalid_length(decoded_len, &byte_length_str.as_str()) + })?; + MultisigPublicKey::from_bytes(&bytes) + .map_err(|err| SerdeError::custom(format!("{err:?}"))) + } +} + +impl Serialize for Signature { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = bs58::encode(self.to_bytes()).into_string(); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for Signature { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + let decoded = + bs58::decode(&s).into_vec().map_err(SerdeError::custom)?; + let decoded_len = decoded.len(); + let byte_length_str = format!("{}", Self::SIZE); + let bytes: [u8; Self::SIZE] = decoded.try_into().map_err(|_| { + SerdeError::invalid_length(decoded_len, &byte_length_str.as_str()) + })?; + Signature::from_bytes(&bytes) + .map_err(|err| SerdeError::custom(format!("{err:?}"))) + } +} + +impl Serialize for MultisigSignature { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = bs58::encode(self.to_bytes()).into_string(); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for MultisigSignature { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + let decoded = + bs58::decode(&s).into_vec().map_err(SerdeError::custom)?; + let decoded_len = decoded.len(); + let byte_length_str = format!("{}", Self::SIZE); + let bytes: [u8; Self::SIZE] = decoded.try_into().map_err(|_| { + SerdeError::invalid_length(decoded_len, &byte_length_str.as_str()) + })?; + MultisigSignature::from_bytes(&bytes) + .map_err(|err| SerdeError::custom(format!("{err:?}"))) + } +} + +impl Serialize for SecretKey { + fn serialize( + &self, + serializer: S, + ) -> Result { + let s = bs58::encode(self.to_bytes()).into_string(); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for SecretKey { + fn deserialize>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + let decoded = + bs58::decode(&s).into_vec().map_err(SerdeError::custom)?; + let decoded_len = decoded.len(); + let byte_length_str = format!("{}", Self::SIZE); + let bytes: [u8; Self::SIZE] = decoded.try_into().map_err(|_| { + SerdeError::invalid_length(decoded_len, &byte_length_str.as_str()) + })?; + SecretKey::from_bytes(&bytes) + .map_err(|err| SerdeError::custom(format!("{err:?}"))) + } +} diff --git a/tests/serde.rs b/tests/serde.rs new file mode 100644 index 0000000..3f14442 --- /dev/null +++ b/tests/serde.rs @@ -0,0 +1,134 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +#![cfg(feature = "serde")] + +use bls12_381_bls::{ + MultisigPublicKey, MultisigSignature, PublicKey, SecretKey, Signature, +}; +use rand::rngs::StdRng; +use rand::SeedableRng; + +#[test] +fn public_key() { + let mut rng = StdRng::seed_from_u64(0xbeef); + let pk = PublicKey::from(&SecretKey::random(&mut rng)); + let ser = serde_json::to_string(&pk); + let deser = serde_json::from_str(&ser.unwrap()); + assert_eq!(pk, deser.unwrap()); +} + +#[test] +fn multisig_public_key() { + let mut rng = StdRng::seed_from_u64(0xbeef); + let pk = MultisigPublicKey::aggregate(&[PublicKey::from( + &SecretKey::random(&mut rng), + )]) + .unwrap(); + let ser = serde_json::to_string(&pk); + let deser = serde_json::from_str(&ser.unwrap()); + assert_eq!(pk, deser.unwrap()); +} + +#[test] +fn signature() { + let mut rng = StdRng::seed_from_u64(0xbeef); + let sk = SecretKey::random(&mut rng); + let signature = sk.sign(b"a message"); + let ser = serde_json::to_string(&signature).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(signature, deser); +} + +#[test] +fn multisig_signature() { + let mut rng = StdRng::seed_from_u64(0xbeef); + let sk = SecretKey::random(&mut rng); + let pk = PublicKey::from(&sk); + let signature = sk.sign_multisig(&pk, b"a message"); + let ser = serde_json::to_string(&signature).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(signature, deser); +} + +#[test] +fn secret_key() { + let mut rng = StdRng::seed_from_u64(0xbeef); + let sk = SecretKey::random(&mut rng); + let ser = serde_json::to_string(&sk).unwrap(); + let deser = serde_json::from_str(&ser).unwrap(); + assert_eq!(sk, deser); +} + +#[test] +fn wrong_encoded() { + let wrong_encoded = "wrong-encoded"; + let public_key: Result = serde_json::from_str(&wrong_encoded); + assert!(public_key.is_err()); + + let secret_key: Result = serde_json::from_str(&wrong_encoded); + assert!(secret_key.is_err()); + + let signature: Result = serde_json::from_str(&wrong_encoded); + assert!(signature.is_err()); + + let public_key_double: Result = + serde_json::from_str(&wrong_encoded); + assert!(public_key_double.is_err()); + + let signature_double: Result = + serde_json::from_str(&wrong_encoded); + assert!(signature_double.is_err()); +} + +#[test] +fn too_long_encoded() { + let length_33_enc = "\"yaujE5CNg7SRYuf3Vw7G8QQdM7267QxJtfqGUEjLbxyCC\""; + let length_49_enc= "\"RCR6kPYZDuew8ovT9MoxVv7mKRsbygumf2UTjvzs6AJhnukLj3BiFvjaE45Q41tKqdA\""; + let length_97_enc = "\"7a5RpCdtr1aaXvaR3AofnEnVRh7kpzyqE8eYJpCBVLKLLpXVeN9UrXGRTZyq2upTVaJT5QnPQwZCGXW1oxrEAzrPvQ4vbWFwiHMJijZMzrPsTjQJFju1H4shrajuqUG4fYFpC\""; + + let public_key: Result = serde_json::from_str(&length_97_enc); + assert!(public_key.is_err()); + + let secret_key: Result = serde_json::from_str(&length_33_enc); + assert!(secret_key.is_err()); + + let signature: Result = serde_json::from_str(&length_49_enc); + assert!(signature.is_err()); + + let multisig_public_key: Result = + serde_json::from_str(&length_97_enc); + assert!(multisig_public_key.is_err()); + + let multisig_signature: Result = + serde_json::from_str(&length_49_enc); + assert!(multisig_signature.is_err()); +} + +#[test] +fn too_short_encoded() { + let length_31_enc = "\"3uTp29S3e2HQBekFYvVwsmoeEzk4uVWwQUjvJPwWKwU\""; + let length_47_enc = + "\"2F3DDEDEuxrszs3JfzFq51tnGNm3ZtrHwa7sAA4pkeo1JkqGTEYudnBZLNAkCohAd\""; + let length_95_enc = "\"LZXkPWnz5xKxYnyDRZyJvL9vF44oQynzozqRBcpgWA3yZicbaxNeKKJrAMv3eXBbyEvk24mgz9Kg9tck5yEW6k16chN4hDWYUr5gDb9PJJ3YmUqcjG8yPaAuz3cNCE8dHv\""; + + let public_key: Result = serde_json::from_str(&length_95_enc); + assert!(public_key.is_err()); + + let secret_key: Result = serde_json::from_str(&length_31_enc); + assert!(secret_key.is_err()); + + let signature: Result = serde_json::from_str(&length_47_enc); + assert!(signature.is_err()); + + let multisig_public_key: Result = + serde_json::from_str(&length_95_enc); + assert!(multisig_public_key.is_err()); + + let multisig_signature: Result = + serde_json::from_str(&length_47_enc); + assert!(multisig_signature.is_err()); +}