Skip to content

Commit

Permalink
add basic serde support for Scalar, G1, G2 with human readable encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
joschisan committed Mar 28, 2024
1 parent 4df4518 commit 0fc9d7d
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/bls12_381.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"]
Expand All @@ -71,3 +85,4 @@ pairings = ["groups", "pairing"]
alloc = ["group/alloc"]
experimental = ["digest"]
nightly = ["subtle/nightly"]
serde = ["dep:serde", "serde-big-array", "hex-conservative"]
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,6 @@ pub(crate) use digest::generic_array;

#[cfg(feature = "experimental")]
pub mod hash_to_curve;

#[cfg(feature = "serde")]
mod serde;
187 changes: 187 additions & 0 deletions src/serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
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<S>(&self, s: S) -> Result<S::Ok, S::Error>
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: D) -> Result<Self, D::Error>
where
D: Deserializer<'d>,
{
let byte_array = if d.is_human_readable() {
<[u8; 32] as FromHex>::from_hex(&<String>::deserialize(d)?)
.map_err(serde::de::Error::custom)?
} else {
<[u8; 32] as Deserialize>::deserialize(d)?
};

Option::from(Scalar::from_bytes(&byte_array))
.ok_or(D::Error::custom("Could not decode scalar"))
}
}

impl Serialize for G1Affine {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
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: D) -> Result<Self, D::Error>
where
D: Deserializer<'d>,
{
let byte_array = if d.is_human_readable() {
<[u8; 48] as FromHex>::from_hex(&<String>::deserialize(d)?)
.map_err(serde::de::Error::custom)?
} else {
<[u8; 48] as BigArray<u8>>::deserialize(d)?
};

Option::from(G1Affine::from_compressed(&byte_array)).ok_or(D::Error::custom(
"Could not decode compressed group element",
))
}
}

impl Serialize for G2Affine {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
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: D) -> Result<Self, D::Error>
where
D: Deserializer<'d>,
{
let byte_array = if d.is_human_readable() {
<[u8; 96] as FromHex>::from_hex(&<String>::deserialize(d)?)
.map_err(serde::de::Error::custom)?
} else {
<[u8; 96] as BigArray<u8>>::deserialize(d)?
};

Option::from(G2Affine::from_compressed(&byte_array)).ok_or(D::Error::custom(
"Could not decode compressed group element",
))
}
}

impl Serialize for G1Projective {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_affine().serialize(s)
}
}

impl<'d> Deserialize<'d> for G1Projective {
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where
D: Deserializer<'d>,
{
Ok(G1Affine::deserialize(d)?.into())
}
}

impl Serialize for G2Projective {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_affine().serialize(s)
}
}

impl<'d> Deserialize<'d> for G2Projective {
fn deserialize<D>(d: D) -> Result<Self, D::Error>
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());
}

0 comments on commit 0fc9d7d

Please sign in to comment.