Skip to content

Commit

Permalink
feat: Blob Tx Sidecar Iterator (alloy-rs#1334)
Browse files Browse the repository at this point in the history
feat: blob tx sidecar iterator
  • Loading branch information
refcell authored and lwedge99 committed Oct 8, 2024
1 parent 35d3452 commit e5e17c2
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 19 deletions.
14 changes: 14 additions & 0 deletions crates/eips/src/eip4844/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ pub const BYTES_PER_PROOF: usize = 48;
/// A Blob serialized as 0x-prefixed hex string
pub type Blob = FixedBytes<BYTES_PER_BLOB>;

/// Helper function to deserialize boxed blobs.
#[cfg(feature = "serde")]
pub fn deserialize_blob<'de, D>(deserializer: D) -> Result<alloc::boxed::Box<Blob>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
use serde::Deserialize;
let raw_blob = <alloy_primitives::Bytes>::deserialize(deserializer)?;
let blob = alloc::boxed::Box::new(
Blob::try_from(raw_blob.as_ref()).map_err(serde::de::Error::custom)?,
);
Ok(blob)
}

/// A commitment/proof serialized as 0x-prefixed hex string
pub type Bytes48 = FixedBytes<48>;

Expand Down
101 changes: 96 additions & 5 deletions crates/eips/src/eip4844/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use crate::eip4844::{
kzg_to_versioned_hash, Blob, Bytes48, BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_PROOF,
};
use alloc::boxed::Box;
use alloy_primitives::{bytes::BufMut, B256};
use alloy_rlp::{Decodable, Encodable};

Expand All @@ -12,6 +13,10 @@ use crate::eip4844::MAX_BLOBS_PER_BLOCK;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

/// The versioned hash version for KZG.
#[cfg(feature = "kzg")]
pub(crate) const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;

/// This represents a set of blobs, and its corresponding commitments and proofs.
///
/// This type encodes and decodes the fields without an rlp header.
Expand All @@ -32,6 +37,96 @@ pub struct BlobTransactionSidecar {
pub proofs: Vec<Bytes48>,
}

impl IntoIterator for BlobTransactionSidecar {
type Item = BlobTransactionSidecarItem;
type IntoIter = alloc::vec::IntoIter<BlobTransactionSidecarItem>;

fn into_iter(self) -> Self::IntoIter {
self.blobs
.into_iter()
.zip(self.commitments)
.zip(self.proofs)
.enumerate()
.map(|(index, ((blob, commitment), proof))| BlobTransactionSidecarItem {
index,
blob: Box::new(blob),
kzg_commitment: commitment,
kzg_proof: proof,
})
.collect::<Vec<_>>()
.into_iter()
}
}

/// A single blob sidecar.
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[repr(C)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlobTransactionSidecarItem {
/// The index of this item within the [BlobTransactionSidecar].
pub index: usize,
/// The blob in this sidecar item.
#[cfg_attr(feature = "serde", serde(deserialize_with = "super::deserialize_blob"))]
pub blob: Box<Blob>,
/// The KZG commitment.
pub kzg_commitment: Bytes48,
/// The KZG proof.
pub kzg_proof: Bytes48,
}

#[cfg(feature = "kzg")]
impl BlobTransactionSidecarItem {
/// `VERSIONED_HASH_VERSION_KZG ++ sha256(commitment)[1..]`
pub fn to_kzg_versioned_hash(&self) -> [u8; 32] {
use sha2::Digest;
let commitment = self.kzg_commitment.as_slice();
let mut hash: [u8; 32] = sha2::Sha256::digest(commitment).into();
hash[0] = VERSIONED_HASH_VERSION_KZG;
hash
}

/// Verifies the KZG proof of a blob to ensure its integrity and correctness.
pub fn verify_blob_kzg_proof(&self) -> Result<(), BlobTransactionValidationError> {
let binding = crate::eip4844::env_settings::EnvKzgSettings::Default;
let settings = binding.get();

let blob = c_kzg::Blob::from_bytes(self.blob.as_slice())
.map_err(BlobTransactionValidationError::KZGError)?;

let commitment = c_kzg::Bytes48::from_bytes(self.kzg_commitment.as_slice())
.map_err(BlobTransactionValidationError::KZGError)?;

let proof = c_kzg::Bytes48::from_bytes(self.kzg_proof.as_slice())
.map_err(BlobTransactionValidationError::KZGError)?;

let result = c_kzg::KzgProof::verify_blob_kzg_proof(&blob, &commitment, &proof, settings)
.map_err(BlobTransactionValidationError::KZGError)?;

result.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
}

/// Verify the blob sidecar against its [crate::NumHash].
pub fn verify_blob(&self, hash: &crate::NumHash) -> Result<(), BlobTransactionValidationError> {
if self.index != hash.number as usize {
let blob_hash_part = B256::from_slice(&self.blob[0..32]);
return Err(BlobTransactionValidationError::WrongVersionedHash {
have: blob_hash_part,
expected: hash.hash,
});
}

let computed_hash = self.to_kzg_versioned_hash();
if computed_hash != hash.hash {
return Err(BlobTransactionValidationError::WrongVersionedHash {
have: computed_hash.into(),
expected: hash.hash,
});
}

self.verify_blob_kzg_proof()
}
}

#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for BlobTransactionSidecar {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Expand Down Expand Up @@ -139,11 +234,7 @@ impl BlobTransactionSidecar {
}
.map_err(BlobTransactionValidationError::KZGError)?;

if res {
Ok(())
} else {
Err(BlobTransactionValidationError::InvalidProof)
}
res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
}

/// Returns an iterator over the versioned hashes of the commitments.
Expand Down
16 changes: 2 additions & 14 deletions crates/rpc-types-beacon/src/sidecar.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::header::Header;
use alloy_eips::eip4844::{Blob, BlobTransactionSidecar, Bytes48};
use alloy_primitives::{Bytes, B256};
use alloy_eips::eip4844::{deserialize_blob, Blob, BlobTransactionSidecar, Bytes48};
use alloy_primitives::B256;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use std::vec::IntoIter;
Expand Down Expand Up @@ -101,18 +101,6 @@ pub struct BlobData {
pub kzg_commitment_inclusion_proof: Vec<B256>,
}

/// Helper function to deserialize boxed blobs
fn deserialize_blob<'de, D>(deserializer: D) -> Result<Box<Blob>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let raw_blob = <Bytes>::deserialize(deserializer)?;

let blob = Box::new(Blob::try_from(raw_blob.as_ref()).map_err(serde::de::Error::custom)?);

Ok(blob)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit e5e17c2

Please sign in to comment.