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
This commit is contained in:
Dan Cline 2023-08-24 23:39:46 -04:00 committed by GitHub
parent 4c0d477c0f
commit 666a9de002
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 209 additions and 9 deletions

View File

@ -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",

View File

@ -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" }

View File

@ -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;

View File

@ -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<S, T>(x: T, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: AsRef<[u8]>,
{
s.serialize_str(&format!("0x{}", hex::encode(x.as_ref())))
}
impl Serialize for Blob {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_bytes(self.bytes, serializer)
}
}
impl<'de> Deserialize<'de> for Blob {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_bytes(self.bytes, serializer)
}
}
impl<'de> Deserialize<'de> for Bytes48 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_bytes(self.bytes, serializer)
}
}
impl<'de> Deserialize<'de> for Bytes32 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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(), '"');
}
}