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, EMPTY_LIST_CODE};
11
12use super::{BlobSidecarEncoding, 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 fn encode_7594_len_with(&self, encoding: BlobSidecarEncoding) -> usize {
423 match self {
424 Self::Eip4844(sidecar) => sidecar.encode_7594_len_with(encoding),
425 Self::Eip7594(sidecar) => sidecar.encode_7594_len_with(encoding),
426 }
427 }
428
429 fn encode_7594_with(&self, encoding: BlobSidecarEncoding, out: &mut dyn BufMut) {
430 match self {
431 Self::Eip4844(sidecar) => sidecar.encode_7594_with(encoding, out),
432 Self::Eip7594(sidecar) => sidecar.encode_7594_with(encoding, out),
433 }
434 }
435}
436
437impl Decodable7594 for BlobTransactionSidecarVariant {
438 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
439 if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
440 Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
441 } else {
442 Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
443 }
444 }
445}
446
447#[cfg(feature = "kzg")]
448impl TryFrom<BlobTransactionSidecarVariant> for BlobTransactionSidecarEip7594 {
449 type Error = c_kzg::Error;
450
451 fn try_from(value: BlobTransactionSidecarVariant) -> Result<Self, Self::Error> {
452 value.try_into_eip7594()
453 }
454}
455
456#[cfg(feature = "serde")]
457impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
458 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
459 where
460 D: serde::Deserializer<'de>,
461 {
462 use core::fmt;
463
464 #[derive(serde::Deserialize, fmt::Debug)]
465 #[serde(field_identifier, rename_all = "camelCase")]
466 enum Field {
467 Blobs,
468 Commitments,
469 Proofs,
470 CellProofs,
471 }
472
473 struct VariantVisitor;
474
475 impl<'de> serde::de::Visitor<'de> for VariantVisitor {
476 type Value = BlobTransactionSidecarVariant;
477
478 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
479 formatter
480 .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
481 }
482
483 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
484 where
485 M: serde::de::MapAccess<'de>,
486 {
487 let mut blobs = None;
488 let mut commitments = None;
489 let mut proofs = None;
490 let mut cell_proofs = None;
491
492 while let Some(key) = map.next_key()? {
493 match key {
494 Field::Blobs => {
495 blobs = Some(crate::eip4844::deserialize_blobs_map(&mut map)?);
496 }
497 Field::Commitments => commitments = Some(map.next_value()?),
498 Field::Proofs => proofs = Some(map.next_value()?),
499 Field::CellProofs => cell_proofs = Some(map.next_value()?),
500 }
501 }
502
503 let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
504 let commitments =
505 commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
506
507 match (cell_proofs, proofs) {
508 (Some(cp), None) => {
509 Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
510 blobs,
511 commitments,
512 cell_proofs: cp,
513 }))
514 }
515 (None, Some(pf)) => {
516 Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
517 blobs,
518 commitments,
519 proofs: pf,
520 }))
521 }
522 (None, None) => {
523 Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
524 }
525 (Some(_), Some(_)) => Err(serde::de::Error::custom(
526 "Both 'cellProofs' and 'proofs' cannot be present",
527 )),
528 }
529 }
530 }
531
532 const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
533 deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
534 }
535}
536
537#[derive(Clone, Default, PartialEq, Eq, Hash)]
547#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
548#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
549#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
550pub struct BlobTransactionSidecarEip7594 {
551 #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::eip4844::deserialize_blobs"))]
553 pub blobs: Vec<Blob>,
554 pub commitments: Vec<Bytes48>,
556 pub cell_proofs: Vec<Bytes48>,
561}
562
563impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
564 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
565 f.debug_struct("BlobTransactionSidecarEip7594")
566 .field("blobs", &self.blobs.len())
567 .field("commitments", &self.commitments)
568 .field("cell_proofs", &self.cell_proofs)
569 .finish()
570 }
571}
572
573impl BlobTransactionSidecarEip7594 {
574 pub const fn new(
576 blobs: Vec<Blob>,
577 commitments: Vec<Bytes48>,
578 cell_proofs: Vec<Bytes48>,
579 ) -> Self {
580 Self { blobs, commitments, cell_proofs }
581 }
582
583 #[cfg(feature = "kzg")]
596 pub fn try_recover_from_cells(
597 commitments: Vec<Bytes48>,
598 cell_mask: BlobCellMask,
599 cells: &[crate::eip7594::Cell],
600 ) -> Result<Self, BlobCellRecoveryError> {
601 use crate::eip4844::env_settings::EnvKzgSettings;
602
603 Self::try_recover_from_cells_with_settings(
604 commitments,
605 cell_mask,
606 cells,
607 EnvKzgSettings::Default.get(),
608 )
609 }
610
611 #[cfg(feature = "kzg")]
616 pub fn try_recover_from_cells_with_settings(
617 commitments: Vec<Bytes48>,
618 cell_mask: BlobCellMask,
619 cells: &[crate::eip7594::Cell],
620 settings: &c_kzg::KzgSettings,
621 ) -> Result<Self, BlobCellRecoveryError> {
622 let cells_per_blob = cell_mask.count();
623 if !commitments.is_empty() && cells_per_blob < CELLS_PER_EXT_BLOB / 2 {
624 return Err(BlobCellRecoveryError::InsufficientCells {
625 provided: cells_per_blob,
626 required: CELLS_PER_EXT_BLOB / 2,
627 });
628 }
629
630 let expected_cells = commitments
631 .len()
632 .checked_mul(cells_per_blob)
633 .ok_or(BlobCellRecoveryError::CellCountOverflow)?;
634 if cells.len() != expected_cells {
635 return Err(BlobCellRecoveryError::CellCountMismatch {
636 provided: cells.len(),
637 expected: expected_cells,
638 });
639 }
640 if commitments.is_empty() {
641 return Ok(Self::new(Vec::new(), commitments, Vec::new()));
642 }
643
644 let cell_indices =
645 cell_mask.selected_indices().map(|index| index as u64).collect::<Vec<_>>();
646 let mut blobs = Vec::with_capacity(commitments.len());
647 let cell_proof_capacity = commitments
648 .len()
649 .checked_mul(CELLS_PER_EXT_BLOB)
650 .ok_or(BlobCellRecoveryError::CellCountOverflow)?;
651 let mut cell_proofs = Vec::with_capacity(cell_proof_capacity);
652
653 for (blob_index, (blob_cells, expected_commitment)) in
654 cells.chunks_exact(cells_per_blob).zip(&commitments).enumerate()
655 {
656 let ckzg_cells = crate::eip7594::Cell::slice_as_ckzg(blob_cells);
657 let (recovered_cells, recovered_proofs) =
658 settings.recover_cells_and_kzg_proofs(&cell_indices, ckzg_cells)?;
659 let blob = reconstruct_blob(recovered_cells.as_ref());
660
661 let commitment = settings.blob_to_kzg_commitment(blob.as_ckzg())?;
662 let commitment = Bytes48::from_ckzg(commitment.to_bytes());
663 if commitment != *expected_commitment {
664 return Err(BlobCellRecoveryError::CommitmentMismatch { blob_index });
665 }
666
667 blobs.push(blob);
668 cell_proofs
669 .extend_from_slice(c_kzg::KzgProof::slice_as_alloy(recovered_proofs.as_ref()));
670 }
671
672 Ok(Self::new(blobs, commitments, cell_proofs))
673 }
674
675 pub fn clear_eip7594_blobs(&mut self) {
682 self.blobs.clear();
683 }
684
685 #[inline]
687 pub const fn size(&self) -> usize {
688 self.blobs.capacity() * BYTES_PER_BLOB
689 + self.commitments.capacity() * BYTES_PER_COMMITMENT
690 + self.cell_proofs.capacity() * BYTES_PER_PROOF
691 }
692
693 #[inline]
695 pub fn shrink_to_fit(&mut self) {
696 self.blobs.shrink_to_fit();
697 self.commitments.shrink_to_fit();
698 self.cell_proofs.shrink_to_fit();
699 }
700
701 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
705 pub fn try_from_blobs_hex<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
706 where
707 I: IntoIterator<Item = B>,
708 B: AsRef<str>,
709 {
710 let mut converted = Vec::new();
711 for blob in blobs {
712 converted.push(crate::eip4844::utils::hex_to_blob(blob)?);
713 }
714 Self::try_from_blobs(converted)
715 }
716
717 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
722 pub fn try_from_blobs_bytes<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
723 where
724 I: IntoIterator<Item = B>,
725 B: AsRef<[u8]>,
726 {
727 let mut converted = Vec::new();
728 for blob in blobs {
729 converted.push(crate::eip4844::utils::bytes_to_blob(blob)?);
730 }
731 Self::try_from_blobs(converted)
732 }
733
734 #[cfg(feature = "kzg")]
737 pub fn try_from_blobs_with_settings(
738 blobs: Vec<Blob>,
739 settings: &c_kzg::KzgSettings,
740 ) -> Result<Self, c_kzg::Error> {
741 if let [blob] = blobs.as_slice() {
742 let blob = blob.as_ckzg();
743 let commitment = settings.blob_to_kzg_commitment(blob)?;
744 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
745 let commitments = vec![Bytes48::from_ckzg(commitment.to_bytes())];
746 let proofs = c_kzg::KzgProof::boxed_slice_as_alloy(kzg_proofs).into();
747 return Ok(Self::new(blobs, commitments, proofs));
748 }
749
750 let mut commitments = Vec::with_capacity(blobs.len());
751 let mut proofs = Vec::with_capacity(blobs.len() * CELLS_PER_EXT_BLOB);
752 for blob in &blobs {
753 let blob = blob.as_ckzg();
754 let commitment = settings.blob_to_kzg_commitment(blob)?;
755 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
756
757 commitments.push(Bytes48::from_ckzg(commitment.to_bytes()));
758 proofs.extend_from_slice(c_kzg::KzgProof::slice_as_alloy(kzg_proofs.as_ref()));
759 }
760
761 Ok(Self::new(blobs, commitments, proofs))
762 }
763
764 #[cfg(feature = "kzg")]
770 pub fn try_from_blobs(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error> {
771 use crate::eip4844::env_settings::EnvKzgSettings;
772
773 Self::try_from_blobs_with_settings(blobs, EnvKzgSettings::Default.get())
774 }
775
776 #[cfg(feature = "kzg")]
784 pub fn compute_cells(&self) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
785 use crate::eip4844::env_settings::EnvKzgSettings;
786
787 self.compute_cells_with_settings(EnvKzgSettings::Default.get())
788 }
789
790 #[cfg(feature = "kzg")]
798 pub fn compute_cells_with_settings(
799 &self,
800 settings: &c_kzg::KzgSettings,
801 ) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
802 if let [blob] = self.blobs.as_slice() {
803 let blob_cells = settings.compute_cells(blob.as_ckzg())?;
804 return Ok(c_kzg::Cell::boxed_slice_as_alloy(blob_cells).into());
805 }
806
807 let mut cells = Vec::with_capacity(self.blobs.len() * CELLS_PER_EXT_BLOB);
808 for blob in &self.blobs {
809 let blob_cells = settings.compute_cells(blob.as_ckzg())?;
810 cells.extend_from_slice(c_kzg::Cell::slice_as_alloy(blob_cells.as_ref()));
811 }
812 Ok(cells)
813 }
814
815 #[cfg(feature = "kzg")]
823 pub fn compute_matching_cells(
824 &self,
825 cell_mask: BlobCellMask,
826 ) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
827 use crate::eip4844::env_settings::EnvKzgSettings;
828
829 self.compute_matching_cells_with_settings(cell_mask, EnvKzgSettings::Default.get())
830 }
831
832 #[cfg(feature = "kzg")]
838 pub fn compute_matching_cells_with_settings(
839 &self,
840 cell_mask: BlobCellMask,
841 settings: &c_kzg::KzgSettings,
842 ) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
843 let cells = self.compute_cells_with_settings(settings)?;
844 Ok(cell_mask
845 .matching_cells_from_computed_cells(&cells)
846 .expect("computed cells must contain full extended blob cell chunks"))
847 }
848
849 #[cfg(feature = "kzg")]
863 pub fn validate(
864 &self,
865 blob_versioned_hashes: &[B256],
866 proof_settings: &c_kzg::KzgSettings,
867 ) -> Result<(), BlobTransactionValidationError> {
868 if blob_versioned_hashes.len() != self.commitments.len() {
870 return Err(c_kzg::Error::MismatchLength(format!(
871 "There are {} versioned commitment hashes and {} commitments",
872 blob_versioned_hashes.len(),
873 self.commitments.len()
874 ))
875 .into());
876 }
877
878 let blobs_len = self.blobs.len();
879 let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
880 if self.cell_proofs.len() != expected_cell_proofs_len {
881 return Err(c_kzg::Error::MismatchLength(format!(
882 "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
883 self.cell_proofs.len(),
884 blobs_len,
885 expected_cell_proofs_len
886 ))
887 .into());
888 }
889
890 for (versioned_hash, commitment) in
892 blob_versioned_hashes.iter().zip(self.commitments.iter())
893 {
894 let calculated_versioned_hash =
896 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
897 if *versioned_hash != calculated_versioned_hash {
898 return Err(BlobTransactionValidationError::WrongVersionedHash {
899 have: *versioned_hash,
900 expected: calculated_versioned_hash,
901 });
902 }
903 }
904
905 let cell_indices =
907 Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
908
909 let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
911 for commitment in &self.commitments {
912 commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
913 }
914
915 let cells = if let [blob] = self.blobs.as_slice() {
916 let cells: Box<[c_kzg::Cell]> = proof_settings.compute_cells(blob.as_ckzg())?;
917 cells.into()
918 } else {
919 let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
920 for blob in &self.blobs {
921 let blob_cells = proof_settings.compute_cells(blob.as_ckzg())?;
922 cells.extend_from_slice(blob_cells.as_ref());
923 }
924 cells
925 };
926
927 let res = proof_settings.verify_cell_kzg_proof_batch(
928 Bytes48::slice_as_ckzg(&commitments),
929 &cell_indices,
930 &cells,
931 Bytes48::slice_as_ckzg(self.cell_proofs.as_slice()),
932 )?;
933
934 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
935 }
936
937 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
939 VersionedHashIter::new(&self.commitments)
940 }
941
942 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
944 self.commitments.iter().position(|commitment| {
945 crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
946 })
947 }
948
949 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
951 self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
952 }
953
954 #[cfg(feature = "kzg")]
958 pub fn blob_cells_and_proofs(
959 &self,
960 blob_index: usize,
961 cell_mask: BlobCellMask,
962 ) -> Result<Option<crate::eip4844::BlobCellsAndProofsV1>, c_kzg::Error> {
963 use crate::eip4844::env_settings::EnvKzgSettings;
964
965 self.blob_cells_and_proofs_with_settings(
966 blob_index,
967 cell_mask,
968 EnvKzgSettings::Default.get(),
969 )
970 }
971
972 #[cfg(feature = "kzg")]
974 pub fn blob_cells_and_proofs_with_settings(
975 &self,
976 blob_index: usize,
977 cell_mask: BlobCellMask,
978 settings: &c_kzg::KzgSettings,
979 ) -> Result<Option<crate::eip4844::BlobCellsAndProofsV1>, c_kzg::Error> {
980 let Some(blob) = self.blobs.get(blob_index) else { return Ok(None) };
981
982 let proof_start = blob_index * CELLS_PER_EXT_BLOB;
983 let Some(proofs) = self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
984 else {
985 return Ok(None);
986 };
987
988 if cell_mask.count() == 0 {
989 return Ok(Some(crate::eip4844::BlobCellsAndProofsV1::default()));
990 }
991
992 let cells = settings.compute_cells(blob.as_ckzg())?;
993
994 Ok(Some(Self::blob_cells_and_proofs_from_computed_cells(cell_mask, cells.as_ref(), proofs)))
995 }
996
997 #[cfg(feature = "kzg")]
999 fn blob_cells_and_proofs_from_computed_cells(
1000 cell_mask: BlobCellMask,
1001 cells: &[c_kzg::Cell],
1002 proofs: &[Bytes48],
1003 ) -> crate::eip4844::BlobCellsAndProofsV1 {
1004 let mut blob_cells = Vec::with_capacity(cell_mask.count());
1007 let mut selected_proofs = Vec::with_capacity(cell_mask.count());
1008 for cell_index in cell_mask.selected_indices() {
1009 blob_cells
1010 .push(cells.get(cell_index).map(|cell| crate::eip7594::Cell::new(cell.to_bytes())));
1011 selected_proofs.push(proofs.get(cell_index).copied());
1012 }
1013
1014 crate::eip4844::BlobCellsAndProofsV1 { blob_cells, proofs: selected_proofs }
1015 }
1016
1017 pub fn match_versioned_hashes<'a>(
1023 &'a self,
1024 versioned_hashes: &'a [B256],
1025 ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
1026 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
1027 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
1028 if blob_versioned_hash == *target_hash {
1029 let maybe_blob = self.blobs.get(i);
1030 let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
1031 let maybe_proofs = self
1032 .cell_proofs
1033 .get(proof_range)
1034 .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
1035 if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
1036 return Some((
1037 j,
1038 BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
1039 ));
1040 }
1041 }
1042 None
1043 })
1044 })
1045 }
1046
1047 #[cfg(feature = "kzg")]
1055 pub fn match_versioned_hashes_cells<'a>(
1056 &'a self,
1057 versioned_hashes: &'a [B256],
1058 cell_mask: BlobCellMask,
1059 ) -> Result<
1060 impl Iterator<Item = (usize, crate::eip4844::BlobCellsAndProofsV1)> + 'a,
1061 c_kzg::Error,
1062 > {
1063 use crate::eip4844::env_settings::EnvKzgSettings;
1064
1065 self.match_versioned_hashes_cells_with_settings(
1066 versioned_hashes,
1067 cell_mask,
1068 EnvKzgSettings::Default.get(),
1069 )
1070 }
1071
1072 #[cfg(feature = "kzg")]
1076 pub fn match_versioned_hashes_cells_with_settings<'a>(
1077 &'a self,
1078 versioned_hashes: &'a [B256],
1079 cell_mask: BlobCellMask,
1080 settings: &c_kzg::KzgSettings,
1081 ) -> Result<
1082 impl Iterator<Item = (usize, crate::eip4844::BlobCellsAndProofsV1)> + 'a,
1083 c_kzg::Error,
1084 > {
1085 let mut matches = Vec::new();
1086 let mut cells_and_proofs_by_blob =
1087 Vec::<(usize, crate::eip4844::BlobCellsAndProofsV1)>::new();
1088
1089 for (blob_index, commitment) in self.commitments.iter().enumerate() {
1090 let blob_versioned_hash = crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
1091 for (matched_index, target_hash) in versioned_hashes.iter().enumerate() {
1092 if blob_versioned_hash != *target_hash {
1093 continue;
1094 }
1095
1096 let Some(blob) = self.blobs.get(blob_index) else { continue };
1097 let proof_start = blob_index * CELLS_PER_EXT_BLOB;
1098 let Some(proofs) =
1099 self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
1100 else {
1101 continue;
1102 };
1103
1104 let cells_and_proofs = if cell_mask.count() == 0 {
1105 crate::eip4844::BlobCellsAndProofsV1::default()
1106 } else if let Some((_, cells_and_proofs)) =
1107 cells_and_proofs_by_blob.iter().find(|(index, _)| *index == blob_index)
1108 {
1109 cells_and_proofs.clone()
1110 } else {
1111 let cells = settings.compute_cells(blob.as_ckzg())?;
1112 let cells_and_proofs = Self::blob_cells_and_proofs_from_computed_cells(
1113 cell_mask,
1114 cells.as_ref(),
1115 proofs,
1116 );
1117 cells_and_proofs_by_blob.push((blob_index, cells_and_proofs.clone()));
1118 cells_and_proofs
1119 };
1120
1121 matches.push((matched_index, cells_and_proofs));
1122 }
1123 }
1124
1125 Ok(matches.into_iter())
1126 }
1127
1128 #[doc(hidden)]
1130 pub fn rlp_encoded_fields_length(&self) -> usize {
1131 1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
1133 }
1134
1135 #[inline]
1144 #[doc(hidden)]
1145 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
1146 out.put_u8(EIP_7594_WRAPPER_VERSION);
1148 self.blobs.encode(out);
1150 self.commitments.encode(out);
1151 self.cell_proofs.encode(out);
1152 }
1153
1154 fn rlp_header(&self) -> Header {
1156 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
1157 }
1158
1159 pub fn rlp_encoded_length(&self) -> usize {
1162 self.rlp_header().length() + self.rlp_encoded_fields_length()
1163 }
1164
1165 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
1167 self.rlp_header().encode(out);
1168 self.rlp_encode_fields(out);
1169 }
1170
1171 #[doc(hidden)]
1173 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1174 Ok(Self {
1175 blobs: Decodable::decode(buf)?,
1176 commitments: Decodable::decode(buf)?,
1177 cell_proofs: Decodable::decode(buf)?,
1178 })
1179 }
1180
1181 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1183 let header = Header::decode(buf)?;
1184 if !header.list {
1185 return Err(alloy_rlp::Error::UnexpectedString);
1186 }
1187 if buf.len() < header.payload_length {
1188 return Err(alloy_rlp::Error::InputTooShort);
1189 }
1190 let remaining = buf.len();
1191
1192 let this = Self::decode_7594(buf)?;
1193 if buf.len() + header.payload_length != remaining {
1194 return Err(alloy_rlp::Error::UnexpectedLength);
1195 }
1196
1197 Ok(this)
1198 }
1199}
1200
1201#[cfg(feature = "kzg")]
1203#[derive(Debug)]
1204pub enum BlobCellRecoveryError {
1205 InsufficientCells {
1207 provided: usize,
1209 required: usize,
1211 },
1212 CellCountMismatch {
1214 provided: usize,
1216 expected: usize,
1218 },
1219 CellCountOverflow,
1221 CommitmentMismatch {
1223 blob_index: usize,
1225 },
1226 Kzg(c_kzg::Error),
1228}
1229
1230#[cfg(feature = "kzg")]
1231impl core::fmt::Display for BlobCellRecoveryError {
1232 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1233 match self {
1234 Self::InsufficientCells { provided, required } => {
1235 write!(f, "need at least {required} cells per blob for recovery, got {provided}")
1236 }
1237 Self::CellCountMismatch { provided, expected } => {
1238 write!(f, "expected {expected} cells, got {provided}")
1239 }
1240 Self::CellCountOverflow => f.write_str("the expected cell count overflows usize"),
1241 Self::CommitmentMismatch { blob_index } => {
1242 write!(f, "reconstructed blob {blob_index} does not match its commitment")
1243 }
1244 Self::Kzg(err) => write!(f, "KZG error: {err:?}"),
1245 }
1246 }
1247}
1248
1249#[cfg(feature = "kzg")]
1250impl core::error::Error for BlobCellRecoveryError {}
1251
1252#[cfg(feature = "kzg")]
1253impl From<c_kzg::Error> for BlobCellRecoveryError {
1254 fn from(source: c_kzg::Error) -> Self {
1255 Self::Kzg(source)
1256 }
1257}
1258
1259#[cfg(feature = "kzg")]
1260fn reconstruct_blob(recovered_cells: &[c_kzg::Cell; CELLS_PER_EXT_BLOB]) -> Blob {
1261 let mut blob = [0u8; BYTES_PER_BLOB];
1266 for (cell_index, cell) in recovered_cells.iter().take(CELLS_PER_EXT_BLOB / 2).enumerate() {
1267 let start = cell_index * crate::eip7594::BYTES_PER_CELL;
1268 let end = start + crate::eip7594::BYTES_PER_CELL;
1269 blob[start..end].copy_from_slice(cell.as_alloy().as_slice());
1270 }
1271 Blob::new(blob)
1272}
1273
1274impl Encodable for BlobTransactionSidecarEip7594 {
1275 fn encode(&self, out: &mut dyn BufMut) {
1277 self.rlp_encode(out);
1278 }
1279
1280 fn length(&self) -> usize {
1281 self.rlp_encoded_length()
1282 }
1283}
1284
1285impl Decodable for BlobTransactionSidecarEip7594 {
1286 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1288 Self::rlp_decode(buf)
1289 }
1290}
1291
1292impl Encodable7594 for BlobTransactionSidecarEip7594 {
1293 fn encode_7594_len(&self) -> usize {
1294 self.rlp_encoded_fields_length()
1295 }
1296
1297 fn encode_7594(&self, out: &mut dyn BufMut) {
1298 self.rlp_encode_fields(out);
1299 }
1300
1301 fn encode_7594_len_with(&self, encoding: BlobSidecarEncoding) -> usize {
1302 let blobs_len = match encoding {
1303 BlobSidecarEncoding::WithBlobs => self.blobs.length(),
1304 BlobSidecarEncoding::WithoutBlobs => 1,
1305 };
1306 1 + blobs_len + self.commitments.length() + self.cell_proofs.length()
1307 }
1308
1309 fn encode_7594_with(&self, encoding: BlobSidecarEncoding, out: &mut dyn BufMut) {
1310 out.put_u8(EIP_7594_WRAPPER_VERSION);
1311 match encoding {
1312 BlobSidecarEncoding::WithBlobs => self.blobs.encode(out),
1313 BlobSidecarEncoding::WithoutBlobs => out.put_u8(EMPTY_LIST_CODE),
1314 }
1315 self.commitments.encode(out);
1316 self.cell_proofs.encode(out);
1317 }
1318}
1319
1320impl Decodable7594 for BlobTransactionSidecarEip7594 {
1321 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1322 let wrapper_version: u8 = Decodable::decode(buf)?;
1323 if wrapper_version != EIP_7594_WRAPPER_VERSION {
1324 return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
1325 }
1326 Self::rlp_decode_fields(buf)
1327 }
1328}
1329
1330#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
1332pub struct BlobCellMask {
1333 value: u128,
1334}
1335
1336impl BlobCellMask {
1337 #[inline]
1339 pub fn new(indices_bitarray: B128) -> Self {
1340 Self { value: u128::from(indices_bitarray) }
1341 }
1342
1343 #[inline]
1345 pub const fn from_bits(value: u128) -> Self {
1346 Self { value }
1347 }
1348
1349 #[inline]
1351 pub const fn bits(self) -> u128 {
1352 self.value
1353 }
1354
1355 #[inline]
1357 pub const fn count(self) -> usize {
1358 self.value.count_ones() as usize
1359 }
1360
1361 #[inline]
1363 pub const fn contains(self, index: usize) -> bool {
1364 index < CELLS_PER_EXT_BLOB && self.value & (1u128 << index) != 0
1365 }
1366
1367 #[inline]
1369 pub fn selected_indices(self) -> impl Iterator<Item = usize> {
1370 let mut bits = self.value;
1371 core::iter::from_fn(move || {
1372 if bits == 0 {
1373 return None;
1374 }
1375
1376 let index = bits.trailing_zeros() as usize;
1377 bits &= bits - 1;
1378 Some(index)
1379 })
1380 }
1381
1382 pub fn matching_cells_from_computed_cells(
1392 self,
1393 cells: &[crate::eip7594::Cell],
1394 ) -> Option<Vec<crate::eip7594::Cell>> {
1395 let (chunks, remainder) = cells.as_chunks::<CELLS_PER_EXT_BLOB>();
1396 if !remainder.is_empty() {
1397 return None;
1398 }
1399
1400 let mut matching_cells = Vec::with_capacity(chunks.len() * self.count());
1401 for blob_cells in chunks {
1402 for cell_index in self.selected_indices() {
1403 let cell = blob_cells
1404 .get(cell_index)
1405 .expect("cell mask index must be within extended blob cells");
1406 matching_cells.push(*cell);
1407 }
1408 }
1409
1410 Some(matching_cells)
1411 }
1412}
1413
1414#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
1416pub mod serde_bincode_compat {
1417 use crate::eip4844::{Blob, Bytes48};
1418 use alloc::{borrow::Cow, vec::Vec};
1419 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1420 use serde_with::{DeserializeAs, SerializeAs};
1421
1422 #[derive(Debug, Serialize, Deserialize)]
1438 pub struct BlobTransactionSidecarVariant<'a> {
1439 pub blobs: Cow<'a, Vec<Blob>>,
1441 pub commitments: Cow<'a, Vec<Bytes48>>,
1443 pub proofs: Option<Cow<'a, Vec<Bytes48>>>,
1445 pub cell_proofs: Option<Cow<'a, Vec<Bytes48>>>,
1447 }
1448
1449 impl<'a> From<&'a super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'a> {
1450 fn from(value: &'a super::BlobTransactionSidecarVariant) -> Self {
1451 match value {
1452 super::BlobTransactionSidecarVariant::Eip4844(sidecar) => Self {
1453 blobs: Cow::Borrowed(&sidecar.blobs),
1454 commitments: Cow::Borrowed(&sidecar.commitments),
1455 proofs: Some(Cow::Borrowed(&sidecar.proofs)),
1456 cell_proofs: None,
1457 },
1458 super::BlobTransactionSidecarVariant::Eip7594(sidecar) => Self {
1459 blobs: Cow::Borrowed(&sidecar.blobs),
1460 commitments: Cow::Borrowed(&sidecar.commitments),
1461 proofs: None,
1462 cell_proofs: Some(Cow::Borrowed(&sidecar.cell_proofs)),
1463 },
1464 }
1465 }
1466 }
1467
1468 impl<'a> BlobTransactionSidecarVariant<'a> {
1469 fn try_into_inner(self) -> Result<super::BlobTransactionSidecarVariant, &'static str> {
1470 match (self.proofs, self.cell_proofs) {
1471 (Some(proofs), None) => Ok(super::BlobTransactionSidecarVariant::Eip4844(
1472 crate::eip4844::BlobTransactionSidecar {
1473 blobs: self.blobs.into_owned(),
1474 commitments: self.commitments.into_owned(),
1475 proofs: proofs.into_owned(),
1476 },
1477 )),
1478 (None, Some(cell_proofs)) => Ok(super::BlobTransactionSidecarVariant::Eip7594(
1479 super::BlobTransactionSidecarEip7594 {
1480 blobs: self.blobs.into_owned(),
1481 commitments: self.commitments.into_owned(),
1482 cell_proofs: cell_proofs.into_owned(),
1483 },
1484 )),
1485 (None, None) => Err("Missing both 'proofs' and 'cell_proofs'"),
1486 (Some(_), Some(_)) => Err("Both 'proofs' and 'cell_proofs' cannot be present"),
1487 }
1488 }
1489 }
1490
1491 impl<'a> From<BlobTransactionSidecarVariant<'a>> for super::BlobTransactionSidecarVariant {
1492 fn from(value: BlobTransactionSidecarVariant<'a>) -> Self {
1493 value.try_into_inner().expect("Invalid BlobTransactionSidecarVariant")
1494 }
1495 }
1496
1497 impl SerializeAs<super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'_> {
1498 fn serialize_as<S>(
1499 source: &super::BlobTransactionSidecarVariant,
1500 serializer: S,
1501 ) -> Result<S::Ok, S::Error>
1502 where
1503 S: Serializer,
1504 {
1505 BlobTransactionSidecarVariant::from(source).serialize(serializer)
1506 }
1507 }
1508
1509 impl<'de> DeserializeAs<'de, super::BlobTransactionSidecarVariant>
1510 for BlobTransactionSidecarVariant<'de>
1511 {
1512 fn deserialize_as<D>(
1513 deserializer: D,
1514 ) -> Result<super::BlobTransactionSidecarVariant, D::Error>
1515 where
1516 D: Deserializer<'de>,
1517 {
1518 let value = BlobTransactionSidecarVariant::deserialize(deserializer)?;
1519 value.try_into_inner().map_err(serde::de::Error::custom)
1520 }
1521 }
1522}
1523
1524#[cfg(test)]
1525mod tests {
1526 use super::*;
1527 #[cfg(feature = "kzg")]
1528 use crate::eip4844::{
1529 builder::{SidecarBuilder, SimpleCoder},
1530 env_settings::EnvKzgSettings,
1531 };
1532
1533 #[test]
1534 fn clear_eip7594_blobs_preserves_metadata() {
1535 let commitments = vec![Bytes48::repeat_byte(0x01)];
1536 let cell_proofs = vec![Bytes48::repeat_byte(0x02); CELLS_PER_EXT_BLOB];
1537 let sidecar = BlobTransactionSidecarEip7594::new(
1538 vec![Blob::repeat_byte(0x03)],
1539 commitments.clone(),
1540 cell_proofs.clone(),
1541 );
1542 let mut variant = BlobTransactionSidecarVariant::Eip7594(sidecar);
1543
1544 variant.clear_eip7594_blobs();
1545
1546 let sidecar = variant.as_eip7594().unwrap();
1547 assert!(sidecar.blobs.is_empty());
1548 assert_eq!(sidecar.commitments, commitments);
1549 assert_eq!(sidecar.cell_proofs, cell_proofs);
1550 }
1551
1552 #[test]
1553 fn clear_eip7594_blobs_ignores_eip4844_variant() {
1554 let sidecar = BlobTransactionSidecar::new(
1555 vec![Blob::repeat_byte(0x01)],
1556 vec![Bytes48::repeat_byte(0x02)],
1557 vec![Bytes48::repeat_byte(0x03)],
1558 );
1559 let mut variant = BlobTransactionSidecarVariant::Eip4844(sidecar.clone());
1560
1561 variant.clear_eip7594_blobs();
1562
1563 assert_eq!(variant, BlobTransactionSidecarVariant::Eip4844(sidecar));
1564 }
1565
1566 #[test]
1567 fn sidecar_variant_rlp_roundtrip() {
1568 let mut encoded = Vec::new();
1569
1570 let empty_sidecar_4844 =
1572 BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
1573 empty_sidecar_4844.encode(&mut encoded);
1574 assert_eq!(
1575 empty_sidecar_4844,
1576 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1577 );
1578
1579 let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
1580 vec![Blob::default()],
1581 vec![Bytes48::ZERO],
1582 vec![Bytes48::ZERO],
1583 ));
1584 encoded.clear();
1585 sidecar_4844.encode(&mut encoded);
1586 assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1587
1588 let empty_sidecar_7594 =
1590 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
1591 encoded.clear();
1592 empty_sidecar_7594.encode(&mut encoded);
1593 assert_eq!(
1594 empty_sidecar_7594,
1595 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1596 );
1597
1598 let sidecar_7594 =
1599 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
1600 vec![Blob::default()],
1601 vec![Bytes48::ZERO],
1602 core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
1603 ));
1604 encoded.clear();
1605 sidecar_7594.encode(&mut encoded);
1606 assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1607 }
1608
1609 #[test]
1610 #[cfg(feature = "serde")]
1611 fn sidecar_variant_json_deserialize_sanity() {
1612 let mut eip4844 = BlobTransactionSidecar::default();
1613 eip4844.blobs.push(Blob::repeat_byte(0x2));
1614
1615 let json = serde_json::to_string(&eip4844).unwrap();
1616 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1617 assert!(variant.is_eip4844());
1618 let jsonvariant = serde_json::to_string(&variant).unwrap();
1619 assert_eq!(json, jsonvariant);
1620
1621 let mut eip7594 = BlobTransactionSidecarEip7594::default();
1622 eip7594.blobs.push(Blob::repeat_byte(0x4));
1623 let json = serde_json::to_string(&eip7594).unwrap();
1624 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1625 assert!(variant.is_eip7594());
1626 let jsonvariant = serde_json::to_string(&variant).unwrap();
1627 assert_eq!(json, jsonvariant);
1628 }
1629
1630 #[test]
1631 fn rlp_7594_roundtrip() {
1632 let mut encoded = Vec::new();
1633
1634 let sidecar_4844 = BlobTransactionSidecar::default();
1635 sidecar_4844.encode_7594(&mut encoded);
1636 assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1637
1638 let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
1639 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1640 encoded.clear();
1641 sidecar_variant_4844.encode_7594(&mut encoded);
1642 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1643
1644 let sidecar_7594 = BlobTransactionSidecarEip7594::default();
1645 encoded.clear();
1646 sidecar_7594.encode_7594(&mut encoded);
1647 assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1648
1649 let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
1650 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1651 encoded.clear();
1652 sidecar_variant_7594.encode_7594(&mut encoded);
1653 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1654 }
1655
1656 #[test]
1657 fn rlp_7594_encoding_without_blobs_preserves_metadata() {
1658 fn encode_without_blobs(sidecar: &impl Encodable7594) -> Vec<u8> {
1659 let encoding = BlobSidecarEncoding::WithoutBlobs;
1660 let mut encoded = Vec::with_capacity(sidecar.encode_7594_len_with(encoding));
1661 sidecar.encode_7594_with(encoding, &mut encoded);
1662 assert_eq!(encoded.len(), sidecar.encode_7594_len_with(encoding));
1663 encoded
1664 }
1665
1666 let sidecar_4844 = BlobTransactionSidecar::new(
1667 vec![Blob::repeat_byte(0x01)],
1668 vec![Bytes48::repeat_byte(0x02)],
1669 vec![Bytes48::repeat_byte(0x03)],
1670 );
1671 let encoded_4844 = encode_without_blobs(&sidecar_4844);
1672 let decoded_4844 = BlobTransactionSidecar::decode_7594(&mut &encoded_4844[..]).unwrap();
1673 assert!(decoded_4844.blobs.is_empty());
1674 assert_eq!(decoded_4844.commitments, sidecar_4844.commitments);
1675 assert_eq!(decoded_4844.proofs, sidecar_4844.proofs);
1676
1677 let variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
1678 assert_eq!(encode_without_blobs(&variant_4844), encoded_4844);
1679
1680 let sidecar_7594 = BlobTransactionSidecarEip7594::new(
1681 vec![Blob::repeat_byte(0x04)],
1682 vec![Bytes48::repeat_byte(0x05)],
1683 vec![Bytes48::repeat_byte(0x06); CELLS_PER_EXT_BLOB],
1684 );
1685 let encoded_7594 = encode_without_blobs(&sidecar_7594);
1686 let decoded_7594 =
1687 BlobTransactionSidecarEip7594::decode_7594(&mut &encoded_7594[..]).unwrap();
1688 assert!(decoded_7594.blobs.is_empty());
1689 assert_eq!(decoded_7594.commitments, sidecar_7594.commitments);
1690 assert_eq!(decoded_7594.cell_proofs, sidecar_7594.cell_proofs);
1691
1692 let variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594.clone());
1693 assert_eq!(encode_without_blobs(&variant_7594), encoded_7594);
1694
1695 let mut with_blobs = Vec::new();
1696 sidecar_7594.encode_7594_with(BlobSidecarEncoding::WithBlobs, &mut with_blobs);
1697 assert_eq!(with_blobs, sidecar_7594.encoded_7594());
1698 assert_eq!(
1699 sidecar_7594.encode_7594_len_with(BlobSidecarEncoding::WithBlobs),
1700 sidecar_7594.encode_7594_len()
1701 );
1702 }
1703
1704 #[test]
1705 #[cfg(feature = "kzg")]
1706 fn validate_7594_sidecar() {
1707 let sidecar =
1708 SidecarBuilder::<SimpleCoder>::from_slice(b"Blobs are fun!").build_7594().unwrap();
1709 let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1710
1711 sidecar.validate(&versioned_hashes, EnvKzgSettings::Default.get()).unwrap();
1712 }
1713
1714 #[test]
1715 #[cfg(feature = "kzg")]
1716 fn compute_cells_for_7594_sidecar() {
1717 let settings = EnvKzgSettings::Default.get();
1718 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1719 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1720 settings,
1721 )
1722 .unwrap();
1723
1724 let cells = sidecar.compute_cells_with_settings(settings).unwrap();
1725 assert_eq!(cells.len(), sidecar.blobs.len() * CELLS_PER_EXT_BLOB);
1726 assert_eq!(sidecar.compute_cells().unwrap(), cells);
1727
1728 let cell_mask = BlobCellMask::from_bits((1u128 << 0) | (1u128 << 7));
1729 let matching_cells =
1730 sidecar.compute_matching_cells_with_settings(cell_mask, settings).unwrap();
1731 let expected_matching_cells = cells
1732 .as_chunks::<CELLS_PER_EXT_BLOB>()
1733 .0
1734 .iter()
1735 .flat_map(|blob_cells| [blob_cells[0], blob_cells[7]])
1736 .collect::<Vec<_>>();
1737 assert_eq!(
1738 cell_mask.matching_cells_from_computed_cells(&cells),
1739 Some(expected_matching_cells.clone())
1740 );
1741 assert_eq!(matching_cells, expected_matching_cells);
1742 assert_eq!(sidecar.compute_matching_cells(cell_mask).unwrap(), expected_matching_cells);
1743 assert!(sidecar.compute_matching_cells(BlobCellMask::default()).unwrap().is_empty());
1744
1745 for (blob_index, blob) in sidecar.blobs.iter().enumerate() {
1746 let expected_cells = settings.compute_cells(blob.as_ckzg()).unwrap();
1747 let start = blob_index * CELLS_PER_EXT_BLOB;
1748 let end = start + CELLS_PER_EXT_BLOB;
1749
1750 for (cell, expected_cell) in cells[start..end].iter().zip(expected_cells.iter()) {
1751 assert_eq!(*cell, crate::eip7594::Cell::new(expected_cell.to_bytes()));
1752 }
1753 }
1754 }
1755
1756 #[cfg(feature = "kzg")]
1757 fn sparse_cells_for_mask(
1758 sidecar: &BlobTransactionSidecarEip7594,
1759 cell_mask: BlobCellMask,
1760 settings: &c_kzg::KzgSettings,
1761 ) -> Vec<crate::eip7594::Cell> {
1762 let cells = sidecar.compute_cells_with_settings(settings).unwrap();
1763 cell_mask.matching_cells_from_computed_cells(&cells).unwrap()
1764 }
1765
1766 #[test]
1767 #[cfg(feature = "kzg")]
1768 fn recover_sidecar_from_complete_cells() {
1769 let settings = EnvKzgSettings::Default.get();
1770 assert_eq!(
1771 BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1772 Vec::new(),
1773 BlobCellMask::default(),
1774 &[],
1775 settings,
1776 )
1777 .unwrap(),
1778 BlobTransactionSidecarEip7594::default()
1779 );
1780
1781 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1782 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1783 settings,
1784 )
1785 .unwrap();
1786 let cells = sidecar.compute_cells_with_settings(settings).unwrap();
1787 let cell_mask = BlobCellMask::from_bits(u128::MAX);
1788
1789 let recovered = BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1790 sidecar.commitments.clone(),
1791 cell_mask,
1792 &cells,
1793 settings,
1794 )
1795 .unwrap();
1796 assert_eq!(recovered, sidecar);
1797
1798 assert_eq!(
1799 BlobTransactionSidecarEip7594::try_recover_from_cells(
1800 recovered.commitments.clone(),
1801 cell_mask,
1802 &cells,
1803 )
1804 .unwrap(),
1805 recovered
1806 );
1807 }
1808
1809 #[test]
1812 #[cfg(feature = "kzg")]
1813 fn recover_sparse_blobs_from_minimum_cells() {
1814 let settings = EnvKzgSettings::Default.get();
1815 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1816 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02), Blob::repeat_byte(0x03)],
1817 settings,
1818 )
1819 .unwrap();
1820
1821 let cell_mask = BlobCellMask::from_bits(
1822 (0..CELLS_PER_EXT_BLOB).step_by(2).fold(0, |mask, index| mask | (1u128 << index)),
1823 );
1824 assert_eq!(cell_mask.count(), CELLS_PER_EXT_BLOB / 2);
1825 let sparse_cells = sparse_cells_for_mask(&sidecar, cell_mask, settings);
1826
1827 let recovered = BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1828 sidecar.commitments.clone(),
1829 cell_mask,
1830 &sparse_cells,
1831 settings,
1832 )
1833 .unwrap();
1834
1835 assert_eq!(recovered.blobs, sidecar.blobs);
1836 assert_eq!(recovered.commitments, sidecar.commitments);
1837 assert_eq!(recovered.cell_proofs, sidecar.cell_proofs);
1838 }
1839
1840 #[test]
1842 #[cfg(feature = "kzg")]
1843 fn recover_sparse_blobs_with_more_than_minimum_cells() {
1844 let settings = EnvKzgSettings::Default.get();
1845 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1846 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1847 settings,
1848 )
1849 .unwrap();
1850
1851 let cell_mask = BlobCellMask::from_bits(
1852 ((1u128 << (CELLS_PER_EXT_BLOB / 2)) - 1) | (1u128 << (CELLS_PER_EXT_BLOB - 1)),
1853 );
1854 assert_eq!(cell_mask.count(), CELLS_PER_EXT_BLOB / 2 + 1);
1855 let sparse_cells = sparse_cells_for_mask(&sidecar, cell_mask, settings);
1856
1857 let recovered = BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1858 sidecar.commitments.clone(),
1859 cell_mask,
1860 &sparse_cells,
1861 settings,
1862 )
1863 .unwrap();
1864
1865 assert_eq!(recovered.blobs, sidecar.blobs);
1866 assert_eq!(recovered.cell_proofs, sidecar.cell_proofs);
1867 }
1868
1869 #[test]
1870 #[cfg(feature = "kzg")]
1871 fn recover_sparse_blobs_rejects_insufficient_cells() {
1872 let settings = EnvKzgSettings::Default.get();
1873 let cell_mask = BlobCellMask::from_bits((1u128 << (CELLS_PER_EXT_BLOB / 2 - 1)) - 1);
1874 let cells = vec![crate::eip7594::Cell::repeat_byte(0); cell_mask.count()];
1875
1876 let err = BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1877 vec![Bytes48::ZERO],
1878 cell_mask,
1879 &cells,
1880 settings,
1881 )
1882 .unwrap_err();
1883 assert!(matches!(
1884 err,
1885 BlobCellRecoveryError::InsufficientCells {
1886 provided,
1887 required,
1888 } if provided == CELLS_PER_EXT_BLOB / 2 - 1
1889 && required == CELLS_PER_EXT_BLOB / 2
1890 ));
1891 }
1892
1893 #[test]
1894 #[cfg(feature = "kzg")]
1895 fn recover_sparse_blobs_rejects_mismatched_cell_count() {
1896 let settings = EnvKzgSettings::Default.get();
1897 let cell_mask = BlobCellMask::from_bits((1u128 << (CELLS_PER_EXT_BLOB / 2)) - 1);
1898 let cells = vec![crate::eip7594::Cell::repeat_byte(0); cell_mask.count() - 1];
1899
1900 let err = BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1901 vec![Bytes48::ZERO, Bytes48::ZERO],
1902 cell_mask,
1903 &cells,
1904 settings,
1905 )
1906 .unwrap_err();
1907 assert!(matches!(
1908 err,
1909 BlobCellRecoveryError::CellCountMismatch {
1910 provided,
1911 expected,
1912 } if provided == CELLS_PER_EXT_BLOB / 2 - 1 && expected == CELLS_PER_EXT_BLOB
1913 ));
1914 }
1915
1916 #[test]
1917 #[cfg(feature = "kzg")]
1918 fn recover_sparse_blobs_rejects_commitment_mismatch() {
1919 let settings = EnvKzgSettings::Default.get();
1920 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1921 vec![Blob::repeat_byte(0x01)],
1922 settings,
1923 )
1924 .unwrap();
1925 let cell_mask = BlobCellMask::from_bits((1u128 << (CELLS_PER_EXT_BLOB / 2)) - 1);
1926 let sparse_cells = sparse_cells_for_mask(&sidecar, cell_mask, settings);
1927
1928 let err = BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1929 vec![Bytes48::ZERO],
1930 cell_mask,
1931 &sparse_cells,
1932 settings,
1933 )
1934 .unwrap_err();
1935 assert!(matches!(err, BlobCellRecoveryError::CommitmentMismatch { blob_index: 0 }));
1936 }
1937
1938 #[test]
1940 #[cfg(feature = "kzg")]
1941 fn recover_sparse_blobs_rejects_corrupted_cells() {
1942 let settings = EnvKzgSettings::Default.get();
1943 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1944 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02), Blob::repeat_byte(0x03)],
1945 settings,
1946 )
1947 .unwrap();
1948 let cell_mask = BlobCellMask::from_bits((1u128 << (CELLS_PER_EXT_BLOB / 2)) - 1);
1949 let mut sparse_cells = sparse_cells_for_mask(&sidecar, cell_mask, settings);
1950 sparse_cells[0][0] ^= 0xff;
1951
1952 assert!(BlobTransactionSidecarEip7594::try_recover_from_cells_with_settings(
1953 sidecar.commitments,
1954 cell_mask,
1955 &sparse_cells,
1956 settings,
1957 )
1958 .is_err());
1959 }
1960
1961 #[test]
1962 fn blob_cell_mask_selects_indices() {
1963 let selected = (1u128 << 0) | (1u128 << 7);
1964 let mask = BlobCellMask::new(B128::from(selected));
1965
1966 assert_eq!(mask.bits(), selected);
1967 assert_eq!(mask.count(), 2);
1968 assert!(mask.contains(0));
1969 assert!(mask.contains(7));
1970 assert!(!mask.contains(1));
1971 assert_eq!(mask.selected_indices().collect::<Vec<_>>(), vec![0, 7]);
1972
1973 let cells = (0..CELLS_PER_EXT_BLOB * 2)
1974 .map(|i| crate::eip7594::Cell::repeat_byte(i as u8))
1975 .collect::<Vec<_>>();
1976 assert_eq!(
1977 mask.matching_cells_from_computed_cells(&cells),
1978 Some(vec![
1979 cells[0],
1980 cells[7],
1981 cells[CELLS_PER_EXT_BLOB],
1982 cells[CELLS_PER_EXT_BLOB + 7]
1983 ])
1984 );
1985 assert_eq!(mask.matching_cells_from_computed_cells(&cells[..cells.len() - 1]), None);
1986 }
1987
1988 #[test]
1989 fn match_versioned_hashes_skips_incomplete_proof_chunks() {
1990 let sidecar = BlobTransactionSidecarEip7594::new(
1991 vec![Blob::repeat_byte(0x01)],
1992 vec![Bytes48::repeat_byte(0x02)],
1993 vec![Bytes48::repeat_byte(0x03)],
1994 );
1995 let versioned_hash = sidecar.versioned_hashes().next().unwrap();
1996
1997 let matches = sidecar.match_versioned_hashes(&[versioned_hash]).collect::<Vec<_>>();
1998 assert!(matches.is_empty());
1999 }
2000
2001 #[test]
2002 #[cfg(feature = "kzg")]
2003 fn match_versioned_hashes_cells_for_7594_sidecar() {
2004 let settings = EnvKzgSettings::Default.get();
2005 let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
2006 vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
2007 settings,
2008 )
2009 .unwrap();
2010 let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
2011 let cell_mask = BlobCellMask::from_bits((1u128 << 0) | (1u128 << 7));
2012
2013 let cells_and_proofs =
2014 sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
2015 assert_eq!(cells_and_proofs.blob_cells.len(), 2);
2016 assert_eq!(cells_and_proofs.proofs.len(), 2);
2017 assert_eq!(
2018 cells_and_proofs.proofs,
2019 vec![Some(sidecar.cell_proofs[0]), Some(sidecar.cell_proofs[7])]
2020 );
2021
2022 let expected_cells = settings.compute_cells(sidecar.blobs[0].as_ckzg()).unwrap();
2023 assert_eq!(
2024 cells_and_proofs.blob_cells,
2025 vec![
2026 Some(crate::eip7594::Cell::new(expected_cells[0].to_bytes())),
2027 Some(crate::eip7594::Cell::new(expected_cells[7].to_bytes()))
2028 ]
2029 );
2030
2031 let request = vec![versioned_hashes[0], B256::ZERO, versioned_hashes[0]];
2032 let matches = sidecar
2033 .match_versioned_hashes_cells_with_settings(&request, cell_mask, settings)
2034 .unwrap()
2035 .collect::<Vec<_>>();
2036 assert_eq!(matches.len(), 2);
2037 assert_eq!(matches[0], (0, cells_and_proofs.clone()));
2038 assert_eq!(matches[1], (2, cells_and_proofs.clone()));
2039
2040 let default_matches = sidecar
2041 .match_versioned_hashes_cells(&[versioned_hashes[0]], cell_mask)
2042 .unwrap()
2043 .collect::<Vec<_>>();
2044 assert_eq!(default_matches, vec![(0, cells_and_proofs)]);
2045 }
2046
2047 #[test]
2048 #[cfg(feature = "kzg")]
2049 fn match_versioned_hashes_cells_only_computes_matched_blobs() {
2050 let settings = EnvKzgSettings::Default.get();
2051 let mut sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
2052 vec![Blob::repeat_byte(0x01)],
2053 settings,
2054 )
2055 .unwrap();
2056 let versioned_hash = sidecar.versioned_hashes().next().unwrap();
2057 let cell_mask = BlobCellMask::from_bits(1);
2058
2059 let invalid_blob = Blob::repeat_byte(0xff);
2060 assert!(settings.compute_cells(invalid_blob.as_ckzg()).is_err());
2061
2062 sidecar.blobs.push(invalid_blob);
2063 sidecar.commitments.push(Bytes48::ZERO);
2064 sidecar.cell_proofs.extend(core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB));
2065
2066 let cells_and_proofs =
2067 sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
2068 let matches = sidecar
2069 .match_versioned_hashes_cells_with_settings(&[versioned_hash], cell_mask, settings)
2070 .unwrap()
2071 .collect::<Vec<_>>();
2072 assert_eq!(matches, vec![(0, cells_and_proofs)]);
2073 }
2074}