1use crate::{
2 eip4844::{
3 Blob, BlobAndProofV2, BlobCellsAndProofsV1, BlobTransactionSidecar, Bytes48,
4 BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_PROOF,
5 },
6 eip7594::{Cell, CELLS_PER_EXT_BLOB, EIP_7594_WRAPPER_VERSION},
7};
8use alloc::{boxed::Box, vec::Vec};
9use alloy_primitives::{B128, B256};
10use alloy_rlp::{BufMut, Decodable, Encodable, Header};
11
12use super::{Decodable7594, Encodable7594};
13#[cfg(feature = "kzg")]
14use crate::eip4844::BlobTransactionValidationError;
15use crate::eip4844::VersionedHashIter;
16
17#[derive(Clone, PartialEq, Eq, Hash, Debug, derive_more::From)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23#[cfg_attr(feature = "serde", serde(untagged))]
24#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
25pub enum BlobTransactionSidecarVariant {
26 Eip4844(BlobTransactionSidecar),
28 Eip7594(BlobTransactionSidecarEip7594),
30}
31
32impl Default for BlobTransactionSidecarVariant {
33 fn default() -> Self {
34 Self::Eip4844(BlobTransactionSidecar::default())
35 }
36}
37
38impl BlobTransactionSidecarVariant {
39 pub const fn is_eip4844(&self) -> bool {
41 matches!(self, Self::Eip4844(_))
42 }
43
44 pub const fn is_eip7594(&self) -> bool {
46 matches!(self, Self::Eip7594(_))
47 }
48
49 pub const fn as_eip4844(&self) -> Option<&BlobTransactionSidecar> {
51 match self {
52 Self::Eip4844(sidecar) => Some(sidecar),
53 _ => None,
54 }
55 }
56
57 pub const fn as_eip7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
59 match self {
60 Self::Eip7594(sidecar) => Some(sidecar),
61 _ => None,
62 }
63 }
64
65 pub fn into_eip4844(self) -> Option<BlobTransactionSidecar> {
67 match self {
68 Self::Eip4844(sidecar) => Some(sidecar),
69 _ => None,
70 }
71 }
72
73 pub fn into_eip7594(self) -> Option<BlobTransactionSidecarEip7594> {
75 match self {
76 Self::Eip7594(sidecar) => Some(sidecar),
77 _ => None,
78 }
79 }
80
81 pub fn blobs(&self) -> &[Blob] {
83 match self {
84 Self::Eip4844(sidecar) => &sidecar.blobs,
85 Self::Eip7594(sidecar) => &sidecar.blobs,
86 }
87 }
88
89 pub fn into_blobs(self) -> Vec<Blob> {
91 match self {
92 Self::Eip4844(sidecar) => sidecar.blobs,
93 Self::Eip7594(sidecar) => sidecar.blobs,
94 }
95 }
96
97 #[inline]
99 pub const fn size(&self) -> usize {
100 match self {
101 Self::Eip4844(sidecar) => sidecar.size(),
102 Self::Eip7594(sidecar) => sidecar.size(),
103 }
104 }
105
106 #[cfg(feature = "kzg")]
134 pub fn try_convert_into_eip7594(self) -> Result<Self, c_kzg::Error> {
135 self.try_convert_into_eip7594_with_settings(
136 crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
137 )
138 }
139
140 #[cfg(feature = "kzg")]
181 pub fn try_convert_into_eip7594_with_settings(
182 self,
183 settings: &c_kzg::KzgSettings,
184 ) -> Result<Self, c_kzg::Error> {
185 match self {
186 Self::Eip4844(legacy) => legacy.try_into_7594(settings).map(Self::Eip7594),
187 sidecar @ Self::Eip7594(_) => Ok(sidecar),
188 }
189 }
190
191 #[cfg(feature = "kzg")]
224 pub fn try_into_eip7594(self) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
225 self.try_into_eip7594_with_settings(
226 crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
227 )
228 }
229
230 #[cfg(feature = "kzg")]
275 pub fn try_into_eip7594_with_settings(
276 self,
277 settings: &c_kzg::KzgSettings,
278 ) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
279 match self {
280 Self::Eip4844(legacy) => legacy.try_into_7594(settings),
281 Self::Eip7594(sidecar) => Ok(sidecar),
282 }
283 }
284
285 #[cfg(feature = "kzg")]
287 pub fn validate(
288 &self,
289 blob_versioned_hashes: &[B256],
290 proof_settings: &c_kzg::KzgSettings,
291 ) -> Result<(), BlobTransactionValidationError> {
292 match self {
293 Self::Eip4844(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
294 Self::Eip7594(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
295 }
296 }
297
298 pub fn commitments(&self) -> &[Bytes48] {
300 match self {
301 Self::Eip4844(sidecar) => &sidecar.commitments,
302 Self::Eip7594(sidecar) => &sidecar.commitments,
303 }
304 }
305
306 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
308 VersionedHashIter::new(self.commitments())
309 }
310
311 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
313 match self {
314 Self::Eip4844(s) => s.versioned_hash_index(hash),
315 Self::Eip7594(s) => s.versioned_hash_index(hash),
316 }
317 }
318
319 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
321 match self {
322 Self::Eip4844(s) => s.blob_by_versioned_hash(hash),
323 Self::Eip7594(s) => s.blob_by_versioned_hash(hash),
324 }
325 }
326
327 #[doc(hidden)]
329 pub fn rlp_encoded_fields_length(&self) -> usize {
330 match self {
331 Self::Eip4844(sidecar) => sidecar.rlp_encoded_fields_length(),
332 Self::Eip7594(sidecar) => sidecar.rlp_encoded_fields_length(),
333 }
334 }
335
336 #[inline]
338 #[doc(hidden)]
339 pub fn rlp_encoded_fields(&self) -> Vec<u8> {
340 let mut buf = Vec::with_capacity(self.rlp_encoded_fields_length());
341 self.rlp_encode_fields(&mut buf);
342 buf
343 }
344
345 #[inline]
348 #[doc(hidden)]
349 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
350 match self {
351 Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
352 Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
353 }
354 }
355
356 #[doc(hidden)]
358 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
359 Self::decode_7594(buf)
360 }
361}
362
363impl Encodable for BlobTransactionSidecarVariant {
364 fn encode(&self, out: &mut dyn BufMut) {
366 match self {
367 Self::Eip4844(sidecar) => sidecar.encode(out),
368 Self::Eip7594(sidecar) => sidecar.encode(out),
369 }
370 }
371
372 fn length(&self) -> usize {
373 match self {
374 Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
375 Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
376 }
377 }
378}
379
380impl Decodable for BlobTransactionSidecarVariant {
381 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
383 let header = Header::decode(buf)?;
384 if !header.list {
385 return Err(alloy_rlp::Error::UnexpectedString);
386 }
387 if buf.len() < header.payload_length {
388 return Err(alloy_rlp::Error::InputTooShort);
389 }
390 let remaining = buf.len();
391 let this = Self::rlp_decode_fields(buf)?;
392 if buf.len() + header.payload_length != remaining {
393 return Err(alloy_rlp::Error::UnexpectedLength);
394 }
395
396 Ok(this)
397 }
398}
399
400impl Encodable7594 for BlobTransactionSidecarVariant {
401 fn encode_7594_len(&self) -> usize {
402 self.rlp_encoded_fields_length()
403 }
404
405 fn encode_7594(&self, out: &mut dyn BufMut) {
406 self.rlp_encode_fields(out);
407 }
408}
409
410impl Decodable7594 for BlobTransactionSidecarVariant {
411 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
412 if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
413 Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
414 } else {
415 Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
416 }
417 }
418}
419
420#[cfg(feature = "kzg")]
421impl TryFrom<BlobTransactionSidecarVariant> for BlobTransactionSidecarEip7594 {
422 type Error = c_kzg::Error;
423
424 fn try_from(value: BlobTransactionSidecarVariant) -> Result<Self, Self::Error> {
425 value.try_into_eip7594()
426 }
427}
428
429#[cfg(feature = "serde")]
430impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
431 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
432 where
433 D: serde::Deserializer<'de>,
434 {
435 use core::fmt;
436
437 #[derive(serde::Deserialize, fmt::Debug)]
438 #[serde(field_identifier, rename_all = "camelCase")]
439 enum Field {
440 Blobs,
441 Commitments,
442 Proofs,
443 CellProofs,
444 }
445
446 struct VariantVisitor;
447
448 impl<'de> serde::de::Visitor<'de> for VariantVisitor {
449 type Value = BlobTransactionSidecarVariant;
450
451 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
452 formatter
453 .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
454 }
455
456 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
457 where
458 M: serde::de::MapAccess<'de>,
459 {
460 let mut blobs = None;
461 let mut commitments = None;
462 let mut proofs = None;
463 let mut cell_proofs = None;
464
465 while let Some(key) = map.next_key()? {
466 match key {
467 Field::Blobs => {
468 blobs = Some(crate::eip4844::deserialize_blobs_map(&mut map)?);
469 }
470 Field::Commitments => commitments = Some(map.next_value()?),
471 Field::Proofs => proofs = Some(map.next_value()?),
472 Field::CellProofs => cell_proofs = Some(map.next_value()?),
473 }
474 }
475
476 let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
477 let commitments =
478 commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
479
480 match (cell_proofs, proofs) {
481 (Some(cp), None) => {
482 Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
483 blobs,
484 commitments,
485 cell_proofs: cp,
486 }))
487 }
488 (None, Some(pf)) => {
489 Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
490 blobs,
491 commitments,
492 proofs: pf,
493 }))
494 }
495 (None, None) => {
496 Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
497 }
498 (Some(_), Some(_)) => Err(serde::de::Error::custom(
499 "Both 'cellProofs' and 'proofs' cannot be present",
500 )),
501 }
502 }
503 }
504
505 const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
506 deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
507 }
508}
509
510#[derive(Clone, Default, PartialEq, Eq, Hash)]
514#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
515#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
516#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
517pub struct BlobTransactionSidecarEip7594 {
518 #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::eip4844::deserialize_blobs"))]
520 pub blobs: Vec<Blob>,
521 pub commitments: Vec<Bytes48>,
523 pub cell_proofs: Vec<Bytes48>,
528}
529
530impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
531 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
532 f.debug_struct("BlobTransactionSidecarEip7594")
533 .field("blobs", &self.blobs.len())
534 .field("commitments", &self.commitments)
535 .field("cell_proofs", &self.cell_proofs)
536 .finish()
537 }
538}
539
540#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
542pub struct BlobCellMask {
543 value: u128,
544}
545
546impl BlobCellMask {
547 #[inline]
549 pub fn new(indices_bitarray: B128) -> Self {
550 Self { value: u128::from(indices_bitarray) }
551 }
552
553 #[inline]
555 pub const fn from_bits(value: u128) -> Self {
556 Self { value }
557 }
558
559 #[inline]
561 pub const fn bits(self) -> u128 {
562 self.value
563 }
564
565 #[inline]
567 pub const fn count(self) -> usize {
568 self.value.count_ones() as usize
569 }
570
571 #[inline]
573 pub const fn contains(self, index: usize) -> bool {
574 index < CELLS_PER_EXT_BLOB && self.value & (1u128 << index) != 0
575 }
576
577 #[inline]
579 pub fn selected_indices(self) -> impl Iterator<Item = usize> {
580 let mut bits = self.value;
581 core::iter::from_fn(move || {
582 if bits == 0 {
583 return None;
584 }
585
586 let index = bits.trailing_zeros() as usize;
587 bits &= bits - 1;
588 Some(index)
589 })
590 }
591}
592
593impl BlobTransactionSidecarEip7594 {
594 pub const fn new(
597 blobs: Vec<Blob>,
598 commitments: Vec<Bytes48>,
599 cell_proofs: Vec<Bytes48>,
600 ) -> Self {
601 Self { blobs, commitments, cell_proofs }
602 }
603
604 #[inline]
606 pub const fn size(&self) -> usize {
607 self.blobs.capacity() * BYTES_PER_BLOB
608 + self.commitments.capacity() * BYTES_PER_COMMITMENT
609 + self.cell_proofs.capacity() * BYTES_PER_PROOF
610 }
611
612 #[inline]
614 pub fn shrink_to_fit(&mut self) {
615 self.blobs.shrink_to_fit();
616 self.commitments.shrink_to_fit();
617 self.cell_proofs.shrink_to_fit();
618 }
619
620 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
624 pub fn try_from_blobs_hex<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
625 where
626 I: IntoIterator<Item = B>,
627 B: AsRef<str>,
628 {
629 let mut converted = Vec::new();
630 for blob in blobs {
631 converted.push(crate::eip4844::utils::hex_to_blob(blob)?);
632 }
633 Self::try_from_blobs(converted)
634 }
635
636 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
641 pub fn try_from_blobs_bytes<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
642 where
643 I: IntoIterator<Item = B>,
644 B: AsRef<[u8]>,
645 {
646 let mut converted = Vec::new();
647 for blob in blobs {
648 converted.push(crate::eip4844::utils::bytes_to_blob(blob)?);
649 }
650 Self::try_from_blobs(converted)
651 }
652
653 #[cfg(feature = "kzg")]
656 pub fn try_from_blobs_with_settings(
657 blobs: Vec<Blob>,
658 settings: &c_kzg::KzgSettings,
659 ) -> Result<Self, c_kzg::Error> {
660 let mut commitments = Vec::with_capacity(blobs.len());
661 let mut proofs = Vec::with_capacity(blobs.len());
662 for blob in &blobs {
663 let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
665 let commitment = settings.blob_to_kzg_commitment(blob)?;
666 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
667
668 unsafe {
670 commitments
671 .push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(commitment.to_bytes()));
672 for kzg_proof in kzg_proofs.iter() {
673 proofs.push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(
674 kzg_proof.to_bytes(),
675 ));
676 }
677 }
678 }
679
680 Ok(Self::new(blobs, commitments, proofs))
681 }
682
683 #[cfg(feature = "kzg")]
689 pub fn try_from_blobs(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error> {
690 use crate::eip4844::env_settings::EnvKzgSettings;
691
692 Self::try_from_blobs_with_settings(blobs, EnvKzgSettings::Default.get())
693 }
694
695 #[cfg(feature = "kzg")]
701 pub fn compute_cells(&self) -> Result<Vec<Cell>, c_kzg::Error> {
702 use crate::eip4844::env_settings::EnvKzgSettings;
703
704 self.compute_cells_with_settings(EnvKzgSettings::Default.get())
705 }
706
707 #[cfg(feature = "kzg")]
713 pub fn compute_cells_with_settings(
714 &self,
715 settings: &c_kzg::KzgSettings,
716 ) -> Result<Vec<Cell>, c_kzg::Error> {
717 let mut cells = Vec::with_capacity(self.blobs.len() * CELLS_PER_EXT_BLOB);
718 for blob in &self.blobs {
719 let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
721 let blob_cells = settings.compute_cells(blob)?;
722 cells.extend(blob_cells.iter().map(|cell| Cell::new(cell.to_bytes())));
723 }
724 Ok(cells)
725 }
726
727 #[cfg(feature = "kzg")]
741 pub fn validate(
742 &self,
743 blob_versioned_hashes: &[B256],
744 proof_settings: &c_kzg::KzgSettings,
745 ) -> Result<(), BlobTransactionValidationError> {
746 if blob_versioned_hashes.len() != self.commitments.len() {
748 return Err(c_kzg::Error::MismatchLength(format!(
749 "There are {} versioned commitment hashes and {} commitments",
750 blob_versioned_hashes.len(),
751 self.commitments.len()
752 ))
753 .into());
754 }
755
756 let blobs_len = self.blobs.len();
757 let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
758 if self.cell_proofs.len() != expected_cell_proofs_len {
759 return Err(c_kzg::Error::MismatchLength(format!(
760 "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
761 self.cell_proofs.len(),
762 blobs_len,
763 expected_cell_proofs_len
764 ))
765 .into());
766 }
767
768 for (versioned_hash, commitment) in
770 blob_versioned_hashes.iter().zip(self.commitments.iter())
771 {
772 let calculated_versioned_hash =
774 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
775 if *versioned_hash != calculated_versioned_hash {
776 return Err(BlobTransactionValidationError::WrongVersionedHash {
777 have: *versioned_hash,
778 expected: calculated_versioned_hash,
779 });
780 }
781 }
782
783 let cell_indices =
785 Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
786
787 let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
789 for commitment in &self.commitments {
790 commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
791 }
792
793 let res = unsafe {
795 let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
796 for blob in &self.blobs {
797 let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
798 let blob_cells = proof_settings.compute_cells(blob)?;
799 cells.extend_from_slice(blob_cells.as_ref());
800 }
801
802 proof_settings.verify_cell_kzg_proof_batch(
803 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
805 &cell_indices,
807 &cells,
809 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
811 )?
812 };
813
814 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
815 }
816
817 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
819 VersionedHashIter::new(&self.commitments)
820 }
821
822 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
824 self.commitments.iter().position(|commitment| {
825 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
826 })
827 }
828
829 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
831 self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
832 }
833
834 #[cfg(feature = "kzg")]
838 pub fn blob_cells_and_proofs(
839 &self,
840 blob_index: usize,
841 cell_mask: BlobCellMask,
842 ) -> Result<Option<BlobCellsAndProofsV1>, c_kzg::Error> {
843 use crate::eip4844::env_settings::EnvKzgSettings;
844
845 self.blob_cells_and_proofs_with_settings(
846 blob_index,
847 cell_mask,
848 EnvKzgSettings::Default.get(),
849 )
850 }
851
852 #[cfg(feature = "kzg")]
854 pub fn blob_cells_and_proofs_with_settings(
855 &self,
856 blob_index: usize,
857 cell_mask: BlobCellMask,
858 settings: &c_kzg::KzgSettings,
859 ) -> Result<Option<BlobCellsAndProofsV1>, c_kzg::Error> {
860 let Some(blob) = self.blobs.get(blob_index) else { return Ok(None) };
861
862 let proof_start = blob_index * CELLS_PER_EXT_BLOB;
863 let Some(proofs) = self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
864 else {
865 return Ok(None);
866 };
867
868 if cell_mask.count() == 0 {
869 return Ok(Some(BlobCellsAndProofsV1::default()));
870 }
871
872 let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
874 let cells = settings.compute_cells(blob)?;
875
876 Ok(Some(Self::blob_cells_and_proofs_from_computed_cells(cell_mask, cells.as_ref(), proofs)))
877 }
878
879 #[cfg(feature = "kzg")]
881 fn blob_cells_and_proofs_from_computed_cells(
882 cell_mask: BlobCellMask,
883 cells: &[c_kzg::Cell],
884 proofs: &[Bytes48],
885 ) -> BlobCellsAndProofsV1 {
886 let mut blob_cells = Vec::with_capacity(cell_mask.count());
887 let mut selected_proofs = Vec::with_capacity(cell_mask.count());
888 for cell_index in cell_mask.selected_indices() {
889 blob_cells.push(Some(Cell::new(cells[cell_index].to_bytes())));
890 selected_proofs.push(Some(proofs[cell_index]));
891 }
892
893 BlobCellsAndProofsV1 { blob_cells, proofs: selected_proofs }
894 }
895
896 pub fn match_versioned_hashes<'a>(
902 &'a self,
903 versioned_hashes: &'a [B256],
904 ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
905 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
906 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
907 if blob_versioned_hash == *target_hash {
908 let maybe_blob = self.blobs.get(i);
909 let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
910 let maybe_proofs = Some(&self.cell_proofs[proof_range])
911 .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
912 if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
913 return Some((
914 j,
915 BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
916 ));
917 }
918 }
919 None
920 })
921 })
922 }
923
924 #[cfg(feature = "kzg")]
931 pub fn match_versioned_hashes_cells<'a>(
932 &'a self,
933 versioned_hashes: &'a [B256],
934 cell_mask: BlobCellMask,
935 ) -> Result<impl Iterator<Item = (usize, BlobCellsAndProofsV1)> + 'a, c_kzg::Error> {
936 use crate::eip4844::env_settings::EnvKzgSettings;
937
938 self.match_versioned_hashes_cells_with_settings(
939 versioned_hashes,
940 cell_mask,
941 EnvKzgSettings::Default.get(),
942 )
943 }
944
945 #[cfg(feature = "kzg")]
948 pub fn match_versioned_hashes_cells_with_settings<'a>(
949 &'a self,
950 versioned_hashes: &'a [B256],
951 cell_mask: BlobCellMask,
952 settings: &c_kzg::KzgSettings,
953 ) -> Result<impl Iterator<Item = (usize, BlobCellsAndProofsV1)> + 'a, c_kzg::Error> {
954 let mut matches = Vec::new();
955 let mut cells_and_proofs_by_blob = Vec::<(usize, BlobCellsAndProofsV1)>::new();
956
957 for (blob_index, commitment) in self.commitments.iter().enumerate() {
958 let blob_versioned_hash = crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
959 for (matched_index, target_hash) in versioned_hashes.iter().enumerate() {
960 if blob_versioned_hash != *target_hash {
961 continue;
962 }
963
964 let Some(blob) = self.blobs.get(blob_index) else { continue };
965 let proof_start = blob_index * CELLS_PER_EXT_BLOB;
966 let Some(proofs) =
967 self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
968 else {
969 continue;
970 };
971
972 let cells_and_proofs = if cell_mask.count() == 0 {
973 BlobCellsAndProofsV1::default()
974 } else if let Some((_, cells_and_proofs)) =
975 cells_and_proofs_by_blob.iter().find(|(index, _)| *index == blob_index)
976 {
977 cells_and_proofs.clone()
978 } else {
979 let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
981 let cells = settings.compute_cells(blob)?;
982 let cells_and_proofs = Self::blob_cells_and_proofs_from_computed_cells(
983 cell_mask,
984 cells.as_ref(),
985 proofs,
986 );
987 cells_and_proofs_by_blob.push((blob_index, cells_and_proofs.clone()));
988 cells_and_proofs
989 };
990
991 matches.push((matched_index, cells_and_proofs));
992 }
993 }
994
995 Ok(matches.into_iter())
996 }
997
998 #[doc(hidden)]
1000 pub fn rlp_encoded_fields_length(&self) -> usize {
1001 1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
1003 }
1004
1005 #[inline]
1014 #[doc(hidden)]
1015 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
1016 out.put_u8(EIP_7594_WRAPPER_VERSION);
1018 self.blobs.encode(out);
1020 self.commitments.encode(out);
1021 self.cell_proofs.encode(out);
1022 }
1023
1024 fn rlp_header(&self) -> Header {
1026 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
1027 }
1028
1029 pub fn rlp_encoded_length(&self) -> usize {
1032 self.rlp_header().length() + self.rlp_encoded_fields_length()
1033 }
1034
1035 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
1037 self.rlp_header().encode(out);
1038 self.rlp_encode_fields(out);
1039 }
1040
1041 #[doc(hidden)]
1043 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1044 Ok(Self {
1045 blobs: Decodable::decode(buf)?,
1046 commitments: Decodable::decode(buf)?,
1047 cell_proofs: Decodable::decode(buf)?,
1048 })
1049 }
1050
1051 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1053 let header = Header::decode(buf)?;
1054 if !header.list {
1055 return Err(alloy_rlp::Error::UnexpectedString);
1056 }
1057 if buf.len() < header.payload_length {
1058 return Err(alloy_rlp::Error::InputTooShort);
1059 }
1060 let remaining = buf.len();
1061
1062 let this = Self::decode_7594(buf)?;
1063 if buf.len() + header.payload_length != remaining {
1064 return Err(alloy_rlp::Error::UnexpectedLength);
1065 }
1066
1067 Ok(this)
1068 }
1069}
1070
1071impl Encodable for BlobTransactionSidecarEip7594 {
1072 fn encode(&self, out: &mut dyn BufMut) {
1074 self.rlp_encode(out);
1075 }
1076
1077 fn length(&self) -> usize {
1078 self.rlp_encoded_length()
1079 }
1080}
1081
1082impl Decodable for BlobTransactionSidecarEip7594 {
1083 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1086 Self::rlp_decode(buf)
1087 }
1088}
1089
1090impl Encodable7594 for BlobTransactionSidecarEip7594 {
1091 fn encode_7594_len(&self) -> usize {
1092 self.rlp_encoded_fields_length()
1093 }
1094
1095 fn encode_7594(&self, out: &mut dyn BufMut) {
1096 self.rlp_encode_fields(out);
1097 }
1098}
1099
1100impl Decodable7594 for BlobTransactionSidecarEip7594 {
1101 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1102 let wrapper_version: u8 = Decodable::decode(buf)?;
1103 if wrapper_version != EIP_7594_WRAPPER_VERSION {
1104 return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
1105 }
1106 Self::rlp_decode_fields(buf)
1107 }
1108}
1109
1110#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
1112pub mod serde_bincode_compat {
1113 use crate::eip4844::{Blob, Bytes48};
1114 use alloc::{borrow::Cow, vec::Vec};
1115 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1116 use serde_with::{DeserializeAs, SerializeAs};
1117
1118 #[derive(Debug, Serialize, Deserialize)]
1134 pub struct BlobTransactionSidecarVariant<'a> {
1135 pub blobs: Cow<'a, Vec<Blob>>,
1137 pub commitments: Cow<'a, Vec<Bytes48>>,
1139 pub proofs: Option<Cow<'a, Vec<Bytes48>>>,
1141 pub cell_proofs: Option<Cow<'a, Vec<Bytes48>>>,
1143 }
1144
1145 impl<'a> From<&'a super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'a> {
1146 fn from(value: &'a super::BlobTransactionSidecarVariant) -> Self {
1147 match value {
1148 super::BlobTransactionSidecarVariant::Eip4844(sidecar) => Self {
1149 blobs: Cow::Borrowed(&sidecar.blobs),
1150 commitments: Cow::Borrowed(&sidecar.commitments),
1151 proofs: Some(Cow::Borrowed(&sidecar.proofs)),
1152 cell_proofs: None,
1153 },
1154 super::BlobTransactionSidecarVariant::Eip7594(sidecar) => Self {
1155 blobs: Cow::Borrowed(&sidecar.blobs),
1156 commitments: Cow::Borrowed(&sidecar.commitments),
1157 proofs: None,
1158 cell_proofs: Some(Cow::Borrowed(&sidecar.cell_proofs)),
1159 },
1160 }
1161 }
1162 }
1163
1164 impl<'a> BlobTransactionSidecarVariant<'a> {
1165 fn try_into_inner(self) -> Result<super::BlobTransactionSidecarVariant, &'static str> {
1166 match (self.proofs, self.cell_proofs) {
1167 (Some(proofs), None) => Ok(super::BlobTransactionSidecarVariant::Eip4844(
1168 crate::eip4844::BlobTransactionSidecar {
1169 blobs: self.blobs.into_owned(),
1170 commitments: self.commitments.into_owned(),
1171 proofs: proofs.into_owned(),
1172 },
1173 )),
1174 (None, Some(cell_proofs)) => Ok(super::BlobTransactionSidecarVariant::Eip7594(
1175 super::BlobTransactionSidecarEip7594 {
1176 blobs: self.blobs.into_owned(),
1177 commitments: self.commitments.into_owned(),
1178 cell_proofs: cell_proofs.into_owned(),
1179 },
1180 )),
1181 (None, None) => Err("Missing both 'proofs' and 'cell_proofs'"),
1182 (Some(_), Some(_)) => Err("Both 'proofs' and 'cell_proofs' cannot be present"),
1183 }
1184 }
1185 }
1186
1187 impl<'a> From<BlobTransactionSidecarVariant<'a>> for super::BlobTransactionSidecarVariant {
1188 fn from(value: BlobTransactionSidecarVariant<'a>) -> Self {
1189 value.try_into_inner().expect("Invalid BlobTransactionSidecarVariant")
1190 }
1191 }
1192
1193 impl SerializeAs<super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'_> {
1194 fn serialize_as<S>(
1195 source: &super::BlobTransactionSidecarVariant,
1196 serializer: S,
1197 ) -> Result<S::Ok, S::Error>
1198 where
1199 S: Serializer,
1200 {
1201 BlobTransactionSidecarVariant::from(source).serialize(serializer)
1202 }
1203 }
1204
1205 impl<'de> DeserializeAs<'de, super::BlobTransactionSidecarVariant>
1206 for BlobTransactionSidecarVariant<'de>
1207 {
1208 fn deserialize_as<D>(
1209 deserializer: D,
1210 ) -> Result<super::BlobTransactionSidecarVariant, D::Error>
1211 where
1212 D: Deserializer<'de>,
1213 {
1214 let value = BlobTransactionSidecarVariant::deserialize(deserializer)?;
1215 value.try_into_inner().map_err(serde::de::Error::custom)
1216 }
1217 }
1218}
1219
1220#[cfg(test)]
1221mod tests {
1222 use super::*;
1223 #[cfg(feature = "kzg")]
1224 use crate::eip4844::{
1225 builder::{SidecarBuilder, SimpleCoder},
1226 env_settings::EnvKzgSettings,
1227 };
1228
1229 #[test]
1230 fn sidecar_variant_rlp_roundtrip() {
1231 let mut encoded = Vec::new();
1232
1233 let empty_sidecar_4844 =
1235 BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
1236 empty_sidecar_4844.encode(&mut encoded);
1237 assert_eq!(
1238 empty_sidecar_4844,
1239 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1240 );
1241
1242 let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
1243 vec![Blob::default()],
1244 vec![Bytes48::ZERO],
1245 vec![Bytes48::ZERO],
1246 ));
1247 encoded.clear();
1248 sidecar_4844.encode(&mut encoded);
1249 assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1250
1251 let empty_sidecar_7594 =
1253 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
1254 encoded.clear();
1255 empty_sidecar_7594.encode(&mut encoded);
1256 assert_eq!(
1257 empty_sidecar_7594,
1258 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1259 );
1260
1261 let sidecar_7594 =
1262 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
1263 vec![Blob::default()],
1264 vec![Bytes48::ZERO],
1265 core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
1266 ));
1267 encoded.clear();
1268 sidecar_7594.encode(&mut encoded);
1269 assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1270 }
1271
1272 #[test]
1273 #[cfg(feature = "serde")]
1274 fn sidecar_variant_json_deserialize_sanity() {
1275 let mut eip4844 = BlobTransactionSidecar::default();
1276 eip4844.blobs.push(Blob::repeat_byte(0x2));
1277
1278 let json = serde_json::to_string(&eip4844).unwrap();
1279 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1280 assert!(variant.is_eip4844());
1281 let jsonvariant = serde_json::to_string(&variant).unwrap();
1282 assert_eq!(json, jsonvariant);
1283
1284 let mut eip7594 = BlobTransactionSidecarEip7594::default();
1285 eip7594.blobs.push(Blob::repeat_byte(0x4));
1286 let json = serde_json::to_string(&eip7594).unwrap();
1287 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1288 assert!(variant.is_eip7594());
1289 let jsonvariant = serde_json::to_string(&variant).unwrap();
1290 assert_eq!(json, jsonvariant);
1291 }
1292
1293 #[test]
1294 fn rlp_7594_roundtrip() {
1295 let mut encoded = Vec::new();
1296
1297 let sidecar_4844 = BlobTransactionSidecar::default();
1298 sidecar_4844.encode_7594(&mut encoded);
1299 assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1300
1301 let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
1302 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1303 encoded.clear();
1304 sidecar_variant_4844.encode_7594(&mut encoded);
1305 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1306
1307 let sidecar_7594 = BlobTransactionSidecarEip7594::default();
1308 encoded.clear();
1309 sidecar_7594.encode_7594(&mut encoded);
1310 assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1311
1312 let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
1313 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1314 encoded.clear();
1315 sidecar_variant_7594.encode_7594(&mut encoded);
1316 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1317 }
1318
1319 #[test]
1320 #[cfg(feature = "kzg")]
1321 fn validate_7594_sidecar() {
1322 let sidecar =
1323 SidecarBuilder::<SimpleCoder>::from_slice(b"Blobs are fun!").build_7594().unwrap();
1324 let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1325
1326 sidecar.validate(&versioned_hashes, EnvKzgSettings::Default.get()).unwrap();
1327 }
1328
1329 #[test]
1330 #[cfg(feature = "kzg")]
1331 fn compute_cells_for_7594_sidecar() {
1332 let settings = EnvKzgSettings::Default.get();
1333 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1334 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1335 settings,
1336 )
1337 .unwrap();
1338
1339 let cells = sidecar.compute_cells_with_settings(settings).unwrap();
1340 assert_eq!(cells.len(), sidecar.blobs.len() * CELLS_PER_EXT_BLOB);
1341 assert_eq!(sidecar.compute_cells().unwrap(), cells);
1342
1343 for (blob_index, blob) in sidecar.blobs.iter().enumerate() {
1344 let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
1346 let expected_cells = settings.compute_cells(blob).unwrap();
1347 let start = blob_index * CELLS_PER_EXT_BLOB;
1348 let end = start + CELLS_PER_EXT_BLOB;
1349
1350 for (cell, expected_cell) in cells[start..end].iter().zip(expected_cells.iter()) {
1351 assert_eq!(*cell, Cell::new(expected_cell.to_bytes()));
1352 }
1353 }
1354 }
1355
1356 #[test]
1357 fn blob_cell_mask_selects_indices() {
1358 let selected = (1u128 << 0) | (1u128 << 7);
1359 let mask = BlobCellMask::new(B128::from(selected));
1360
1361 assert_eq!(mask.bits(), selected);
1362 assert_eq!(mask.count(), 2);
1363 assert!(mask.contains(0));
1364 assert!(mask.contains(7));
1365 assert!(!mask.contains(1));
1366 assert_eq!(mask.selected_indices().collect::<Vec<_>>(), vec![0, 7]);
1367 }
1368
1369 #[test]
1370 #[cfg(feature = "kzg")]
1371 fn match_versioned_hashes_cells_for_7594_sidecar() {
1372 let settings = EnvKzgSettings::Default.get();
1373 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1374 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1375 settings,
1376 )
1377 .unwrap();
1378 let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1379 let cell_mask = BlobCellMask::from_bits((1u128 << 0) | (1u128 << 7));
1380
1381 let cells_and_proofs =
1382 sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
1383 assert_eq!(cells_and_proofs.blob_cells.len(), 2);
1384 assert_eq!(cells_and_proofs.proofs.len(), 2);
1385 assert_eq!(
1386 cells_and_proofs.proofs,
1387 vec![Some(sidecar.cell_proofs[0]), Some(sidecar.cell_proofs[7])]
1388 );
1389
1390 let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(&sidecar.blobs[0]) };
1392 let expected_cells = settings.compute_cells(blob).unwrap();
1393 assert_eq!(
1394 cells_and_proofs.blob_cells,
1395 vec![
1396 Some(Cell::new(expected_cells[0].to_bytes())),
1397 Some(Cell::new(expected_cells[7].to_bytes()))
1398 ]
1399 );
1400
1401 let request = vec![versioned_hashes[0], B256::ZERO, versioned_hashes[0]];
1402 let matches = sidecar
1403 .match_versioned_hashes_cells_with_settings(&request, cell_mask, settings)
1404 .unwrap()
1405 .collect::<Vec<_>>();
1406 assert_eq!(matches.len(), 2);
1407 assert_eq!(matches[0], (0, cells_and_proofs.clone()));
1408 assert_eq!(matches[1], (2, cells_and_proofs.clone()));
1409
1410 let default_matches = sidecar
1411 .match_versioned_hashes_cells(&[versioned_hashes[0]], cell_mask)
1412 .unwrap()
1413 .collect::<Vec<_>>();
1414 assert_eq!(default_matches, vec![(0, cells_and_proofs)]);
1415 }
1416
1417 #[test]
1418 #[cfg(feature = "kzg")]
1419 fn match_versioned_hashes_cells_only_computes_matched_blobs() {
1420 let settings = EnvKzgSettings::Default.get();
1421 let mut sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1422 vec![Blob::repeat_byte(0x01)],
1423 settings,
1424 )
1425 .unwrap();
1426 let versioned_hash = sidecar.versioned_hashes().next().unwrap();
1427 let cell_mask = BlobCellMask::from_bits(1);
1428
1429 let invalid_blob = Blob::repeat_byte(0xff);
1430 let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(&invalid_blob) };
1432 assert!(settings.compute_cells(blob).is_err());
1433
1434 sidecar.blobs.push(invalid_blob);
1435 sidecar.commitments.push(Bytes48::ZERO);
1436 sidecar.cell_proofs.extend(core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB));
1437
1438 let cells_and_proofs =
1439 sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
1440 let matches = sidecar
1441 .match_versioned_hashes_cells_with_settings(&[versioned_hash], cell_mask, settings)
1442 .unwrap()
1443 .collect::<Vec<_>>();
1444 assert_eq!(matches, vec![(0, cells_and_proofs)]);
1445 }
1446}