1use crate::{
2 eip4844::{
3 Blob, BlobAndProofV2, BlobTransactionSidecar, Bytes48, BYTES_PER_BLOB,
4 BYTES_PER_COMMITMENT, BYTES_PER_PROOF,
5 },
6 eip7594::{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};
13use crate::eip4844::VersionedHashIter;
14#[cfg(feature = "kzg")]
15use crate::eip4844::{AsAlloy, AsCkzg, BlobTransactionValidationError};
16
17#[derive(Clone, PartialEq, Eq, Hash, Debug, derive_more::From)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize))]
24#[cfg_attr(feature = "serde", serde(untagged))]
25#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
26pub enum BlobTransactionSidecarVariant {
27 Eip4844(BlobTransactionSidecar),
29 Eip7594(BlobTransactionSidecarEip7594),
31}
32
33impl Default for BlobTransactionSidecarVariant {
34 fn default() -> Self {
35 Self::Eip4844(BlobTransactionSidecar::default())
36 }
37}
38
39impl BlobTransactionSidecarVariant {
40 pub const fn is_eip4844(&self) -> bool {
42 matches!(self, Self::Eip4844(_))
43 }
44
45 pub const fn is_eip7594(&self) -> bool {
47 matches!(self, Self::Eip7594(_))
48 }
49
50 pub const fn as_eip4844(&self) -> Option<&BlobTransactionSidecar> {
52 match self {
53 Self::Eip4844(sidecar) => Some(sidecar),
54 _ => None,
55 }
56 }
57
58 pub const fn as_eip7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
60 match self {
61 Self::Eip7594(sidecar) => Some(sidecar),
62 _ => None,
63 }
64 }
65
66 pub fn into_eip4844(self) -> Option<BlobTransactionSidecar> {
68 match self {
69 Self::Eip4844(sidecar) => Some(sidecar),
70 _ => None,
71 }
72 }
73
74 pub fn into_eip7594(self) -> Option<BlobTransactionSidecarEip7594> {
76 match self {
77 Self::Eip7594(sidecar) => Some(sidecar),
78 _ => None,
79 }
80 }
81
82 pub fn blobs(&self) -> &[Blob] {
84 match self {
85 Self::Eip4844(sidecar) => &sidecar.blobs,
86 Self::Eip7594(sidecar) => &sidecar.blobs,
87 }
88 }
89
90 pub fn into_blobs(self) -> Vec<Blob> {
92 match self {
93 Self::Eip4844(sidecar) => sidecar.blobs,
94 Self::Eip7594(sidecar) => sidecar.blobs,
95 }
96 }
97
98 pub fn clear_eip7594_blobs(&mut self) {
105 if let Self::Eip7594(sidecar) = self {
106 sidecar.clear_eip7594_blobs();
107 }
108 }
109
110 #[inline]
112 pub const fn size(&self) -> usize {
113 match self {
114 Self::Eip4844(sidecar) => sidecar.size(),
115 Self::Eip7594(sidecar) => sidecar.size(),
116 }
117 }
118
119 #[cfg(feature = "kzg")]
147 pub fn try_convert_into_eip7594(self) -> Result<Self, c_kzg::Error> {
148 self.try_convert_into_eip7594_with_settings(
149 crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
150 )
151 }
152
153 #[cfg(feature = "kzg")]
194 pub fn try_convert_into_eip7594_with_settings(
195 self,
196 settings: &c_kzg::KzgSettings,
197 ) -> Result<Self, c_kzg::Error> {
198 match self {
199 Self::Eip4844(legacy) => legacy.try_into_7594(settings).map(Self::Eip7594),
200 sidecar @ Self::Eip7594(_) => Ok(sidecar),
201 }
202 }
203
204 #[cfg(feature = "kzg")]
237 pub fn try_into_eip7594(self) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
238 self.try_into_eip7594_with_settings(
239 crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
240 )
241 }
242
243 #[cfg(feature = "kzg")]
288 pub fn try_into_eip7594_with_settings(
289 self,
290 settings: &c_kzg::KzgSettings,
291 ) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
292 match self {
293 Self::Eip4844(legacy) => legacy.try_into_7594(settings),
294 Self::Eip7594(sidecar) => Ok(sidecar),
295 }
296 }
297
298 #[cfg(feature = "kzg")]
300 pub fn validate(
301 &self,
302 blob_versioned_hashes: &[B256],
303 proof_settings: &c_kzg::KzgSettings,
304 ) -> Result<(), BlobTransactionValidationError> {
305 match self {
306 Self::Eip4844(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
307 Self::Eip7594(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
308 }
309 }
310
311 pub fn commitments(&self) -> &[Bytes48] {
313 match self {
314 Self::Eip4844(sidecar) => &sidecar.commitments,
315 Self::Eip7594(sidecar) => &sidecar.commitments,
316 }
317 }
318
319 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
321 VersionedHashIter::new(self.commitments())
322 }
323
324 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
326 match self {
327 Self::Eip4844(s) => s.versioned_hash_index(hash),
328 Self::Eip7594(s) => s.versioned_hash_index(hash),
329 }
330 }
331
332 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
334 match self {
335 Self::Eip4844(s) => s.blob_by_versioned_hash(hash),
336 Self::Eip7594(s) => s.blob_by_versioned_hash(hash),
337 }
338 }
339
340 #[doc(hidden)]
342 pub fn rlp_encoded_fields_length(&self) -> usize {
343 match self {
344 Self::Eip4844(sidecar) => sidecar.rlp_encoded_fields_length(),
345 Self::Eip7594(sidecar) => sidecar.rlp_encoded_fields_length(),
346 }
347 }
348
349 #[inline]
351 #[doc(hidden)]
352 pub fn rlp_encoded_fields(&self) -> Vec<u8> {
353 let mut buf = Vec::with_capacity(self.rlp_encoded_fields_length());
354 self.rlp_encode_fields(&mut buf);
355 buf
356 }
357
358 #[inline]
361 #[doc(hidden)]
362 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
363 match self {
364 Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
365 Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
366 }
367 }
368
369 #[doc(hidden)]
371 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
372 Self::decode_7594(buf)
373 }
374}
375
376impl Encodable for BlobTransactionSidecarVariant {
377 fn encode(&self, out: &mut dyn BufMut) {
379 match self {
380 Self::Eip4844(sidecar) => sidecar.encode(out),
381 Self::Eip7594(sidecar) => sidecar.encode(out),
382 }
383 }
384
385 fn length(&self) -> usize {
386 match self {
387 Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
388 Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
389 }
390 }
391}
392
393impl Decodable for BlobTransactionSidecarVariant {
394 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
396 let header = Header::decode(buf)?;
397 if !header.list {
398 return Err(alloy_rlp::Error::UnexpectedString);
399 }
400 if buf.len() < header.payload_length {
401 return Err(alloy_rlp::Error::InputTooShort);
402 }
403 let remaining = buf.len();
404 let this = Self::rlp_decode_fields(buf)?;
405 if buf.len() + header.payload_length != remaining {
406 return Err(alloy_rlp::Error::UnexpectedLength);
407 }
408
409 Ok(this)
410 }
411}
412
413impl Encodable7594 for BlobTransactionSidecarVariant {
414 fn encode_7594_len(&self) -> usize {
415 self.rlp_encoded_fields_length()
416 }
417
418 fn encode_7594(&self, out: &mut dyn BufMut) {
419 self.rlp_encode_fields(out);
420 }
421}
422
423impl Decodable7594 for BlobTransactionSidecarVariant {
424 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
425 if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
426 Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
427 } else {
428 Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
429 }
430 }
431}
432
433#[cfg(feature = "kzg")]
434impl TryFrom<BlobTransactionSidecarVariant> for BlobTransactionSidecarEip7594 {
435 type Error = c_kzg::Error;
436
437 fn try_from(value: BlobTransactionSidecarVariant) -> Result<Self, Self::Error> {
438 value.try_into_eip7594()
439 }
440}
441
442#[cfg(feature = "serde")]
443impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
444 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
445 where
446 D: serde::Deserializer<'de>,
447 {
448 use core::fmt;
449
450 #[derive(serde::Deserialize, fmt::Debug)]
451 #[serde(field_identifier, rename_all = "camelCase")]
452 enum Field {
453 Blobs,
454 Commitments,
455 Proofs,
456 CellProofs,
457 }
458
459 struct VariantVisitor;
460
461 impl<'de> serde::de::Visitor<'de> for VariantVisitor {
462 type Value = BlobTransactionSidecarVariant;
463
464 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
465 formatter
466 .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
467 }
468
469 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
470 where
471 M: serde::de::MapAccess<'de>,
472 {
473 let mut blobs = None;
474 let mut commitments = None;
475 let mut proofs = None;
476 let mut cell_proofs = None;
477
478 while let Some(key) = map.next_key()? {
479 match key {
480 Field::Blobs => {
481 blobs = Some(crate::eip4844::deserialize_blobs_map(&mut map)?);
482 }
483 Field::Commitments => commitments = Some(map.next_value()?),
484 Field::Proofs => proofs = Some(map.next_value()?),
485 Field::CellProofs => cell_proofs = Some(map.next_value()?),
486 }
487 }
488
489 let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
490 let commitments =
491 commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
492
493 match (cell_proofs, proofs) {
494 (Some(cp), None) => {
495 Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
496 blobs,
497 commitments,
498 cell_proofs: cp,
499 }))
500 }
501 (None, Some(pf)) => {
502 Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
503 blobs,
504 commitments,
505 proofs: pf,
506 }))
507 }
508 (None, None) => {
509 Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
510 }
511 (Some(_), Some(_)) => Err(serde::de::Error::custom(
512 "Both 'cellProofs' and 'proofs' cannot be present",
513 )),
514 }
515 }
516 }
517
518 const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
519 deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
520 }
521}
522
523#[derive(Clone, Default, PartialEq, Eq, Hash)]
533#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
534#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
535#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
536pub struct BlobTransactionSidecarEip7594 {
537 #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::eip4844::deserialize_blobs"))]
539 pub blobs: Vec<Blob>,
540 pub commitments: Vec<Bytes48>,
542 pub cell_proofs: Vec<Bytes48>,
547}
548
549impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
550 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
551 f.debug_struct("BlobTransactionSidecarEip7594")
552 .field("blobs", &self.blobs.len())
553 .field("commitments", &self.commitments)
554 .field("cell_proofs", &self.cell_proofs)
555 .finish()
556 }
557}
558
559impl BlobTransactionSidecarEip7594 {
560 pub const fn new(
562 blobs: Vec<Blob>,
563 commitments: Vec<Bytes48>,
564 cell_proofs: Vec<Bytes48>,
565 ) -> Self {
566 Self { blobs, commitments, cell_proofs }
567 }
568
569 #[cfg(feature = "kzg")]
582 pub fn try_recover_from_cells(
583 commitments: Vec<Bytes48>,
584 cell_mask: BlobCellMask,
585 cells: &[crate::eip7594::Cell],
586 ) -> Result<Self, BlobCellRecoveryError> {
587 use crate::eip4844::env_settings::EnvKzgSettings;
588
589 Self::try_recover_from_cells_with_settings(
590 commitments,
591 cell_mask,
592 cells,
593 EnvKzgSettings::Default.get(),
594 )
595 }
596
597 #[cfg(feature = "kzg")]
602 pub fn try_recover_from_cells_with_settings(
603 commitments: Vec<Bytes48>,
604 cell_mask: BlobCellMask,
605 cells: &[crate::eip7594::Cell],
606 settings: &c_kzg::KzgSettings,
607 ) -> Result<Self, BlobCellRecoveryError> {
608 let cells_per_blob = cell_mask.count();
609 if !commitments.is_empty() && cells_per_blob < CELLS_PER_EXT_BLOB / 2 {
610 return Err(BlobCellRecoveryError::InsufficientCells {
611 provided: cells_per_blob,
612 required: CELLS_PER_EXT_BLOB / 2,
613 });
614 }
615
616 let expected_cells = commitments
617 .len()
618 .checked_mul(cells_per_blob)
619 .ok_or(BlobCellRecoveryError::CellCountOverflow)?;
620 if cells.len() != expected_cells {
621 return Err(BlobCellRecoveryError::CellCountMismatch {
622 provided: cells.len(),
623 expected: expected_cells,
624 });
625 }
626 if commitments.is_empty() {
627 return Ok(Self::new(Vec::new(), commitments, Vec::new()));
628 }
629
630 let cell_indices =
631 cell_mask.selected_indices().map(|index| index as u64).collect::<Vec<_>>();
632 let mut blobs = Vec::with_capacity(commitments.len());
633 let cell_proof_capacity = commitments
634 .len()
635 .checked_mul(CELLS_PER_EXT_BLOB)
636 .ok_or(BlobCellRecoveryError::CellCountOverflow)?;
637 let mut cell_proofs = Vec::with_capacity(cell_proof_capacity);
638
639 for (blob_index, (blob_cells, expected_commitment)) in
640 cells.chunks_exact(cells_per_blob).zip(&commitments).enumerate()
641 {
642 let ckzg_cells = crate::eip7594::Cell::slice_as_ckzg(blob_cells);
643 let (recovered_cells, recovered_proofs) =
644 settings.recover_cells_and_kzg_proofs(&cell_indices, ckzg_cells)?;
645 let blob = reconstruct_blob(recovered_cells.as_ref());
646
647 let commitment = settings.blob_to_kzg_commitment(blob.as_ckzg())?;
648 let commitment = Bytes48::from_ckzg(commitment.to_bytes());
649 if commitment != *expected_commitment {
650 return Err(BlobCellRecoveryError::CommitmentMismatch { blob_index });
651 }
652
653 blobs.push(blob);
654 cell_proofs
655 .extend_from_slice(c_kzg::KzgProof::slice_as_alloy(recovered_proofs.as_ref()));
656 }
657
658 Ok(Self::new(blobs, commitments, cell_proofs))
659 }
660
661 pub fn clear_eip7594_blobs(&mut self) {
668 self.blobs.clear();
669 }
670
671 #[inline]
673 pub const fn size(&self) -> usize {
674 self.blobs.capacity() * BYTES_PER_BLOB
675 + self.commitments.capacity() * BYTES_PER_COMMITMENT
676 + self.cell_proofs.capacity() * BYTES_PER_PROOF
677 }
678
679 #[inline]
681 pub fn shrink_to_fit(&mut self) {
682 self.blobs.shrink_to_fit();
683 self.commitments.shrink_to_fit();
684 self.cell_proofs.shrink_to_fit();
685 }
686
687 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
691 pub fn try_from_blobs_hex<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
692 where
693 I: IntoIterator<Item = B>,
694 B: AsRef<str>,
695 {
696 let mut converted = Vec::new();
697 for blob in blobs {
698 converted.push(crate::eip4844::utils::hex_to_blob(blob)?);
699 }
700 Self::try_from_blobs(converted)
701 }
702
703 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
708 pub fn try_from_blobs_bytes<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
709 where
710 I: IntoIterator<Item = B>,
711 B: AsRef<[u8]>,
712 {
713 let mut converted = Vec::new();
714 for blob in blobs {
715 converted.push(crate::eip4844::utils::bytes_to_blob(blob)?);
716 }
717 Self::try_from_blobs(converted)
718 }
719
720 #[cfg(feature = "kzg")]
723 pub fn try_from_blobs_with_settings(
724 blobs: Vec<Blob>,
725 settings: &c_kzg::KzgSettings,
726 ) -> Result<Self, c_kzg::Error> {
727 if let [blob] = blobs.as_slice() {
728 let blob = blob.as_ckzg();
729 let commitment = settings.blob_to_kzg_commitment(blob)?;
730 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
731 let commitments = vec![Bytes48::from_ckzg(commitment.to_bytes())];
732 let proofs = c_kzg::KzgProof::boxed_slice_as_alloy(kzg_proofs).into();
733 return Ok(Self::new(blobs, commitments, proofs));
734 }
735
736 let mut commitments = Vec::with_capacity(blobs.len());
737 let mut proofs = Vec::with_capacity(blobs.len() * CELLS_PER_EXT_BLOB);
738 for blob in &blobs {
739 let blob = blob.as_ckzg();
740 let commitment = settings.blob_to_kzg_commitment(blob)?;
741 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
742
743 commitments.push(Bytes48::from_ckzg(commitment.to_bytes()));
744 proofs.extend_from_slice(c_kzg::KzgProof::slice_as_alloy(kzg_proofs.as_ref()));
745 }
746
747 Ok(Self::new(blobs, commitments, proofs))
748 }
749
750 #[cfg(feature = "kzg")]
756 pub fn try_from_blobs(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error> {
757 use crate::eip4844::env_settings::EnvKzgSettings;
758
759 Self::try_from_blobs_with_settings(blobs, EnvKzgSettings::Default.get())
760 }
761
762 #[cfg(feature = "kzg")]
770 pub fn compute_cells(&self) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
771 use crate::eip4844::env_settings::EnvKzgSettings;
772
773 self.compute_cells_with_settings(EnvKzgSettings::Default.get())
774 }
775
776 #[cfg(feature = "kzg")]
784 pub fn compute_cells_with_settings(
785 &self,
786 settings: &c_kzg::KzgSettings,
787 ) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
788 if let [blob] = self.blobs.as_slice() {
789 let blob_cells = settings.compute_cells(blob.as_ckzg())?;
790 return Ok(c_kzg::Cell::boxed_slice_as_alloy(blob_cells).into());
791 }
792
793 let mut cells = Vec::with_capacity(self.blobs.len() * CELLS_PER_EXT_BLOB);
794 for blob in &self.blobs {
795 let blob_cells = settings.compute_cells(blob.as_ckzg())?;
796 cells.extend_from_slice(c_kzg::Cell::slice_as_alloy(blob_cells.as_ref()));
797 }
798 Ok(cells)
799 }
800
801 #[cfg(feature = "kzg")]
809 pub fn compute_matching_cells(
810 &self,
811 cell_mask: BlobCellMask,
812 ) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
813 use crate::eip4844::env_settings::EnvKzgSettings;
814
815 self.compute_matching_cells_with_settings(cell_mask, EnvKzgSettings::Default.get())
816 }
817
818 #[cfg(feature = "kzg")]
824 pub fn compute_matching_cells_with_settings(
825 &self,
826 cell_mask: BlobCellMask,
827 settings: &c_kzg::KzgSettings,
828 ) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
829 let cells = self.compute_cells_with_settings(settings)?;
830 Ok(cell_mask
831 .matching_cells_from_computed_cells(&cells)
832 .expect("computed cells must contain full extended blob cell chunks"))
833 }
834
835 #[cfg(feature = "kzg")]
849 pub fn validate(
850 &self,
851 blob_versioned_hashes: &[B256],
852 proof_settings: &c_kzg::KzgSettings,
853 ) -> Result<(), BlobTransactionValidationError> {
854 if blob_versioned_hashes.len() != self.commitments.len() {
856 return Err(c_kzg::Error::MismatchLength(format!(
857 "There are {} versioned commitment hashes and {} commitments",
858 blob_versioned_hashes.len(),
859 self.commitments.len()
860 ))
861 .into());
862 }
863
864 let blobs_len = self.blobs.len();
865 let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
866 if self.cell_proofs.len() != expected_cell_proofs_len {
867 return Err(c_kzg::Error::MismatchLength(format!(
868 "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
869 self.cell_proofs.len(),
870 blobs_len,
871 expected_cell_proofs_len
872 ))
873 .into());
874 }
875
876 for (versioned_hash, commitment) in
878 blob_versioned_hashes.iter().zip(self.commitments.iter())
879 {
880 let calculated_versioned_hash =
882 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
883 if *versioned_hash != calculated_versioned_hash {
884 return Err(BlobTransactionValidationError::WrongVersionedHash {
885 have: *versioned_hash,
886 expected: calculated_versioned_hash,
887 });
888 }
889 }
890
891 let cell_indices =
893 Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
894
895 let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
897 for commitment in &self.commitments {
898 commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
899 }
900
901 let cells = if let [blob] = self.blobs.as_slice() {
902 let cells: Box<[c_kzg::Cell]> = proof_settings.compute_cells(blob.as_ckzg())?;
903 cells.into()
904 } else {
905 let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
906 for blob in &self.blobs {
907 let blob_cells = proof_settings.compute_cells(blob.as_ckzg())?;
908 cells.extend_from_slice(blob_cells.as_ref());
909 }
910 cells
911 };
912
913 let res = proof_settings.verify_cell_kzg_proof_batch(
914 Bytes48::slice_as_ckzg(&commitments),
915 &cell_indices,
916 &cells,
917 Bytes48::slice_as_ckzg(self.cell_proofs.as_slice()),
918 )?;
919
920 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
921 }
922
923 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
925 VersionedHashIter::new(&self.commitments)
926 }
927
928 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
930 self.commitments.iter().position(|commitment| {
931 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
932 })
933 }
934
935 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
937 self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
938 }
939
940 #[cfg(feature = "kzg")]
944 pub fn blob_cells_and_proofs(
945 &self,
946 blob_index: usize,
947 cell_mask: BlobCellMask,
948 ) -> Result<Option<crate::eip4844::BlobCellsAndProofsV1>, c_kzg::Error> {
949 use crate::eip4844::env_settings::EnvKzgSettings;
950
951 self.blob_cells_and_proofs_with_settings(
952 blob_index,
953 cell_mask,
954 EnvKzgSettings::Default.get(),
955 )
956 }
957
958 #[cfg(feature = "kzg")]
960 pub fn blob_cells_and_proofs_with_settings(
961 &self,
962 blob_index: usize,
963 cell_mask: BlobCellMask,
964 settings: &c_kzg::KzgSettings,
965 ) -> Result<Option<crate::eip4844::BlobCellsAndProofsV1>, c_kzg::Error> {
966 let Some(blob) = self.blobs.get(blob_index) else { return Ok(None) };
967
968 let proof_start = blob_index * CELLS_PER_EXT_BLOB;
969 let Some(proofs) = self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
970 else {
971 return Ok(None);
972 };
973
974 if cell_mask.count() == 0 {
975 return Ok(Some(crate::eip4844::BlobCellsAndProofsV1::default()));
976 }
977
978 let cells = settings.compute_cells(blob.as_ckzg())?;
979
980 Ok(Some(Self::blob_cells_and_proofs_from_computed_cells(cell_mask, cells.as_ref(), proofs)))
981 }
982
983 #[cfg(feature = "kzg")]
985 fn blob_cells_and_proofs_from_computed_cells(
986 cell_mask: BlobCellMask,
987 cells: &[c_kzg::Cell],
988 proofs: &[Bytes48],
989 ) -> crate::eip4844::BlobCellsAndProofsV1 {
990 let mut blob_cells = Vec::with_capacity(cell_mask.count());
993 let mut selected_proofs = Vec::with_capacity(cell_mask.count());
994 for cell_index in cell_mask.selected_indices() {
995 blob_cells
996 .push(cells.get(cell_index).map(|cell| crate::eip7594::Cell::new(cell.to_bytes())));
997 selected_proofs.push(proofs.get(cell_index).copied());
998 }
999
1000 crate::eip4844::BlobCellsAndProofsV1 { blob_cells, proofs: selected_proofs }
1001 }
1002
1003 pub fn match_versioned_hashes<'a>(
1009 &'a self,
1010 versioned_hashes: &'a [B256],
1011 ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
1012 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
1013 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
1014 if blob_versioned_hash == *target_hash {
1015 let maybe_blob = self.blobs.get(i);
1016 let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
1017 let maybe_proofs = self
1018 .cell_proofs
1019 .get(proof_range)
1020 .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
1021 if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
1022 return Some((
1023 j,
1024 BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
1025 ));
1026 }
1027 }
1028 None
1029 })
1030 })
1031 }
1032
1033 #[cfg(feature = "kzg")]
1041 pub fn match_versioned_hashes_cells<'a>(
1042 &'a self,
1043 versioned_hashes: &'a [B256],
1044 cell_mask: BlobCellMask,
1045 ) -> Result<
1046 impl Iterator<Item = (usize, crate::eip4844::BlobCellsAndProofsV1)> + 'a,
1047 c_kzg::Error,
1048 > {
1049 use crate::eip4844::env_settings::EnvKzgSettings;
1050
1051 self.match_versioned_hashes_cells_with_settings(
1052 versioned_hashes,
1053 cell_mask,
1054 EnvKzgSettings::Default.get(),
1055 )
1056 }
1057
1058 #[cfg(feature = "kzg")]
1062 pub fn match_versioned_hashes_cells_with_settings<'a>(
1063 &'a self,
1064 versioned_hashes: &'a [B256],
1065 cell_mask: BlobCellMask,
1066 settings: &c_kzg::KzgSettings,
1067 ) -> Result<
1068 impl Iterator<Item = (usize, crate::eip4844::BlobCellsAndProofsV1)> + 'a,
1069 c_kzg::Error,
1070 > {
1071 let mut matches = Vec::new();
1072 let mut cells_and_proofs_by_blob =
1073 Vec::<(usize, crate::eip4844::BlobCellsAndProofsV1)>::new();
1074
1075 for (blob_index, commitment) in self.commitments.iter().enumerate() {
1076 let blob_versioned_hash = crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
1077 for (matched_index, target_hash) in versioned_hashes.iter().enumerate() {
1078 if blob_versioned_hash != *target_hash {
1079 continue;
1080 }
1081
1082 let Some(blob) = self.blobs.get(blob_index) else { continue };
1083 let proof_start = blob_index * CELLS_PER_EXT_BLOB;
1084 let Some(proofs) =
1085 self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
1086 else {
1087 continue;
1088 };
1089
1090 let cells_and_proofs = if cell_mask.count() == 0 {
1091 crate::eip4844::BlobCellsAndProofsV1::default()
1092 } else if let Some((_, cells_and_proofs)) =
1093 cells_and_proofs_by_blob.iter().find(|(index, _)| *index == blob_index)
1094 {
1095 cells_and_proofs.clone()
1096 } else {
1097 let cells = settings.compute_cells(blob.as_ckzg())?;
1098 let cells_and_proofs = Self::blob_cells_and_proofs_from_computed_cells(
1099 cell_mask,
1100 cells.as_ref(),
1101 proofs,
1102 );
1103 cells_and_proofs_by_blob.push((blob_index, cells_and_proofs.clone()));
1104 cells_and_proofs
1105 };
1106
1107 matches.push((matched_index, cells_and_proofs));
1108 }
1109 }
1110
1111 Ok(matches.into_iter())
1112 }
1113
1114 #[doc(hidden)]
1116 pub fn rlp_encoded_fields_length(&self) -> usize {
1117 1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
1119 }
1120
1121 #[inline]
1130 #[doc(hidden)]
1131 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
1132 out.put_u8(EIP_7594_WRAPPER_VERSION);
1134 self.blobs.encode(out);
1136 self.commitments.encode(out);
1137 self.cell_proofs.encode(out);
1138 }
1139
1140 fn rlp_header(&self) -> Header {
1142 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
1143 }
1144
1145 pub fn rlp_encoded_length(&self) -> usize {
1148 self.rlp_header().length() + self.rlp_encoded_fields_length()
1149 }
1150
1151 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
1153 self.rlp_header().encode(out);
1154 self.rlp_encode_fields(out);
1155 }
1156
1157 #[doc(hidden)]
1159 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1160 Ok(Self {
1161 blobs: Decodable::decode(buf)?,
1162 commitments: Decodable::decode(buf)?,
1163 cell_proofs: Decodable::decode(buf)?,
1164 })
1165 }
1166
1167 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1169 let header = Header::decode(buf)?;
1170 if !header.list {
1171 return Err(alloy_rlp::Error::UnexpectedString);
1172 }
1173 if buf.len() < header.payload_length {
1174 return Err(alloy_rlp::Error::InputTooShort);
1175 }
1176 let remaining = buf.len();
1177
1178 let this = Self::decode_7594(buf)?;
1179 if buf.len() + header.payload_length != remaining {
1180 return Err(alloy_rlp::Error::UnexpectedLength);
1181 }
1182
1183 Ok(this)
1184 }
1185}
1186
1187#[cfg(feature = "kzg")]
1189#[derive(Debug)]
1190pub enum BlobCellRecoveryError {
1191 InsufficientCells {
1193 provided: usize,
1195 required: usize,
1197 },
1198 CellCountMismatch {
1200 provided: usize,
1202 expected: usize,
1204 },
1205 CellCountOverflow,
1207 CommitmentMismatch {
1209 blob_index: usize,
1211 },
1212 Kzg(c_kzg::Error),
1214}
1215
1216#[cfg(feature = "kzg")]
1217impl core::fmt::Display for BlobCellRecoveryError {
1218 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1219 match self {
1220 Self::InsufficientCells { provided, required } => {
1221 write!(f, "need at least {required} cells per blob for recovery, got {provided}")
1222 }
1223 Self::CellCountMismatch { provided, expected } => {
1224 write!(f, "expected {expected} cells, got {provided}")
1225 }
1226 Self::CellCountOverflow => f.write_str("the expected cell count overflows usize"),
1227 Self::CommitmentMismatch { blob_index } => {
1228 write!(f, "reconstructed blob {blob_index} does not match its commitment")
1229 }
1230 Self::Kzg(err) => write!(f, "KZG error: {err:?}"),
1231 }
1232 }
1233}
1234
1235#[cfg(feature = "kzg")]
1236impl core::error::Error for BlobCellRecoveryError {}
1237
1238#[cfg(feature = "kzg")]
1239impl From<c_kzg::Error> for BlobCellRecoveryError {
1240 fn from(source: c_kzg::Error) -> Self {
1241 Self::Kzg(source)
1242 }
1243}
1244
1245#[cfg(feature = "kzg")]
1246fn reconstruct_blob(recovered_cells: &[c_kzg::Cell; CELLS_PER_EXT_BLOB]) -> Blob {
1247 let mut blob = [0u8; BYTES_PER_BLOB];
1252 for (cell_index, cell) in recovered_cells.iter().take(CELLS_PER_EXT_BLOB / 2).enumerate() {
1253 let start = cell_index * crate::eip7594::BYTES_PER_CELL;
1254 let end = start + crate::eip7594::BYTES_PER_CELL;
1255 blob[start..end].copy_from_slice(cell.as_alloy().as_slice());
1256 }
1257 Blob::new(blob)
1258}
1259
1260impl Encodable for BlobTransactionSidecarEip7594 {
1261 fn encode(&self, out: &mut dyn BufMut) {
1263 self.rlp_encode(out);
1264 }
1265
1266 fn length(&self) -> usize {
1267 self.rlp_encoded_length()
1268 }
1269}
1270
1271impl Decodable for BlobTransactionSidecarEip7594 {
1272 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1274 Self::rlp_decode(buf)
1275 }
1276}
1277
1278impl Encodable7594 for BlobTransactionSidecarEip7594 {
1279 fn encode_7594_len(&self) -> usize {
1280 self.rlp_encoded_fields_length()
1281 }
1282
1283 fn encode_7594(&self, out: &mut dyn BufMut) {
1284 self.rlp_encode_fields(out);
1285 }
1286}
1287
1288impl Decodable7594 for BlobTransactionSidecarEip7594 {
1289 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1290 let wrapper_version: u8 = Decodable::decode(buf)?;
1291 if wrapper_version != EIP_7594_WRAPPER_VERSION {
1292 return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
1293 }
1294 Self::rlp_decode_fields(buf)
1295 }
1296}
1297
1298#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
1300pub struct BlobCellMask {
1301 value: u128,
1302}
1303
1304impl BlobCellMask {
1305 #[inline]
1307 pub fn new(indices_bitarray: B128) -> Self {
1308 Self { value: u128::from(indices_bitarray) }
1309 }
1310
1311 #[inline]
1313 pub const fn from_bits(value: u128) -> Self {
1314 Self { value }
1315 }
1316
1317 #[inline]
1319 pub const fn bits(self) -> u128 {
1320 self.value
1321 }
1322
1323 #[inline]
1325 pub const fn count(self) -> usize {
1326 self.value.count_ones() as usize
1327 }
1328
1329 #[inline]
1331 pub const fn contains(self, index: usize) -> bool {
1332 index < CELLS_PER_EXT_BLOB && self.value & (1u128 << index) != 0
1333 }
1334
1335 #[inline]
1337 pub fn selected_indices(self) -> impl Iterator<Item = usize> {
1338 let mut bits = self.value;
1339 core::iter::from_fn(move || {
1340 if bits == 0 {
1341 return None;
1342 }
1343
1344 let index = bits.trailing_zeros() as usize;
1345 bits &= bits - 1;
1346 Some(index)
1347 })
1348 }
1349
1350 pub fn matching_cells_from_computed_cells(
1360 self,
1361 cells: &[crate::eip7594::Cell],
1362 ) -> Option<Vec<crate::eip7594::Cell>> {
1363 let (chunks, remainder) = cells.as_chunks::<CELLS_PER_EXT_BLOB>();
1364 if !remainder.is_empty() {
1365 return None;
1366 }
1367
1368 let mut matching_cells = Vec::with_capacity(chunks.len() * self.count());
1369 for blob_cells in chunks {
1370 for cell_index in self.selected_indices() {
1371 let cell = blob_cells
1372 .get(cell_index)
1373 .expect("cell mask index must be within extended blob cells");
1374 matching_cells.push(*cell);
1375 }
1376 }
1377
1378 Some(matching_cells)
1379 }
1380}
1381
1382#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
1384pub mod serde_bincode_compat {
1385 use crate::eip4844::{Blob, Bytes48};
1386 use alloc::{borrow::Cow, vec::Vec};
1387 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1388 use serde_with::{DeserializeAs, SerializeAs};
1389
1390 #[derive(Debug, Serialize, Deserialize)]
1406 pub struct BlobTransactionSidecarVariant<'a> {
1407 pub blobs: Cow<'a, Vec<Blob>>,
1409 pub commitments: Cow<'a, Vec<Bytes48>>,
1411 pub proofs: Option<Cow<'a, Vec<Bytes48>>>,
1413 pub cell_proofs: Option<Cow<'a, Vec<Bytes48>>>,
1415 }
1416
1417 impl<'a> From<&'a super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'a> {
1418 fn from(value: &'a super::BlobTransactionSidecarVariant) -> Self {
1419 match value {
1420 super::BlobTransactionSidecarVariant::Eip4844(sidecar) => Self {
1421 blobs: Cow::Borrowed(&sidecar.blobs),
1422 commitments: Cow::Borrowed(&sidecar.commitments),
1423 proofs: Some(Cow::Borrowed(&sidecar.proofs)),
1424 cell_proofs: None,
1425 },
1426 super::BlobTransactionSidecarVariant::Eip7594(sidecar) => Self {
1427 blobs: Cow::Borrowed(&sidecar.blobs),
1428 commitments: Cow::Borrowed(&sidecar.commitments),
1429 proofs: None,
1430 cell_proofs: Some(Cow::Borrowed(&sidecar.cell_proofs)),
1431 },
1432 }
1433 }
1434 }
1435
1436 impl<'a> BlobTransactionSidecarVariant<'a> {
1437 fn try_into_inner(self) -> Result<super::BlobTransactionSidecarVariant, &'static str> {
1438 match (self.proofs, self.cell_proofs) {
1439 (Some(proofs), None) => Ok(super::BlobTransactionSidecarVariant::Eip4844(
1440 crate::eip4844::BlobTransactionSidecar {
1441 blobs: self.blobs.into_owned(),
1442 commitments: self.commitments.into_owned(),
1443 proofs: proofs.into_owned(),
1444 },
1445 )),
1446 (None, Some(cell_proofs)) => Ok(super::BlobTransactionSidecarVariant::Eip7594(
1447 super::BlobTransactionSidecarEip7594 {
1448 blobs: self.blobs.into_owned(),
1449 commitments: self.commitments.into_owned(),
1450 cell_proofs: cell_proofs.into_owned(),
1451 },
1452 )),
1453 (None, None) => Err("Missing both 'proofs' and 'cell_proofs'"),
1454 (Some(_), Some(_)) => Err("Both 'proofs' and 'cell_proofs' cannot be present"),
1455 }
1456 }
1457 }
1458
1459 impl<'a> From<BlobTransactionSidecarVariant<'a>> for super::BlobTransactionSidecarVariant {
1460 fn from(value: BlobTransactionSidecarVariant<'a>) -> Self {
1461 value.try_into_inner().expect("Invalid BlobTransactionSidecarVariant")
1462 }
1463 }
1464
1465 impl SerializeAs<super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'_> {
1466 fn serialize_as<S>(
1467 source: &super::BlobTransactionSidecarVariant,
1468 serializer: S,
1469 ) -> Result<S::Ok, S::Error>
1470 where
1471 S: Serializer,
1472 {
1473 BlobTransactionSidecarVariant::from(source).serialize(serializer)
1474 }
1475 }
1476
1477 impl<'de> DeserializeAs<'de, super::BlobTransactionSidecarVariant>
1478 for BlobTransactionSidecarVariant<'de>
1479 {
1480 fn deserialize_as<D>(
1481 deserializer: D,
1482 ) -> Result<super::BlobTransactionSidecarVariant, D::Error>
1483 where
1484 D: Deserializer<'de>,
1485 {
1486 let value = BlobTransactionSidecarVariant::deserialize(deserializer)?;
1487 value.try_into_inner().map_err(serde::de::Error::custom)
1488 }
1489 }
1490}
1491
1492#[cfg(test)]
1493mod tests {
1494 use super::*;
1495 #[cfg(feature = "kzg")]
1496 use crate::eip4844::{
1497 builder::{SidecarBuilder, SimpleCoder},
1498 env_settings::EnvKzgSettings,
1499 };
1500
1501 #[test]
1502 fn clear_eip7594_blobs_preserves_metadata() {
1503 let commitments = vec![Bytes48::repeat_byte(0x01)];
1504 let cell_proofs = vec![Bytes48::repeat_byte(0x02); CELLS_PER_EXT_BLOB];
1505 let sidecar = BlobTransactionSidecarEip7594::new(
1506 vec![Blob::repeat_byte(0x03)],
1507 commitments.clone(),
1508 cell_proofs.clone(),
1509 );
1510 let mut variant = BlobTransactionSidecarVariant::Eip7594(sidecar);
1511
1512 variant.clear_eip7594_blobs();
1513
1514 let sidecar = variant.as_eip7594().unwrap();
1515 assert!(sidecar.blobs.is_empty());
1516 assert_eq!(sidecar.commitments, commitments);
1517 assert_eq!(sidecar.cell_proofs, cell_proofs);
1518 }
1519
1520 #[test]
1521 fn clear_eip7594_blobs_ignores_eip4844_variant() {
1522 let sidecar = BlobTransactionSidecar::new(
1523 vec![Blob::repeat_byte(0x01)],
1524 vec![Bytes48::repeat_byte(0x02)],
1525 vec![Bytes48::repeat_byte(0x03)],
1526 );
1527 let mut variant = BlobTransactionSidecarVariant::Eip4844(sidecar.clone());
1528
1529 variant.clear_eip7594_blobs();
1530
1531 assert_eq!(variant, BlobTransactionSidecarVariant::Eip4844(sidecar));
1532 }
1533
1534 #[test]
1535 fn sidecar_variant_rlp_roundtrip() {
1536 let mut encoded = Vec::new();
1537
1538 let empty_sidecar_4844 =
1540 BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
1541 empty_sidecar_4844.encode(&mut encoded);
1542 assert_eq!(
1543 empty_sidecar_4844,
1544 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1545 );
1546
1547 let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
1548 vec![Blob::default()],
1549 vec![Bytes48::ZERO],
1550 vec![Bytes48::ZERO],
1551 ));
1552 encoded.clear();
1553 sidecar_4844.encode(&mut encoded);
1554 assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1555
1556 let empty_sidecar_7594 =
1558 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
1559 encoded.clear();
1560 empty_sidecar_7594.encode(&mut encoded);
1561 assert_eq!(
1562 empty_sidecar_7594,
1563 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1564 );
1565
1566 let sidecar_7594 =
1567 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
1568 vec![Blob::default()],
1569 vec![Bytes48::ZERO],
1570 core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
1571 ));
1572 encoded.clear();
1573 sidecar_7594.encode(&mut encoded);
1574 assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1575 }
1576
1577 #[test]
1578 #[cfg(feature = "serde")]
1579 fn sidecar_variant_json_deserialize_sanity() {
1580 let mut eip4844 = BlobTransactionSidecar::default();
1581 eip4844.blobs.push(Blob::repeat_byte(0x2));
1582
1583 let json = serde_json::to_string(&eip4844).unwrap();
1584 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1585 assert!(variant.is_eip4844());
1586 let jsonvariant = serde_json::to_string(&variant).unwrap();
1587 assert_eq!(json, jsonvariant);
1588
1589 let mut eip7594 = BlobTransactionSidecarEip7594::default();
1590 eip7594.blobs.push(Blob::repeat_byte(0x4));
1591 let json = serde_json::to_string(&eip7594).unwrap();
1592 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1593 assert!(variant.is_eip7594());
1594 let jsonvariant = serde_json::to_string(&variant).unwrap();
1595 assert_eq!(json, jsonvariant);
1596 }
1597
1598 #[test]
1599 fn rlp_7594_roundtrip() {
1600 let mut encoded = Vec::new();
1601
1602 let sidecar_4844 = BlobTransactionSidecar::default();
1603 sidecar_4844.encode_7594(&mut encoded);
1604 assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1605
1606 let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
1607 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1608 encoded.clear();
1609 sidecar_variant_4844.encode_7594(&mut encoded);
1610 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1611
1612 let sidecar_7594 = BlobTransactionSidecarEip7594::default();
1613 encoded.clear();
1614 sidecar_7594.encode_7594(&mut encoded);
1615 assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1616
1617 let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
1618 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1619 encoded.clear();
1620 sidecar_variant_7594.encode_7594(&mut encoded);
1621 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1622 }
1623
1624 #[test]
1625 #[cfg(feature = "kzg")]
1626 fn validate_7594_sidecar() {
1627 let sidecar =
1628 SidecarBuilder::<SimpleCoder>::from_slice(b"Blobs are fun!").build_7594().unwrap();
1629 let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1630
1631 sidecar.validate(&versioned_hashes, EnvKzgSettings::Default.get()).unwrap();
1632 }
1633
1634 #[test]
1635 #[cfg(feature = "kzg")]
1636 fn compute_cells_for_7594_sidecar() {
1637 let settings = EnvKzgSettings::Default.get();
1638 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1639 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1640 settings,
1641 )
1642 .unwrap();
1643
1644 let cells = sidecar.compute_cells_with_settings(settings).unwrap();
1645 assert_eq!(cells.len(), sidecar.blobs.len() * CELLS_PER_EXT_BLOB);
1646 assert_eq!(sidecar.compute_cells().unwrap(), cells);
1647
1648 let cell_mask = BlobCellMask::from_bits((1u128 << 0) | (1u128 << 7));
1649 let matching_cells =
1650 sidecar.compute_matching_cells_with_settings(cell_mask, settings).unwrap();
1651 let expected_matching_cells = cells
1652 .as_chunks::<CELLS_PER_EXT_BLOB>()
1653 .0
1654 .iter()
1655 .flat_map(|blob_cells| [blob_cells[0], blob_cells[7]])
1656 .collect::<Vec<_>>();
1657 assert_eq!(
1658 cell_mask.matching_cells_from_computed_cells(&cells),
1659 Some(expected_matching_cells.clone())
1660 );
1661 assert_eq!(matching_cells, expected_matching_cells);
1662 assert_eq!(sidecar.compute_matching_cells(cell_mask).unwrap(), expected_matching_cells);
1663 assert!(sidecar.compute_matching_cells(BlobCellMask::default()).unwrap().is_empty());
1664
1665 for (blob_index, blob) in sidecar.blobs.iter().enumerate() {
1666 let expected_cells = settings.compute_cells(blob.as_ckzg()).unwrap();
1667 let start = blob_index * CELLS_PER_EXT_BLOB;
1668 let end = start + CELLS_PER_EXT_BLOB;
1669
1670 for (cell, expected_cell) in cells[start..end].iter().zip(expected_cells.iter()) {
1671 assert_eq!(*cell, crate::eip7594::Cell::new(expected_cell.to_bytes()));
1672 }
1673 }
1674 }
1675
1676 #[cfg(feature = "kzg")]
1677 fn sparse_cells_for_mask(
1678 sidecar: &BlobTransactionSidecarEip7594,
1679 cell_mask: BlobCellMask,
1680 settings: &c_kzg::KzgSettings,
1681 ) -> Vec<crate::eip7594::Cell> {
1682 let cells = sidecar.compute_cells_with_settings(settings).unwrap();
1683 cell_mask.matching_cells_from_computed_cells(&cells).unwrap()
1684 }
1685
1686 #[test]
1687 #[cfg(feature = "kzg")]
1688 fn recover_sidecar_from_complete_cells() {
1689 let settings = EnvKzgSettings::Default.get();
1690 assert_eq!(
1691 BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1692 Vec::new(),
1693 BlobCellMask::default(),
1694 &[],
1695 settings,
1696 )
1697 .unwrap(),
1698 BlobTransactionSidecarEip7594::default()
1699 );
1700
1701 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1702 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1703 settings,
1704 )
1705 .unwrap();
1706 let cells = sidecar.compute_cells_with_settings(settings).unwrap();
1707 let cell_mask = BlobCellMask::from_bits(u128::MAX);
1708
1709 let recovered = BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1710 sidecar.commitments.clone(),
1711 cell_mask,
1712 &cells,
1713 settings,
1714 )
1715 .unwrap();
1716 assert_eq!(recovered, sidecar);
1717
1718 assert_eq!(
1719 BlobTransactionSidecarEip7594::try_recover_from_cells(
1720 recovered.commitments.clone(),
1721 cell_mask,
1722 &cells,
1723 )
1724 .unwrap(),
1725 recovered
1726 );
1727 }
1728
1729 #[test]
1732 #[cfg(feature = "kzg")]
1733 fn recover_sparse_blobs_from_minimum_cells() {
1734 let settings = EnvKzgSettings::Default.get();
1735 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1736 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02), Blob::repeat_byte(0x03)],
1737 settings,
1738 )
1739 .unwrap();
1740
1741 let cell_mask = BlobCellMask::from_bits(
1742 (0..CELLS_PER_EXT_BLOB).step_by(2).fold(0, |mask, index| mask | (1u128 << index)),
1743 );
1744 assert_eq!(cell_mask.count(), CELLS_PER_EXT_BLOB / 2);
1745 let sparse_cells = sparse_cells_for_mask(&sidecar, cell_mask, settings);
1746
1747 let recovered = BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1748 sidecar.commitments.clone(),
1749 cell_mask,
1750 &sparse_cells,
1751 settings,
1752 )
1753 .unwrap();
1754
1755 assert_eq!(recovered.blobs, sidecar.blobs);
1756 assert_eq!(recovered.commitments, sidecar.commitments);
1757 assert_eq!(recovered.cell_proofs, sidecar.cell_proofs);
1758 }
1759
1760 #[test]
1762 #[cfg(feature = "kzg")]
1763 fn recover_sparse_blobs_with_more_than_minimum_cells() {
1764 let settings = EnvKzgSettings::Default.get();
1765 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1766 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1767 settings,
1768 )
1769 .unwrap();
1770
1771 let cell_mask = BlobCellMask::from_bits(
1772 ((1u128 << (CELLS_PER_EXT_BLOB / 2)) - 1) | (1u128 << (CELLS_PER_EXT_BLOB - 1)),
1773 );
1774 assert_eq!(cell_mask.count(), CELLS_PER_EXT_BLOB / 2 + 1);
1775 let sparse_cells = sparse_cells_for_mask(&sidecar, cell_mask, settings);
1776
1777 let recovered = BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1778 sidecar.commitments.clone(),
1779 cell_mask,
1780 &sparse_cells,
1781 settings,
1782 )
1783 .unwrap();
1784
1785 assert_eq!(recovered.blobs, sidecar.blobs);
1786 assert_eq!(recovered.cell_proofs, sidecar.cell_proofs);
1787 }
1788
1789 #[test]
1790 #[cfg(feature = "kzg")]
1791 fn recover_sparse_blobs_rejects_insufficient_cells() {
1792 let settings = EnvKzgSettings::Default.get();
1793 let cell_mask = BlobCellMask::from_bits((1u128 << (CELLS_PER_EXT_BLOB / 2 - 1)) - 1);
1794 let cells = vec![crate::eip7594::Cell::repeat_byte(0); cell_mask.count()];
1795
1796 let err = BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1797 vec![Bytes48::ZERO],
1798 cell_mask,
1799 &cells,
1800 settings,
1801 )
1802 .unwrap_err();
1803 assert!(matches!(
1804 err,
1805 BlobCellRecoveryError::InsufficientCells {
1806 provided,
1807 required,
1808 } if provided == CELLS_PER_EXT_BLOB / 2 - 1
1809 && required == CELLS_PER_EXT_BLOB / 2
1810 ));
1811 }
1812
1813 #[test]
1814 #[cfg(feature = "kzg")]
1815 fn recover_sparse_blobs_rejects_mismatched_cell_count() {
1816 let settings = EnvKzgSettings::Default.get();
1817 let cell_mask = BlobCellMask::from_bits((1u128 << (CELLS_PER_EXT_BLOB / 2)) - 1);
1818 let cells = vec![crate::eip7594::Cell::repeat_byte(0); cell_mask.count() - 1];
1819
1820 let err = BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1821 vec![Bytes48::ZERO, Bytes48::ZERO],
1822 cell_mask,
1823 &cells,
1824 settings,
1825 )
1826 .unwrap_err();
1827 assert!(matches!(
1828 err,
1829 BlobCellRecoveryError::CellCountMismatch {
1830 provided,
1831 expected,
1832 } if provided == CELLS_PER_EXT_BLOB / 2 - 1 && expected == CELLS_PER_EXT_BLOB
1833 ));
1834 }
1835
1836 #[test]
1837 #[cfg(feature = "kzg")]
1838 fn recover_sparse_blobs_rejects_commitment_mismatch() {
1839 let settings = EnvKzgSettings::Default.get();
1840 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1841 vec![Blob::repeat_byte(0x01)],
1842 settings,
1843 )
1844 .unwrap();
1845 let cell_mask = BlobCellMask::from_bits((1u128 << (CELLS_PER_EXT_BLOB / 2)) - 1);
1846 let sparse_cells = sparse_cells_for_mask(&sidecar, cell_mask, settings);
1847
1848 let err = BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1849 vec![Bytes48::ZERO],
1850 cell_mask,
1851 &sparse_cells,
1852 settings,
1853 )
1854 .unwrap_err();
1855 assert!(matches!(err, BlobCellRecoveryError::CommitmentMismatch { blob_index: 0 }));
1856 }
1857
1858 #[test]
1860 #[cfg(feature = "kzg")]
1861 fn recover_sparse_blobs_rejects_corrupted_cells() {
1862 let settings = EnvKzgSettings::Default.get();
1863 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1864 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02), Blob::repeat_byte(0x03)],
1865 settings,
1866 )
1867 .unwrap();
1868 let cell_mask = BlobCellMask::from_bits((1u128 << (CELLS_PER_EXT_BLOB / 2)) - 1);
1869 let mut sparse_cells = sparse_cells_for_mask(&sidecar, cell_mask, settings);
1870 sparse_cells[0][0] ^= 0xff;
1871
1872 assert!(BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1873 sidecar.commitments,
1874 cell_mask,
1875 &sparse_cells,
1876 settings,
1877 )
1878 .is_err());
1879 }
1880
1881 #[test]
1882 fn blob_cell_mask_selects_indices() {
1883 let selected = (1u128 << 0) | (1u128 << 7);
1884 let mask = BlobCellMask::new(B128::from(selected));
1885
1886 assert_eq!(mask.bits(), selected);
1887 assert_eq!(mask.count(), 2);
1888 assert!(mask.contains(0));
1889 assert!(mask.contains(7));
1890 assert!(!mask.contains(1));
1891 assert_eq!(mask.selected_indices().collect::<Vec<_>>(), vec![0, 7]);
1892
1893 let cells = (0..CELLS_PER_EXT_BLOB * 2)
1894 .map(|i| crate::eip7594::Cell::repeat_byte(i as u8))
1895 .collect::<Vec<_>>();
1896 assert_eq!(
1897 mask.matching_cells_from_computed_cells(&cells),
1898 Some(vec![
1899 cells[0],
1900 cells[7],
1901 cells[CELLS_PER_EXT_BLOB],
1902 cells[CELLS_PER_EXT_BLOB + 7]
1903 ])
1904 );
1905 assert_eq!(mask.matching_cells_from_computed_cells(&cells[..cells.len() - 1]), None);
1906 }
1907
1908 #[test]
1909 fn match_versioned_hashes_skips_incomplete_proof_chunks() {
1910 let sidecar = BlobTransactionSidecarEip7594::new(
1911 vec![Blob::repeat_byte(0x01)],
1912 vec![Bytes48::repeat_byte(0x02)],
1913 vec![Bytes48::repeat_byte(0x03)],
1914 );
1915 let versioned_hash = sidecar.versioned_hashes().next().unwrap();
1916
1917 let matches = sidecar.match_versioned_hashes(&[versioned_hash]).collect::<Vec<_>>();
1918 assert!(matches.is_empty());
1919 }
1920
1921 #[test]
1922 #[cfg(feature = "kzg")]
1923 fn match_versioned_hashes_cells_for_7594_sidecar() {
1924 let settings = EnvKzgSettings::Default.get();
1925 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1926 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1927 settings,
1928 )
1929 .unwrap();
1930 let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1931 let cell_mask = BlobCellMask::from_bits((1u128 << 0) | (1u128 << 7));
1932
1933 let cells_and_proofs =
1934 sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
1935 assert_eq!(cells_and_proofs.blob_cells.len(), 2);
1936 assert_eq!(cells_and_proofs.proofs.len(), 2);
1937 assert_eq!(
1938 cells_and_proofs.proofs,
1939 vec![Some(sidecar.cell_proofs[0]), Some(sidecar.cell_proofs[7])]
1940 );
1941
1942 let expected_cells = settings.compute_cells(sidecar.blobs[0].as_ckzg()).unwrap();
1943 assert_eq!(
1944 cells_and_proofs.blob_cells,
1945 vec![
1946 Some(crate::eip7594::Cell::new(expected_cells[0].to_bytes())),
1947 Some(crate::eip7594::Cell::new(expected_cells[7].to_bytes()))
1948 ]
1949 );
1950
1951 let request = vec![versioned_hashes[0], B256::ZERO, versioned_hashes[0]];
1952 let matches = sidecar
1953 .match_versioned_hashes_cells_with_settings(&request, cell_mask, settings)
1954 .unwrap()
1955 .collect::<Vec<_>>();
1956 assert_eq!(matches.len(), 2);
1957 assert_eq!(matches[0], (0, cells_and_proofs.clone()));
1958 assert_eq!(matches[1], (2, cells_and_proofs.clone()));
1959
1960 let default_matches = sidecar
1961 .match_versioned_hashes_cells(&[versioned_hashes[0]], cell_mask)
1962 .unwrap()
1963 .collect::<Vec<_>>();
1964 assert_eq!(default_matches, vec![(0, cells_and_proofs)]);
1965 }
1966
1967 #[test]
1968 #[cfg(feature = "kzg")]
1969 fn match_versioned_hashes_cells_only_computes_matched_blobs() {
1970 let settings = EnvKzgSettings::Default.get();
1971 let mut sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1972 vec![Blob::repeat_byte(0x01)],
1973 settings,
1974 )
1975 .unwrap();
1976 let versioned_hash = sidecar.versioned_hashes().next().unwrap();
1977 let cell_mask = BlobCellMask::from_bits(1);
1978
1979 let invalid_blob = Blob::repeat_byte(0xff);
1980 assert!(settings.compute_cells(invalid_blob.as_ckzg()).is_err());
1981
1982 sidecar.blobs.push(invalid_blob);
1983 sidecar.commitments.push(Bytes48::ZERO);
1984 sidecar.cell_proofs.extend(core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB));
1985
1986 let cells_and_proofs =
1987 sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
1988 let matches = sidecar
1989 .match_versioned_hashes_cells_with_settings(&[versioned_hash], cell_mask, settings)
1990 .unwrap()
1991 .collect::<Vec<_>>();
1992 assert_eq!(matches, vec![(0, cells_and_proofs)]);
1993 }
1994}