1use crate::{
4 eip4844::{
5 kzg_to_versioned_hash, Blob, BlobAndProofV1, Bytes48, BYTES_PER_BLOB, BYTES_PER_COMMITMENT,
6 BYTES_PER_PROOF,
7 },
8 eip7594::{Decodable7594, Encodable7594},
9};
10use alloc::{boxed::Box, vec::Vec};
11use alloy_primitives::{bytes::BufMut, B256};
12use alloy_rlp::{Decodable, Encodable, Header};
13
14#[cfg(any(test, feature = "arbitrary"))]
15use crate::eip4844::MAX_BLOBS_PER_BLOCK_DENCUN;
16
17#[cfg(feature = "kzg")]
19pub(crate) const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
20
21#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct IndexedBlobHash {
25 pub index: u64,
27 pub hash: B256,
29}
30
31#[derive(Clone, Default, PartialEq, Eq, Hash)]
35#[repr(C)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
38#[doc(alias = "BlobTxSidecar")]
39pub struct BlobTransactionSidecar {
40 #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::eip4844::deserialize_blobs"))]
42 pub blobs: Vec<Blob>,
43 pub commitments: Vec<Bytes48>,
45 pub proofs: Vec<Bytes48>,
47}
48
49impl core::fmt::Debug for BlobTransactionSidecar {
50 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51 f.debug_struct("BlobTransactionSidecar")
52 .field("blobs", &self.blobs.len())
53 .field("commitments", &self.commitments)
54 .field("proofs", &self.proofs)
55 .finish()
56 }
57}
58
59impl BlobTransactionSidecar {
60 pub fn match_versioned_hashes<'a>(
66 &'a self,
67 versioned_hashes: &'a [B256],
68 ) -> impl Iterator<Item = (usize, BlobAndProofV1)> + 'a {
69 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
70 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
71 if blob_versioned_hash == *target_hash {
72 if let Some((blob, proof)) =
73 self.blobs.get(i).copied().zip(self.proofs.get(i).copied())
74 {
75 return Some((j, BlobAndProofV1 { blob: Box::new(blob), proof }));
76 }
77 }
78 None
79 })
80 })
81 }
82
83 #[cfg(feature = "kzg")]
88 pub fn try_into_7594(
89 self,
90 settings: &c_kzg::KzgSettings,
91 ) -> Result<crate::eip7594::BlobTransactionSidecarEip7594, c_kzg::Error> {
92 use crate::eip7594::CELLS_PER_EXT_BLOB;
93
94 let mut cell_proofs = Vec::with_capacity(self.blobs.len() * CELLS_PER_EXT_BLOB);
95
96 for blob in self.blobs.iter() {
97 let blob_kzg = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
99
100 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob_kzg)?;
102
103 unsafe {
105 for kzg_proof in kzg_proofs.iter() {
106 cell_proofs.push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(
107 kzg_proof.to_bytes(),
108 ));
109 }
110 }
111 }
112
113 Ok(crate::eip7594::BlobTransactionSidecarEip7594::new(
114 self.blobs,
115 self.commitments,
116 cell_proofs,
117 ))
118 }
119}
120
121impl IntoIterator for BlobTransactionSidecar {
122 type Item = BlobTransactionSidecarItem;
123 type IntoIter = alloc::vec::IntoIter<BlobTransactionSidecarItem>;
124
125 fn into_iter(self) -> Self::IntoIter {
126 self.blobs
127 .into_iter()
128 .zip(self.commitments)
129 .zip(self.proofs)
130 .enumerate()
131 .map(|(index, ((blob, commitment), proof))| BlobTransactionSidecarItem {
132 index: index as u64,
133 blob: Box::new(blob),
134 kzg_commitment: commitment,
135 kzg_proof: proof,
136 })
137 .collect::<Vec<_>>()
138 .into_iter()
139 }
140}
141
142#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
144#[repr(C)]
145#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
146pub struct BlobTransactionSidecarItem {
147 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
149 pub index: u64,
150 #[cfg_attr(feature = "serde", serde(deserialize_with = "super::deserialize_blob"))]
152 pub blob: Box<Blob>,
153 pub kzg_commitment: Bytes48,
155 pub kzg_proof: Bytes48,
157}
158
159#[cfg(feature = "kzg")]
160impl BlobTransactionSidecarItem {
161 pub fn to_kzg_versioned_hash(&self) -> [u8; 32] {
163 use sha2::Digest;
164 let commitment = self.kzg_commitment.as_slice();
165 let mut hash: [u8; 32] = sha2::Sha256::digest(commitment).into();
166 hash[0] = VERSIONED_HASH_VERSION_KZG;
167 hash
168 }
169
170 pub fn verify_blob_kzg_proof(&self) -> Result<(), BlobTransactionValidationError> {
172 let binding = crate::eip4844::env_settings::EnvKzgSettings::Default;
173 let settings = binding.get();
174
175 let blob = c_kzg::Blob::from_bytes(self.blob.as_slice())
176 .map_err(BlobTransactionValidationError::KZGError)?;
177
178 let commitment = c_kzg::Bytes48::from_bytes(self.kzg_commitment.as_slice())
179 .map_err(BlobTransactionValidationError::KZGError)?;
180
181 let proof = c_kzg::Bytes48::from_bytes(self.kzg_proof.as_slice())
182 .map_err(BlobTransactionValidationError::KZGError)?;
183
184 let result = settings
185 .verify_blob_kzg_proof(&blob, &commitment, &proof)
186 .map_err(BlobTransactionValidationError::KZGError)?;
187
188 result.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
189 }
190
191 pub fn verify_blob(
193 &self,
194 hash: &IndexedBlobHash,
195 ) -> Result<(), BlobTransactionValidationError> {
196 if self.index != hash.index {
197 let blob_hash_part = B256::from_slice(&self.blob[0..32]);
198 return Err(BlobTransactionValidationError::WrongVersionedHash {
199 have: blob_hash_part,
200 expected: hash.hash,
201 });
202 }
203
204 let computed_hash = self.to_kzg_versioned_hash();
205 if computed_hash != hash.hash {
206 return Err(BlobTransactionValidationError::WrongVersionedHash {
207 have: computed_hash.into(),
208 expected: hash.hash,
209 });
210 }
211
212 self.verify_blob_kzg_proof()
213 }
214}
215
216#[cfg(any(test, feature = "arbitrary"))]
217impl<'a> arbitrary::Arbitrary<'a> for BlobTransactionSidecar {
218 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
219 let num_blobs = u.int_in_range(1..=MAX_BLOBS_PER_BLOCK_DENCUN)?;
220 let mut blobs = Vec::with_capacity(num_blobs);
221 for _ in 0..num_blobs {
222 blobs.push(Blob::arbitrary(u)?);
223 }
224
225 let mut commitments = Vec::with_capacity(num_blobs);
226 let mut proofs = Vec::with_capacity(num_blobs);
227 for _ in 0..num_blobs {
228 commitments.push(Bytes48::arbitrary(u)?);
229 proofs.push(Bytes48::arbitrary(u)?);
230 }
231
232 Ok(Self { blobs, commitments, proofs })
233 }
234}
235
236impl BlobTransactionSidecar {
237 pub const fn new(blobs: Vec<Blob>, commitments: Vec<Bytes48>, proofs: Vec<Bytes48>) -> Self {
239 Self { blobs, commitments, proofs }
240 }
241
242 #[cfg(feature = "kzg")]
244 pub fn from_kzg(
245 blobs: Vec<c_kzg::Blob>,
246 commitments: Vec<c_kzg::Bytes48>,
247 proofs: Vec<c_kzg::Bytes48>,
248 ) -> Self {
249 unsafe fn transmute_vec<U, T>(input: Vec<T>) -> Vec<U> {
251 let mut v = core::mem::ManuallyDrop::new(input);
252 Vec::from_raw_parts(v.as_mut_ptr() as *mut U, v.len(), v.capacity())
253 }
254
255 unsafe {
257 let blobs = transmute_vec::<Blob, c_kzg::Blob>(blobs);
258 let commitments = transmute_vec::<Bytes48, c_kzg::Bytes48>(commitments);
259 let proofs = transmute_vec::<Bytes48, c_kzg::Bytes48>(proofs);
260 Self { blobs, commitments, proofs }
261 }
262 }
263
264 #[cfg(feature = "kzg")]
278 pub fn validate(
279 &self,
280 blob_versioned_hashes: &[B256],
281 proof_settings: &c_kzg::KzgSettings,
282 ) -> Result<(), BlobTransactionValidationError> {
283 if blob_versioned_hashes.len() != self.commitments.len() {
285 return Err(c_kzg::Error::MismatchLength(format!(
286 "There are {} versioned commitment hashes and {} commitments",
287 blob_versioned_hashes.len(),
288 self.commitments.len()
289 ))
290 .into());
291 }
292
293 for (versioned_hash, commitment) in
295 blob_versioned_hashes.iter().zip(self.commitments.iter())
296 {
297 let calculated_versioned_hash = kzg_to_versioned_hash(commitment.as_slice());
299 if *versioned_hash != calculated_versioned_hash {
300 return Err(BlobTransactionValidationError::WrongVersionedHash {
301 have: *versioned_hash,
302 expected: calculated_versioned_hash,
303 });
304 }
305 }
306
307 let res = unsafe {
309 proof_settings.verify_blob_kzg_proof_batch(
310 core::mem::transmute::<&[Blob], &[c_kzg::Blob]>(self.blobs.as_slice()),
312 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.commitments.as_slice()),
314 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.proofs.as_slice()),
316 )
317 }
318 .map_err(BlobTransactionValidationError::KZGError)?;
319
320 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
321 }
322
323 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
325 VersionedHashIter::new(&self.commitments)
326 }
327
328 pub fn versioned_hash_for_blob(&self, blob_index: usize) -> Option<B256> {
331 self.commitments.get(blob_index).map(|c| kzg_to_versioned_hash(c.as_slice()))
332 }
333
334 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
336 self.commitments
337 .iter()
338 .position(|commitment| kzg_to_versioned_hash(commitment.as_slice()) == *hash)
339 }
340
341 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
343 self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
344 }
345
346 #[inline]
348 pub const fn size(&self) -> usize {
349 self.blobs.len() * BYTES_PER_BLOB + self.commitments.len() * BYTES_PER_COMMITMENT + self.proofs.len() * BYTES_PER_PROOF }
353
354 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
358 pub fn try_from_blobs_hex<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
359 where
360 I: IntoIterator<Item = B>,
361 B: AsRef<str>,
362 {
363 blobs
364 .into_iter()
365 .map(crate::eip4844::utils::hex_to_blob)
366 .collect::<Result<Vec<_>, _>>()
367 .and_then(Self::try_from_blobs)
368 }
369
370 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
375 pub fn try_from_blobs_bytes<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
376 where
377 I: IntoIterator<Item = B>,
378 B: AsRef<[u8]>,
379 {
380 blobs
381 .into_iter()
382 .map(crate::eip4844::utils::bytes_to_blob)
383 .collect::<Result<Vec<_>, _>>()
384 .and_then(Self::try_from_blobs)
385 }
386
387 #[cfg(feature = "kzg")]
390 pub fn try_from_blobs_with_settings(
391 blobs: Vec<Blob>,
392 settings: &c_kzg::KzgSettings,
393 ) -> Result<Self, c_kzg::Error> {
394 let mut commitments = Vec::with_capacity(blobs.len());
395 let mut proofs = Vec::with_capacity(blobs.len());
396 for blob in &blobs {
397 let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
399 let commitment = settings.blob_to_kzg_commitment(blob)?;
400 let proof = settings.compute_blob_kzg_proof(blob, &commitment.to_bytes())?;
401
402 unsafe {
404 commitments
405 .push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(commitment.to_bytes()));
406 proofs.push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(proof.to_bytes()));
407 }
408 }
409
410 Ok(Self::new(blobs, commitments, proofs))
411 }
412
413 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
418 pub fn try_from_blobs(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error> {
419 use crate::eip4844::env_settings::EnvKzgSettings;
420
421 Self::try_from_blobs_with_settings(blobs, EnvKzgSettings::Default.get())
422 }
423
424 #[doc(hidden)]
427 pub fn rlp_encoded_fields_length(&self) -> usize {
428 self.blobs.length() + self.commitments.length() + self.proofs.length()
429 }
430
431 #[inline]
438 #[doc(hidden)]
439 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
440 self.blobs.encode(out);
442 self.commitments.encode(out);
443 self.proofs.encode(out);
444 }
445
446 fn rlp_header(&self) -> Header {
448 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
449 }
450
451 pub fn rlp_encoded_length(&self) -> usize {
454 self.rlp_header().length() + self.rlp_encoded_fields_length()
455 }
456
457 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
459 self.rlp_header().encode(out);
460 self.rlp_encode_fields(out);
461 }
462
463 #[doc(hidden)]
465 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
466 Ok(Self {
467 blobs: Decodable::decode(buf)?,
468 commitments: Decodable::decode(buf)?,
469 proofs: Decodable::decode(buf)?,
470 })
471 }
472
473 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
475 let header = Header::decode(buf)?;
476 if !header.list {
477 return Err(alloy_rlp::Error::UnexpectedString);
478 }
479 if buf.len() < header.payload_length {
480 return Err(alloy_rlp::Error::InputTooShort);
481 }
482 let remaining = buf.len();
483 let this = Self::rlp_decode_fields(buf)?;
484
485 if buf.len() + header.payload_length != remaining {
486 return Err(alloy_rlp::Error::UnexpectedLength);
487 }
488
489 Ok(this)
490 }
491}
492
493impl Encodable for BlobTransactionSidecar {
494 fn encode(&self, out: &mut dyn BufMut) {
496 self.rlp_encode(out);
497 }
498
499 fn length(&self) -> usize {
500 self.rlp_encoded_length()
501 }
502}
503
504impl Decodable for BlobTransactionSidecar {
505 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
507 Self::rlp_decode(buf)
508 }
509}
510
511impl Encodable7594 for BlobTransactionSidecar {
512 fn encode_7594_len(&self) -> usize {
513 self.rlp_encoded_fields_length()
514 }
515
516 fn encode_7594(&self, out: &mut dyn BufMut) {
517 self.rlp_encode_fields(out);
518 }
519}
520
521impl Decodable7594 for BlobTransactionSidecar {
522 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
523 Self::rlp_decode_fields(buf)
524 }
525}
526
527#[cfg(all(debug_assertions, feature = "serde"))]
531pub(crate) fn deserialize_blobs_map<'de, M: serde::de::MapAccess<'de>>(
532 map_access: &mut M,
533) -> Result<Vec<Blob>, M::Error> {
534 let raw_blobs: Vec<alloy_primitives::Bytes> = map_access.next_value()?;
535 let mut blobs = Vec::with_capacity(raw_blobs.len());
536 for blob in raw_blobs {
537 blobs.push(Blob::try_from(blob.as_ref()).map_err(serde::de::Error::custom)?);
538 }
539 Ok(blobs)
540}
541
542#[cfg(all(not(debug_assertions), feature = "serde"))]
543#[inline(always)]
544pub(crate) fn deserialize_blobs_map<'de, M: serde::de::MapAccess<'de>>(
545 map_access: &mut M,
546) -> Result<Vec<Blob>, M::Error> {
547 map_access.next_value()
548}
549
550#[derive(Debug)]
552#[cfg(feature = "kzg")]
553pub enum BlobTransactionValidationError {
554 InvalidProof,
556 KZGError(c_kzg::Error),
558 NotBlobTransaction(u8),
560 MissingSidecar,
562 WrongVersionedHash {
564 have: B256,
566 expected: B256,
568 },
569}
570
571#[cfg(feature = "kzg")]
572impl core::error::Error for BlobTransactionValidationError {}
573
574#[cfg(feature = "kzg")]
575impl core::fmt::Display for BlobTransactionValidationError {
576 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
577 match self {
578 Self::InvalidProof => f.write_str("invalid KZG proof"),
579 Self::KZGError(err) => {
580 write!(f, "KZG error: {err:?}")
581 }
582 Self::NotBlobTransaction(err) => {
583 write!(f, "unable to verify proof for non blob transaction: {err}")
584 }
585 Self::MissingSidecar => {
586 f.write_str("eip4844 tx variant without sidecar being used for verification.")
587 }
588 Self::WrongVersionedHash { have, expected } => {
589 write!(f, "wrong versioned hash: have {have}, expected {expected}")
590 }
591 }
592 }
593}
594
595#[cfg(feature = "kzg")]
596impl From<c_kzg::Error> for BlobTransactionValidationError {
597 fn from(source: c_kzg::Error) -> Self {
598 Self::KZGError(source)
599 }
600}
601
602#[derive(Debug, Clone)]
604pub struct VersionedHashIter<'a> {
605 commitments: core::slice::Iter<'a, Bytes48>,
607}
608
609impl<'a> Iterator for VersionedHashIter<'a> {
610 type Item = B256;
611
612 fn next(&mut self) -> Option<Self::Item> {
613 self.commitments.next().map(|c| kzg_to_versioned_hash(c.as_slice()))
614 }
615}
616
617impl<'a> VersionedHashIter<'a> {
619 pub fn new(commitments: &'a [Bytes48]) -> Self {
621 Self { commitments: commitments.iter() }
622 }
623}
624
625#[cfg(test)]
626mod tests {
627 use super::*;
628 use arbitrary::Arbitrary;
629
630 #[test]
631 #[cfg(feature = "serde")]
632 fn deserialize_blob() {
633 let blob = BlobTransactionSidecar {
634 blobs: vec![Blob::default(), Blob::default(), Blob::default(), Blob::default()],
635 commitments: vec![
636 Bytes48::default(),
637 Bytes48::default(),
638 Bytes48::default(),
639 Bytes48::default(),
640 ],
641 proofs: vec![
642 Bytes48::default(),
643 Bytes48::default(),
644 Bytes48::default(),
645 Bytes48::default(),
646 ],
647 };
648
649 let s = serde_json::to_string(&blob).unwrap();
650 let deserialized: BlobTransactionSidecar = serde_json::from_str(&s).unwrap();
651 assert_eq!(blob, deserialized);
652 }
653
654 #[test]
655 fn test_arbitrary_blob() {
656 let mut unstructured = arbitrary::Unstructured::new(b"unstructured blob");
657 let _blob = BlobTransactionSidecar::arbitrary(&mut unstructured).unwrap();
658 }
659
660 #[test]
661 #[cfg(feature = "serde")]
662 fn test_blob_item_serde_roundtrip() {
663 let blob_item = BlobTransactionSidecarItem {
664 index: 0,
665 blob: Box::new(Blob::default()),
666 kzg_commitment: Bytes48::default(),
667 kzg_proof: Bytes48::default(),
668 };
669
670 let s = serde_json::to_string(&blob_item).unwrap();
671 let deserialized: BlobTransactionSidecarItem = serde_json::from_str(&s).unwrap();
672 assert_eq!(blob_item, deserialized);
673 }
674}