From e5e17c21457c17c1a40853ca3d3e6a76f0723fa4 Mon Sep 17 00:00:00 2001 From: refcell Date: Wed, 25 Sep 2024 07:01:05 -0400 Subject: [PATCH] feat: Blob Tx Sidecar Iterator (#1334) feat: blob tx sidecar iterator --- crates/eips/src/eip4844/mod.rs | 14 ++++ crates/eips/src/eip4844/sidecar.rs | 101 +++++++++++++++++++++++-- crates/rpc-types-beacon/src/sidecar.rs | 16 +--- 3 files changed, 112 insertions(+), 19 deletions(-) diff --git a/crates/eips/src/eip4844/mod.rs b/crates/eips/src/eip4844/mod.rs index 99e315555ea..91ee3f3d678 100644 --- a/crates/eips/src/eip4844/mod.rs +++ b/crates/eips/src/eip4844/mod.rs @@ -85,6 +85,20 @@ pub const BYTES_PER_PROOF: usize = 48; /// A Blob serialized as 0x-prefixed hex string pub type Blob = FixedBytes; +/// Helper function to deserialize boxed blobs. +#[cfg(feature = "serde")] +pub fn deserialize_blob<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::de::Deserializer<'de>, +{ + use serde::Deserialize; + let raw_blob = ::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>; diff --git a/crates/eips/src/eip4844/sidecar.rs b/crates/eips/src/eip4844/sidecar.rs index 33695da1629..d9d2b875851 100644 --- a/crates/eips/src/eip4844/sidecar.rs +++ b/crates/eips/src/eip4844/sidecar.rs @@ -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}; @@ -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. @@ -32,6 +37,96 @@ pub struct BlobTransactionSidecar { pub proofs: Vec, } +impl IntoIterator for BlobTransactionSidecar { + type Item = BlobTransactionSidecarItem; + type IntoIter = alloc::vec::IntoIter; + + 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::>() + .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, + /// 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 { @@ -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. diff --git a/crates/rpc-types-beacon/src/sidecar.rs b/crates/rpc-types-beacon/src/sidecar.rs index 9c2866771a2..b511436fde2 100644 --- a/crates/rpc-types-beacon/src/sidecar.rs +++ b/crates/rpc-types-beacon/src/sidecar.rs @@ -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; @@ -101,18 +101,6 @@ pub struct BlobData { pub kzg_commitment_inclusion_proof: Vec, } -/// Helper function to deserialize boxed blobs -fn deserialize_blob<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::de::Deserializer<'de>, -{ - let raw_blob = ::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::*;