diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/bls12_381.iml b/.idea/bls12_381.iml new file mode 100644 index 00000000..bc2cd874 --- /dev/null +++ b/.idea/bls12_381.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..dd3ae79e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 7d46f832..95e5ad4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ hex = "0.4" rand_xorshift = "0.3" sha2 = "0.9" sha3 = "0.9" +serde_json = "1.0.114" [[bench]] name = "groups" @@ -63,6 +64,19 @@ version = "1.4" default-features = false optional = true +[dependencies.serde] +version = "1.0.197" +default-features = false +optional = true + +[dependencies.serde-big-array] +version = "0.5.1" +optional = true + +[dependencies.hex-conservative] +version = "0.2.0" +optional = true + [features] default = ["groups", "pairings", "alloc", "bits"] bits = ["ff/bits"] @@ -71,3 +85,4 @@ pairings = ["groups", "pairing"] alloc = ["group/alloc"] experimental = ["digest"] nightly = ["subtle/nightly"] +serde_support = ["serde", "serde-big-array", "hex-conservative"] diff --git a/src/lib.rs b/src/lib.rs index cfb6dcc3..c5ba01ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,3 +88,6 @@ pub(crate) use digest::generic_array; #[cfg(feature = "experimental")] pub mod hash_to_curve; + +#[cfg(feature = "serde")] +mod serde; diff --git a/src/serde.rs b/src/serde.rs new file mode 100644 index 00000000..bdde3662 --- /dev/null +++ b/src/serde.rs @@ -0,0 +1,204 @@ +use alloc::string::String; + +use group::Curve; +use hex_conservative::{DisplayHex, FromHex}; +use serde::de::Error; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde_big_array::BigArray; + +use crate::g1::{G1Affine, G1Projective}; +use crate::g2::{G2Affine, G2Projective}; +use crate::scalar::Scalar; + +impl Serialize for Scalar { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + let byte_array = self.to_bytes(); + + if s.is_human_readable() { + s.serialize_str(&DisplayHex::to_lower_hex_string(byte_array.as_slice())) + } else { + Serialize::serialize(&byte_array, s) + } + } +} + +impl<'d> Deserialize<'d> for Scalar { + fn deserialize(d: D) -> Result + where + D: Deserializer<'d>, + { + let byte_array = if d.is_human_readable() { + <[u8; 32] as FromHex>::from_hex(&::deserialize(d)?) + .map_err(serde::de::Error::custom)? + } else { + <[u8; 32] as Deserialize>::deserialize(d)? + }; + + let scalar = Scalar::from_bytes(&byte_array); + + if scalar.is_some().into() { + Ok(scalar.unwrap()) + } else { + Err(D::Error::custom("Could not decode scalar")) + } + } +} + +impl Serialize for G1Affine { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + let byte_array = self.to_compressed(); + + if s.is_human_readable() { + s.serialize_str(&DisplayHex::to_lower_hex_string(byte_array.as_slice())) + } else { + BigArray::serialize(&byte_array, s) + } + } +} + +impl<'d> Deserialize<'d> for G1Affine { + fn deserialize(d: D) -> Result + where + D: Deserializer<'d>, + { + let byte_array = if d.is_human_readable() { + <[u8; 48] as FromHex>::from_hex(&::deserialize(d)?) + .map_err(serde::de::Error::custom)? + } else { + <[u8; 48] as BigArray>::deserialize(d)? + }; + + let g = G1Affine::from_compressed(&byte_array); + + if g.is_some().into() { + Ok(g.unwrap()) + } else { + Err(D::Error::custom( + "Could not decode compressed group element", + )) + } + } +} + +impl Serialize for G2Affine { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + let byte_array = self.to_compressed(); + + if s.is_human_readable() { + s.serialize_str(&DisplayHex::to_lower_hex_string(byte_array.as_slice())) + } else { + BigArray::serialize(&byte_array, s) + } + } +} + +impl<'d> Deserialize<'d> for G2Affine { + fn deserialize(d: D) -> Result + where + D: Deserializer<'d>, + { + let byte_array = if d.is_human_readable() { + <[u8; 96] as FromHex>::from_hex(&::deserialize(d)?) + .map_err(serde::de::Error::custom)? + } else { + <[u8; 96] as BigArray>::deserialize(d)? + }; + + let g = G2Affine::from_compressed(&byte_array); + + if g.is_some().into() { + Ok(g.unwrap()) + } else { + Err(D::Error::custom( + "Could not decode compressed group element", + )) + } + } +} + +impl Serialize for G1Projective { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + self.to_affine().serialize(s) + } +} + +impl<'d> Deserialize<'d> for G1Projective { + fn deserialize(d: D) -> Result + where + D: Deserializer<'d>, + { + Ok(G1Affine::deserialize(d)?.into()) + } +} + +impl Serialize for G2Projective { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + self.to_affine().serialize(s) + } +} + +impl<'d> Deserialize<'d> for G2Projective { + fn deserialize(d: D) -> Result + where + D: Deserializer<'d>, + { + Ok(G2Affine::deserialize(d)?.into()) + } +} + +#[test] +fn serde_json_scalar_roundtrip() { + let serialized = serde_json::to_string(&Scalar::zero()).unwrap(); + + assert_eq!( + serialized, + "\"0000000000000000000000000000000000000000000000000000000000000000\"" + ); + + let deserialized: Scalar = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(deserialized, Scalar::zero()); +} + +#[test] +fn serde_json_g1_roundtrip() { + let serialized = serde_json::to_string(&G1Affine::generator()).unwrap(); + + assert_eq!( + serialized, + "\"97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb\"" + ); + + let deserialized: G1Affine = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(deserialized, G1Affine::generator()); +} + +#[test] +fn serde_json_g2_roundtrip() { + let serialized = serde_json::to_string(&G2Affine::generator()).unwrap(); + + assert_eq!( + serialized, + "\"93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8\"" + ); + + let deserialized: G2Affine = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(deserialized, G2Affine::generator()); +}