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());
+}