From 666a9de002035eb7e929bceee3a70dee1b23aa93 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 24 Aug 2023 23:39:46 -0400 Subject: [PATCH] feat: add serde impls to Blob and Bytes48 (#342) * feat: add serde impls to Blob and Bytes48 * remove duplicate serde file * cargo fmt * serialize and deserialize with prefix * cargo fmt * use different trusted setup based on minimal spec * add Bytes32 serde impls --- bindings/rust/Cargo.lock | 5 +- bindings/rust/Cargo.toml | 1 + bindings/rust/src/bindings/mod.rs | 19 +- bindings/rust/src/bindings/serde_helpers.rs | 193 ++++++++++++++++++++ 4 files changed, 209 insertions(+), 9 deletions(-) create mode 100644 bindings/rust/src/bindings/serde_helpers.rs diff --git a/bindings/rust/Cargo.lock b/bindings/rust/Cargo.lock index 5e563a5..10984e0 100644 --- a/bindings/rust/Cargo.lock +++ b/bindings/rust/Cargo.lock @@ -93,6 +93,7 @@ dependencies = [ "libc", "rand", "serde", + "serde_json", "serde_yaml", ] @@ -690,9 +691,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index 4a082cd..4b42793 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -31,6 +31,7 @@ criterion = "0.5.1" glob = "0.3.1" rand = "0.8.5" serde_yaml = "0.9.17" +serde_json = "1.0.105" [build-dependencies] bindgen = { git = "https://github.com/rust-lang/rust-bindgen" , rev = "0de11f0a521611ac8738b7b01d19dddaf3899e66" } diff --git a/bindings/rust/src/bindings/mod.rs b/bindings/rust/src/bindings/mod.rs index 534d304..e79e290 100644 --- a/bindings/rust/src/bindings/mod.rs +++ b/bindings/rust/src/bindings/mod.rs @@ -2,6 +2,7 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] +mod serde_helpers; mod test_formats; include!(concat!(env!("OUT_DIR"), "/generated.rs")); @@ -492,6 +493,12 @@ impl Deref for Bytes48 { } } +impl DerefMut for Bytes48 { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.bytes + } +} + impl Deref for Blob { type Target = [u8; BYTES_PER_BLOB]; fn deref(&self) -> &Self::Target { @@ -693,10 +700,8 @@ mod tests { for test_file in test_files { let yaml_data = fs::read_to_string(test_file).unwrap(); let test: compute_blob_kzg_proof::Test = serde_yaml::from_str(&yaml_data).unwrap(); - let (Ok(blob), Ok(commitment)) = ( - test.input.get_blob(), - test.input.get_commitment() - ) else { + let (Ok(blob), Ok(commitment)) = (test.input.get_blob(), test.input.get_commitment()) + else { assert!(test.get_output().is_none()); continue; }; @@ -727,7 +732,7 @@ mod tests { test.input.get_commitment(), test.input.get_z(), test.input.get_y(), - test.input.get_proof() + test.input.get_proof(), ) else { assert!(test.get_output().is_none()); continue; @@ -758,7 +763,7 @@ mod tests { let (Ok(blob), Ok(commitment), Ok(proof)) = ( test.input.get_blob(), test.input.get_commitment(), - test.input.get_proof() + test.input.get_proof(), ) else { assert!(test.get_output().is_none()); continue; @@ -789,7 +794,7 @@ mod tests { let (Ok(blobs), Ok(commitments), Ok(proofs)) = ( test.input.get_blobs(), test.input.get_commitments(), - test.input.get_proofs() + test.input.get_proofs(), ) else { assert!(test.get_output().is_none()); continue; diff --git a/bindings/rust/src/bindings/serde_helpers.rs b/bindings/rust/src/bindings/serde_helpers.rs new file mode 100644 index 0000000..8f63c71 --- /dev/null +++ b/bindings/rust/src/bindings/serde_helpers.rs @@ -0,0 +1,193 @@ +//! Serde serialization and deserialization for the basic types in this crate. +use crate::{Blob, Bytes32, Bytes48}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// Serialize a byte vec as a hex string with 0x prefix +pub fn serialize_bytes(x: T, s: S) -> Result +where + S: Serializer, + T: AsRef<[u8]>, +{ + s.serialize_str(&format!("0x{}", hex::encode(x.as_ref()))) +} + +impl Serialize for Blob { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serialize_bytes(self.bytes, serializer) + } +} + +impl<'de> Deserialize<'de> for Blob { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + let bytes_res = match value.strip_prefix("0x") { + Some(value) => hex::decode(value), + None => hex::decode(&value), + }; + + let bytes = bytes_res.map_err(|e| serde::de::Error::custom(e.to_string()))?; + Blob::from_bytes(bytes.as_slice()).map_err(|e| serde::de::Error::custom(format!("{:?}", e))) + } +} + +impl Serialize for Bytes48 { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serialize_bytes(self.bytes, serializer) + } +} + +impl<'de> Deserialize<'de> for Bytes48 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + let bytes_res = match value.strip_prefix("0x") { + Some(value) => hex::decode(value), + None => hex::decode(&value), + }; + + let bytes = bytes_res.map_err(|e| serde::de::Error::custom(e.to_string()))?; + Bytes48::from_bytes(bytes.as_slice()) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) + } +} + +impl Serialize for Bytes32 { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serialize_bytes(self.bytes, serializer) + } +} + +impl<'de> Deserialize<'de> for Bytes32 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + let bytes_res = match value.strip_prefix("0x") { + Some(value) => hex::decode(value), + None => hex::decode(&value), + }; + + let bytes = bytes_res.map_err(|e| serde::de::Error::custom(e.to_string()))?; + Bytes32::from_bytes(bytes.as_slice()) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) + } +} + +#[cfg(test)] +mod tests { + use super::super::*; + use rand::{rngs::ThreadRng, Rng}; + use std::path::PathBuf; + + fn generate_random_blob(rng: &mut ThreadRng) -> Blob { + let mut arr = [0u8; BYTES_PER_BLOB]; + rng.fill(&mut arr[..]); + // Ensure that the blob is canonical by ensuring that + // each field element contained in the blob is < BLS_MODULUS + for i in 0..FIELD_ELEMENTS_PER_BLOB { + arr[i * BYTES_PER_FIELD_ELEMENT] = 0; + } + arr.into() + } + + fn trusted_setup_file() -> PathBuf { + if cfg!(feature = "minimal-spec") { + PathBuf::from("../../src/trusted_setup_4.txt") + } else { + PathBuf::from("../../src/trusted_setup.txt") + } + } + + #[test] + fn test_serialize_roundtrip() { + // load setup so we can create commitments and blobs + let trusted_setup_file = trusted_setup_file(); + assert!(trusted_setup_file.exists()); + let kzg_settings = KZGSettings::load_trusted_setup_file(trusted_setup_file).unwrap(); + + // generate blob, commitment, proof + let mut rng = rand::thread_rng(); + let blob = generate_random_blob(&mut rng); + let commitment = + KZGCommitment::blob_to_kzg_commitment(blob.clone(), &kzg_settings).unwrap(); + let proof = + KZGProof::compute_blob_kzg_proof(blob.clone(), commitment.to_bytes(), &kzg_settings) + .unwrap(); + + // check blob serialization + let blob_serialized = serde_json::to_string(&blob).unwrap(); + let blob_deserialized: Blob = serde_json::from_str(&blob_serialized).unwrap(); + assert_eq!(blob, blob_deserialized); + + // check commitment serialization + let commitment_serialized = serde_json::to_string(&commitment.to_bytes()).unwrap(); + let commitment_deserialized: Bytes48 = + serde_json::from_str(&commitment_serialized).unwrap(); + assert_eq!(commitment.to_bytes(), commitment_deserialized); + + // check proof serialization + let proof_serialized = serde_json::to_string(&proof.to_bytes()).unwrap(); + let proof_deserialized: Bytes48 = serde_json::from_str(&proof_serialized).unwrap(); + assert_eq!(proof.to_bytes(), proof_deserialized); + } + + #[test] + fn test_serialize_blob_with_prefix() { + // generate blob + let mut rng = rand::thread_rng(); + let blob = generate_random_blob(&mut rng); + + // check blob serialization + let blob_serialized = serde_json::to_string(&blob).unwrap(); + + // check that this begins with a quote and 0x + let mut chars = blob_serialized.chars(); + assert_eq!(chars.next().unwrap(), '"'); + assert_eq!(chars.next().unwrap(), '0'); + assert_eq!(chars.next().unwrap(), 'x'); + + // check that it ends with a quote (sanity check) + assert_eq!(chars.last().unwrap(), '"'); + } + + #[test] + fn test_serialize_bytes_48_with_prefix() { + // load setup so we can create a commitments + let trusted_setup_file = trusted_setup_file(); + assert!(trusted_setup_file.exists()); + let kzg_settings = KZGSettings::load_trusted_setup_file(trusted_setup_file).unwrap(); + + // generate blob just to calculate a commitment + let mut rng = rand::thread_rng(); + let blob = generate_random_blob(&mut rng); + let commitment = + KZGCommitment::blob_to_kzg_commitment(blob.clone(), &kzg_settings).unwrap(); + + // check blob serialization + let blob_serialized = serde_json::to_string(&commitment.to_bytes()).unwrap(); + + // check that this begins with a quote and 0x + let mut chars = blob_serialized.chars(); + assert_eq!(chars.next().unwrap(), '"'); + assert_eq!(chars.next().unwrap(), '0'); + assert_eq!(chars.next().unwrap(), 'x'); + + // check that it ends with a quote (sanity check) + assert_eq!(chars.last().unwrap(), '"'); + } +}