Skip to main content

alloy_eips/eip7594/
sidecar.rs

1use crate::{
2    eip4844::{
3        Blob, BlobAndProofV2, BlobCellsAndProofsV1, BlobTransactionSidecar, Bytes48,
4        BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_PROOF,
5    },
6    eip7594::{Cell, CELLS_PER_EXT_BLOB, EIP_7594_WRAPPER_VERSION},
7};
8use alloc::{boxed::Box, vec::Vec};
9use alloy_primitives::{B128, B256};
10use alloy_rlp::{BufMut, Decodable, Encodable, Header};
11
12use super::{Decodable7594, Encodable7594};
13#[cfg(feature = "kzg")]
14use crate::eip4844::BlobTransactionValidationError;
15use crate::eip4844::VersionedHashIter;
16
17/// This represents a set of blobs, and its corresponding commitments and proofs.
18/// Proof type depends on the sidecar variant.
19///
20/// This type encodes and decodes the fields without an rlp header.
21#[derive(Clone, PartialEq, Eq, Hash, Debug, derive_more::From)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23#[cfg_attr(feature = "serde", serde(untagged))]
24#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
25pub enum BlobTransactionSidecarVariant {
26    /// EIP-4844 style blob transaction sidecar.
27    Eip4844(BlobTransactionSidecar),
28    /// EIP-7594 style blob transaction sidecar with cell proofs.
29    Eip7594(BlobTransactionSidecarEip7594),
30}
31
32impl Default for BlobTransactionSidecarVariant {
33    fn default() -> Self {
34        Self::Eip4844(BlobTransactionSidecar::default())
35    }
36}
37
38impl BlobTransactionSidecarVariant {
39    /// Returns true if this is a [`BlobTransactionSidecarVariant::Eip4844`].
40    pub const fn is_eip4844(&self) -> bool {
41        matches!(self, Self::Eip4844(_))
42    }
43
44    /// Returns true if this is a [`BlobTransactionSidecarVariant::Eip7594`].
45    pub const fn is_eip7594(&self) -> bool {
46        matches!(self, Self::Eip7594(_))
47    }
48
49    /// Returns the EIP-4844 sidecar if it is [`Self::Eip4844`].
50    pub const fn as_eip4844(&self) -> Option<&BlobTransactionSidecar> {
51        match self {
52            Self::Eip4844(sidecar) => Some(sidecar),
53            _ => None,
54        }
55    }
56
57    /// Returns the EIP-7594 sidecar if it is [`Self::Eip7594`].
58    pub const fn as_eip7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
59        match self {
60            Self::Eip7594(sidecar) => Some(sidecar),
61            _ => None,
62        }
63    }
64
65    /// Converts into EIP-4844 sidecar if it is [`Self::Eip4844`].
66    pub fn into_eip4844(self) -> Option<BlobTransactionSidecar> {
67        match self {
68            Self::Eip4844(sidecar) => Some(sidecar),
69            _ => None,
70        }
71    }
72
73    /// Converts the EIP-7594 sidecar if it is [`Self::Eip7594`].
74    pub fn into_eip7594(self) -> Option<BlobTransactionSidecarEip7594> {
75        match self {
76            Self::Eip7594(sidecar) => Some(sidecar),
77            _ => None,
78        }
79    }
80
81    /// Get a reference to the blobs
82    pub fn blobs(&self) -> &[Blob] {
83        match self {
84            Self::Eip4844(sidecar) => &sidecar.blobs,
85            Self::Eip7594(sidecar) => &sidecar.blobs,
86        }
87    }
88
89    /// Consume self and return the blobs
90    pub fn into_blobs(self) -> Vec<Blob> {
91        match self {
92            Self::Eip4844(sidecar) => sidecar.blobs,
93            Self::Eip7594(sidecar) => sidecar.blobs,
94        }
95    }
96
97    /// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecarVariant].
98    #[inline]
99    pub const fn size(&self) -> usize {
100        match self {
101            Self::Eip4844(sidecar) => sidecar.size(),
102            Self::Eip7594(sidecar) => sidecar.size(),
103        }
104    }
105
106    /// Attempts to convert this sidecar into the EIP-7594 format using default KZG settings.
107    ///
108    /// This method converts an EIP-4844 sidecar to EIP-7594 by computing cell KZG proofs from
109    /// the blob data. If the sidecar is already in EIP-7594 format, it returns itself unchanged.
110    ///
111    /// The conversion requires computing `CELLS_PER_EXT_BLOB` cell proofs for each blob using
112    /// the KZG trusted setup. The default KZG settings are loaded from the environment.
113    ///
114    /// # Returns
115    ///
116    /// - `Ok(Self)` - The sidecar in EIP-7594 format (either converted or unchanged)
117    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
118    ///
119    /// # Examples
120    ///
121    /// ```no_run
122    /// # use alloy_eips::eip7594::BlobTransactionSidecarVariant;
123    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
124    /// # fn example(sidecar: BlobTransactionSidecarVariant) -> Result<(), c_kzg::Error> {
125    /// // Convert an EIP-4844 sidecar to EIP-7594 format
126    /// let eip7594_sidecar = sidecar.try_convert_into_eip7594()?;
127    ///
128    /// // Verify it's now in EIP-7594 format
129    /// assert!(eip7594_sidecar.is_eip7594());
130    /// # Ok(())
131    /// # }
132    /// ```
133    #[cfg(feature = "kzg")]
134    pub fn try_convert_into_eip7594(self) -> Result<Self, c_kzg::Error> {
135        self.try_convert_into_eip7594_with_settings(
136            crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
137        )
138    }
139
140    /// Attempts to convert this sidecar into the EIP-7594 format using custom KZG settings.
141    ///
142    /// This method converts an EIP-4844 sidecar to EIP-7594 by computing cell KZG proofs from
143    /// the blob data using the provided KZG settings. If the sidecar is already in EIP-7594
144    /// format, it returns itself unchanged.
145    ///
146    /// The conversion requires computing `CELLS_PER_EXT_BLOB` cell proofs for each blob using
147    /// the provided KZG trusted setup parameters.
148    ///
149    /// Use this method when you need to specify custom KZG settings rather than using the
150    /// defaults. For most use cases, [`try_convert_into_eip7594`](Self::try_convert_into_eip7594)
151    /// is sufficient.
152    ///
153    /// # Arguments
154    ///
155    /// * `settings` - The KZG settings to use for computing cell proofs
156    ///
157    /// # Returns
158    ///
159    /// - `Ok(Self)` - The sidecar in EIP-7594 format (either converted or unchanged)
160    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
161    ///
162    /// # Examples
163    ///
164    /// ```no_run
165    /// # use alloy_eips::eip7594::BlobTransactionSidecarVariant;
166    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
167    /// # use alloy_eips::eip4844::env_settings::EnvKzgSettings;
168    /// # fn example(sidecar: BlobTransactionSidecarVariant) -> Result<(), c_kzg::Error> {
169    /// // Load custom KZG settings
170    /// let kzg_settings = EnvKzgSettings::Default.get();
171    ///
172    /// // Convert using custom settings
173    /// let eip7594_sidecar = sidecar.try_convert_into_eip7594_with_settings(kzg_settings)?;
174    ///
175    /// // Verify it's now in EIP-7594 format
176    /// assert!(eip7594_sidecar.is_eip7594());
177    /// # Ok(())
178    /// # }
179    /// ```
180    #[cfg(feature = "kzg")]
181    pub fn try_convert_into_eip7594_with_settings(
182        self,
183        settings: &c_kzg::KzgSettings,
184    ) -> Result<Self, c_kzg::Error> {
185        match self {
186            Self::Eip4844(legacy) => legacy.try_into_7594(settings).map(Self::Eip7594),
187            sidecar @ Self::Eip7594(_) => Ok(sidecar),
188        }
189    }
190
191    /// Consumes this sidecar and returns a [`BlobTransactionSidecarEip7594`] using default KZG
192    /// settings.
193    ///
194    /// This method converts an EIP-4844 sidecar to EIP-7594 by computing cell KZG proofs from
195    /// the blob data. If the sidecar is already in EIP-7594 format, it extracts and returns the
196    /// inner [`BlobTransactionSidecarEip7594`].
197    ///
198    /// Unlike [`try_convert_into_eip7594`](Self::try_convert_into_eip7594), this method returns
199    /// the concrete [`BlobTransactionSidecarEip7594`] type rather than the enum variant.
200    ///
201    /// The conversion requires computing `CELLS_PER_EXT_BLOB` cell proofs for each blob using
202    /// the KZG trusted setup. The default KZG settings are loaded from the environment.
203    ///
204    /// # Returns
205    ///
206    /// - `Ok(BlobTransactionSidecarEip7594)` - The sidecar in EIP-7594 format
207    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
208    ///
209    /// # Examples
210    ///
211    /// ```no_run
212    /// # use alloy_eips::eip7594::BlobTransactionSidecarVariant;
213    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
214    /// # fn example(sidecar: BlobTransactionSidecarVariant) -> Result<(), c_kzg::Error> {
215    /// // Convert and extract the EIP-7594 sidecar
216    /// let eip7594_sidecar = sidecar.try_into_eip7594()?;
217    ///
218    /// // Now we have the concrete BlobTransactionSidecarEip7594 type
219    /// assert_eq!(eip7594_sidecar.blobs.len(), eip7594_sidecar.commitments.len());
220    /// # Ok(())
221    /// # }
222    /// ```
223    #[cfg(feature = "kzg")]
224    pub fn try_into_eip7594(self) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
225        self.try_into_eip7594_with_settings(
226            crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
227        )
228    }
229
230    /// Consumes this sidecar and returns a [`BlobTransactionSidecarEip7594`] using custom KZG
231    /// settings.
232    ///
233    /// This method converts an EIP-4844 sidecar to EIP-7594 by computing cell KZG proofs from
234    /// the blob data using the provided KZG settings. If the sidecar is already in EIP-7594
235    /// format, it extracts and returns the inner [`BlobTransactionSidecarEip7594`].
236    ///
237    /// Unlike [`try_convert_into_eip7594_with_settings`](Self::try_convert_into_eip7594_with_settings),
238    /// this method returns the concrete [`BlobTransactionSidecarEip7594`] type rather than the
239    /// enum variant.
240    ///
241    /// The conversion requires computing `CELLS_PER_EXT_BLOB` cell proofs for each blob using
242    /// the provided KZG trusted setup parameters.
243    ///
244    /// Use this method when you need to specify custom KZG settings rather than using the
245    /// defaults. For most use cases, [`try_into_eip7594`](Self::try_into_eip7594) is sufficient.
246    ///
247    /// # Arguments
248    ///
249    /// * `settings` - The KZG settings to use for computing cell proofs
250    ///
251    /// # Returns
252    ///
253    /// - `Ok(BlobTransactionSidecarEip7594)` - The sidecar in EIP-7594 format
254    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
255    ///
256    /// # Examples
257    ///
258    /// ```no_run
259    /// # use alloy_eips::eip7594::BlobTransactionSidecarVariant;
260    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
261    /// # use alloy_eips::eip4844::env_settings::EnvKzgSettings;
262    /// # fn example(sidecar: BlobTransactionSidecarVariant) -> Result<(), c_kzg::Error> {
263    /// // Load custom KZG settings
264    /// let kzg_settings = EnvKzgSettings::Default.get();
265    ///
266    /// // Convert and extract using custom settings
267    /// let eip7594_sidecar = sidecar.try_into_eip7594_with_settings(kzg_settings)?;
268    ///
269    /// // Now we have the concrete BlobTransactionSidecarEip7594 type
270    /// assert_eq!(eip7594_sidecar.blobs.len(), eip7594_sidecar.commitments.len());
271    /// # Ok(())
272    /// # }
273    /// ```
274    #[cfg(feature = "kzg")]
275    pub fn try_into_eip7594_with_settings(
276        self,
277        settings: &c_kzg::KzgSettings,
278    ) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
279        match self {
280            Self::Eip4844(legacy) => legacy.try_into_7594(settings),
281            Self::Eip7594(sidecar) => Ok(sidecar),
282        }
283    }
284
285    /// Verifies that the sidecar is valid. See relevant methods for each variant for more info.
286    #[cfg(feature = "kzg")]
287    pub fn validate(
288        &self,
289        blob_versioned_hashes: &[B256],
290        proof_settings: &c_kzg::KzgSettings,
291    ) -> Result<(), BlobTransactionValidationError> {
292        match self {
293            Self::Eip4844(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
294            Self::Eip7594(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
295        }
296    }
297
298    /// Returns the commitments of the sidecar.
299    pub fn commitments(&self) -> &[Bytes48] {
300        match self {
301            Self::Eip4844(sidecar) => &sidecar.commitments,
302            Self::Eip7594(sidecar) => &sidecar.commitments,
303        }
304    }
305
306    /// Returns an iterator over the versioned hashes of the commitments.
307    pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
308        VersionedHashIter::new(self.commitments())
309    }
310
311    /// Returns the index of the versioned hash in the commitments vector.
312    pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
313        match self {
314            Self::Eip4844(s) => s.versioned_hash_index(hash),
315            Self::Eip7594(s) => s.versioned_hash_index(hash),
316        }
317    }
318
319    /// Returns the blob corresponding to the versioned hash, if it exists.
320    pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
321        match self {
322            Self::Eip4844(s) => s.blob_by_versioned_hash(hash),
323            Self::Eip7594(s) => s.blob_by_versioned_hash(hash),
324        }
325    }
326
327    /// Outputs the RLP length of the [BlobTransactionSidecarVariant] fields, without a RLP header.
328    #[doc(hidden)]
329    pub fn rlp_encoded_fields_length(&self) -> usize {
330        match self {
331            Self::Eip4844(sidecar) => sidecar.rlp_encoded_fields_length(),
332            Self::Eip7594(sidecar) => sidecar.rlp_encoded_fields_length(),
333        }
334    }
335
336    /// Returns the [`Self::rlp_encode_fields`] RLP bytes.
337    #[inline]
338    #[doc(hidden)]
339    pub fn rlp_encoded_fields(&self) -> Vec<u8> {
340        let mut buf = Vec::with_capacity(self.rlp_encoded_fields_length());
341        self.rlp_encode_fields(&mut buf);
342        buf
343    }
344
345    /// Encodes the inner [BlobTransactionSidecarVariant] fields as RLP bytes, __without__ a RLP
346    /// header.
347    #[inline]
348    #[doc(hidden)]
349    pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
350        match self {
351            Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
352            Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
353        }
354    }
355
356    /// RLP decode the fields of a [BlobTransactionSidecarVariant] based on the wrapper version.
357    #[doc(hidden)]
358    pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
359        Self::decode_7594(buf)
360    }
361}
362
363impl Encodable for BlobTransactionSidecarVariant {
364    /// Encodes the [BlobTransactionSidecar] fields as RLP bytes, without a RLP header.
365    fn encode(&self, out: &mut dyn BufMut) {
366        match self {
367            Self::Eip4844(sidecar) => sidecar.encode(out),
368            Self::Eip7594(sidecar) => sidecar.encode(out),
369        }
370    }
371
372    fn length(&self) -> usize {
373        match self {
374            Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
375            Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
376        }
377    }
378}
379
380impl Decodable for BlobTransactionSidecarVariant {
381    /// Decodes the inner [BlobTransactionSidecar] fields from RLP bytes, without a RLP header.
382    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
383        let header = Header::decode(buf)?;
384        if !header.list {
385            return Err(alloy_rlp::Error::UnexpectedString);
386        }
387        if buf.len() < header.payload_length {
388            return Err(alloy_rlp::Error::InputTooShort);
389        }
390        let remaining = buf.len();
391        let this = Self::rlp_decode_fields(buf)?;
392        if buf.len() + header.payload_length != remaining {
393            return Err(alloy_rlp::Error::UnexpectedLength);
394        }
395
396        Ok(this)
397    }
398}
399
400impl Encodable7594 for BlobTransactionSidecarVariant {
401    fn encode_7594_len(&self) -> usize {
402        self.rlp_encoded_fields_length()
403    }
404
405    fn encode_7594(&self, out: &mut dyn BufMut) {
406        self.rlp_encode_fields(out);
407    }
408}
409
410impl Decodable7594 for BlobTransactionSidecarVariant {
411    fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
412        if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
413            Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
414        } else {
415            Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
416        }
417    }
418}
419
420#[cfg(feature = "kzg")]
421impl TryFrom<BlobTransactionSidecarVariant> for BlobTransactionSidecarEip7594 {
422    type Error = c_kzg::Error;
423
424    fn try_from(value: BlobTransactionSidecarVariant) -> Result<Self, Self::Error> {
425        value.try_into_eip7594()
426    }
427}
428
429#[cfg(feature = "serde")]
430impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
431    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
432    where
433        D: serde::Deserializer<'de>,
434    {
435        use core::fmt;
436
437        #[derive(serde::Deserialize, fmt::Debug)]
438        #[serde(field_identifier, rename_all = "camelCase")]
439        enum Field {
440            Blobs,
441            Commitments,
442            Proofs,
443            CellProofs,
444        }
445
446        struct VariantVisitor;
447
448        impl<'de> serde::de::Visitor<'de> for VariantVisitor {
449            type Value = BlobTransactionSidecarVariant;
450
451            fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
452                formatter
453                    .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
454            }
455
456            fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
457            where
458                M: serde::de::MapAccess<'de>,
459            {
460                let mut blobs = None;
461                let mut commitments = None;
462                let mut proofs = None;
463                let mut cell_proofs = None;
464
465                while let Some(key) = map.next_key()? {
466                    match key {
467                        Field::Blobs => {
468                            blobs = Some(crate::eip4844::deserialize_blobs_map(&mut map)?);
469                        }
470                        Field::Commitments => commitments = Some(map.next_value()?),
471                        Field::Proofs => proofs = Some(map.next_value()?),
472                        Field::CellProofs => cell_proofs = Some(map.next_value()?),
473                    }
474                }
475
476                let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
477                let commitments =
478                    commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
479
480                match (cell_proofs, proofs) {
481                    (Some(cp), None) => {
482                        Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
483                            blobs,
484                            commitments,
485                            cell_proofs: cp,
486                        }))
487                    }
488                    (None, Some(pf)) => {
489                        Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
490                            blobs,
491                            commitments,
492                            proofs: pf,
493                        }))
494                    }
495                    (None, None) => {
496                        Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
497                    }
498                    (Some(_), Some(_)) => Err(serde::de::Error::custom(
499                        "Both 'cellProofs' and 'proofs' cannot be present",
500                    )),
501                }
502            }
503        }
504
505        const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
506        deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
507    }
508}
509
510/// This represents a set of blobs, and its corresponding commitments and cell proofs.
511///
512/// This type encodes and decodes the fields without an rlp header.
513#[derive(Clone, Default, PartialEq, Eq, Hash)]
514#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
515#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
516#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
517pub struct BlobTransactionSidecarEip7594 {
518    /// The blob data.
519    #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::eip4844::deserialize_blobs"))]
520    pub blobs: Vec<Blob>,
521    /// The blob commitments.
522    pub commitments: Vec<Bytes48>,
523    /// List of cell proofs for all blobs in the sidecar, including the proofs for the extension
524    /// indices, for a total of `CELLS_PER_EXT_BLOB` proofs per blob (`CELLS_PER_EXT_BLOB` is the
525    /// number of cells for an extended blob, defined in
526    /// [the consensus specs](https://github.com/ethereum/consensus-specs/tree/9d377fd53d029536e57cfda1a4d2c700c59f86bf/specs/fulu/polynomial-commitments-sampling.md#cells))
527    pub cell_proofs: Vec<Bytes48>,
528}
529
530impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
531    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
532        f.debug_struct("BlobTransactionSidecarEip7594")
533            .field("blobs", &self.blobs.len())
534            .field("commitments", &self.commitments)
535            .field("cell_proofs", &self.cell_proofs)
536            .finish()
537    }
538}
539
540/// Cell indices requested by `engine_getBlobsV4`.
541#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
542pub struct BlobCellMask {
543    value: u128,
544}
545
546impl BlobCellMask {
547    /// Creates a mask from the Engine API 16-byte, big-endian bitarray.
548    #[inline]
549    pub fn new(indices_bitarray: B128) -> Self {
550        Self { value: u128::from(indices_bitarray) }
551    }
552
553    /// Creates a mask from the raw bit representation.
554    #[inline]
555    pub const fn from_bits(value: u128) -> Self {
556        Self { value }
557    }
558
559    /// Returns the raw bit representation.
560    #[inline]
561    pub const fn bits(self) -> u128 {
562        self.value
563    }
564
565    /// Returns the number of selected cells.
566    #[inline]
567    pub const fn count(self) -> usize {
568        self.value.count_ones() as usize
569    }
570
571    /// Returns true if the given cell index is selected.
572    #[inline]
573    pub const fn contains(self, index: usize) -> bool {
574        index < CELLS_PER_EXT_BLOB && self.value & (1u128 << index) != 0
575    }
576
577    /// Iterates selected cell indices in ascending order.
578    #[inline]
579    pub fn selected_indices(self) -> impl Iterator<Item = usize> {
580        let mut bits = self.value;
581        core::iter::from_fn(move || {
582            if bits == 0 {
583                return None;
584            }
585
586            let index = bits.trailing_zeros() as usize;
587            bits &= bits - 1;
588            Some(index)
589        })
590    }
591}
592
593impl BlobTransactionSidecarEip7594 {
594    /// Constructs a new [BlobTransactionSidecarEip7594] from a set of blobs, commitments, and
595    /// cell proofs.
596    pub const fn new(
597        blobs: Vec<Blob>,
598        commitments: Vec<Bytes48>,
599        cell_proofs: Vec<Bytes48>,
600    ) -> Self {
601        Self { blobs, commitments, cell_proofs }
602    }
603
604    /// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecarEip7594].
605    #[inline]
606    pub const fn size(&self) -> usize {
607        self.blobs.capacity() * BYTES_PER_BLOB
608            + self.commitments.capacity() * BYTES_PER_COMMITMENT
609            + self.cell_proofs.capacity() * BYTES_PER_PROOF
610    }
611
612    /// Shrinks the sidecar vectors to fit their current contents.
613    #[inline]
614    pub fn shrink_to_fit(&mut self) {
615        self.blobs.shrink_to_fit();
616        self.commitments.shrink_to_fit();
617        self.cell_proofs.shrink_to_fit();
618    }
619
620    /// Tries to create a new [`BlobTransactionSidecarEip7594`] from the hex encoded blob str.
621    ///
622    /// See also [`Blob::from_hex`](c_kzg::Blob::from_hex)
623    #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
624    pub fn try_from_blobs_hex<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
625    where
626        I: IntoIterator<Item = B>,
627        B: AsRef<str>,
628    {
629        let mut converted = Vec::new();
630        for blob in blobs {
631            converted.push(crate::eip4844::utils::hex_to_blob(blob)?);
632        }
633        Self::try_from_blobs(converted)
634    }
635
636    /// Tries to create a new [`BlobTransactionSidecarEip7594`] from the given blob
637    /// bytes.
638    ///
639    /// See also [`Blob::from_bytes`](c_kzg::Blob::from_bytes)
640    #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
641    pub fn try_from_blobs_bytes<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
642    where
643        I: IntoIterator<Item = B>,
644        B: AsRef<[u8]>,
645    {
646        let mut converted = Vec::new();
647        for blob in blobs {
648            converted.push(crate::eip4844::utils::bytes_to_blob(blob)?);
649        }
650        Self::try_from_blobs(converted)
651    }
652
653    /// Tries to create a new [`BlobTransactionSidecarEip7594`] from the given
654    /// blobs and KZG settings.
655    #[cfg(feature = "kzg")]
656    pub fn try_from_blobs_with_settings(
657        blobs: Vec<Blob>,
658        settings: &c_kzg::KzgSettings,
659    ) -> Result<Self, c_kzg::Error> {
660        let mut commitments = Vec::with_capacity(blobs.len());
661        let mut proofs = Vec::with_capacity(blobs.len());
662        for blob in &blobs {
663            // SAFETY: same size
664            let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
665            let commitment = settings.blob_to_kzg_commitment(blob)?;
666            let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
667
668            // SAFETY: same size
669            unsafe {
670                commitments
671                    .push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(commitment.to_bytes()));
672                for kzg_proof in kzg_proofs.iter() {
673                    proofs.push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(
674                        kzg_proof.to_bytes(),
675                    ));
676                }
677            }
678        }
679
680        Ok(Self::new(blobs, commitments, proofs))
681    }
682
683    /// Tries to create a new [`BlobTransactionSidecarEip7594`] from the given
684    /// blobs.
685    ///
686    /// This uses the global/default KZG settings, see also
687    /// [`EnvKzgSettings::Default`](crate::eip4844::env_settings::EnvKzgSettings).
688    #[cfg(feature = "kzg")]
689    pub fn try_from_blobs(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error> {
690        use crate::eip4844::env_settings::EnvKzgSettings;
691
692        Self::try_from_blobs_with_settings(blobs, EnvKzgSettings::Default.get())
693    }
694
695    /// Computes the EIP-7594 cells for all blobs using the default KZG settings.
696    ///
697    /// The returned cells are flattened by blob and each blob contributes
698    /// [`CELLS_PER_EXT_BLOB`] cells. For blob index `i` and cell index `j`, the cell is at
699    /// `i * CELLS_PER_EXT_BLOB + j`, matching the [`Self::cell_proofs`] layout.
700    #[cfg(feature = "kzg")]
701    pub fn compute_cells(&self) -> Result<Vec<Cell>, c_kzg::Error> {
702        use crate::eip4844::env_settings::EnvKzgSettings;
703
704        self.compute_cells_with_settings(EnvKzgSettings::Default.get())
705    }
706
707    /// Computes the EIP-7594 cells for all blobs using the given KZG settings.
708    ///
709    /// The returned cells are flattened by blob and each blob contributes
710    /// [`CELLS_PER_EXT_BLOB`] cells. For blob index `i` and cell index `j`, the cell is at
711    /// `i * CELLS_PER_EXT_BLOB + j`, matching the [`Self::cell_proofs`] layout.
712    #[cfg(feature = "kzg")]
713    pub fn compute_cells_with_settings(
714        &self,
715        settings: &c_kzg::KzgSettings,
716    ) -> Result<Vec<Cell>, c_kzg::Error> {
717        let mut cells = Vec::with_capacity(self.blobs.len() * CELLS_PER_EXT_BLOB);
718        for blob in &self.blobs {
719            // SAFETY: Blob and c_kzg::Blob have the same memory layout.
720            let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
721            let blob_cells = settings.compute_cells(blob)?;
722            cells.extend(blob_cells.iter().map(|cell| Cell::new(cell.to_bytes())));
723        }
724        Ok(cells)
725    }
726
727    /// Verifies that the versioned hashes are valid for this sidecar's blob data, commitments, and
728    /// proofs.
729    ///
730    /// Takes as input the [KzgSettings](c_kzg::KzgSettings), which should contain the parameters
731    /// derived from the KZG trusted setup.
732    ///
733    /// This ensures that the blob transaction payload has the expected number of blob data
734    /// elements, commitments, and proofs. The cells are constructed from each blob and verified
735    /// against the commitments and proofs.
736    ///
737    /// Returns [BlobTransactionValidationError::InvalidProof] if any blob KZG proof in the response
738    /// fails to verify, or if the versioned hashes in the transaction do not match the actual
739    /// commitment versioned hashes.
740    #[cfg(feature = "kzg")]
741    pub fn validate(
742        &self,
743        blob_versioned_hashes: &[B256],
744        proof_settings: &c_kzg::KzgSettings,
745    ) -> Result<(), BlobTransactionValidationError> {
746        // Ensure the versioned hashes and commitments have the same length.
747        if blob_versioned_hashes.len() != self.commitments.len() {
748            return Err(c_kzg::Error::MismatchLength(format!(
749                "There are {} versioned commitment hashes and {} commitments",
750                blob_versioned_hashes.len(),
751                self.commitments.len()
752            ))
753            .into());
754        }
755
756        let blobs_len = self.blobs.len();
757        let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
758        if self.cell_proofs.len() != expected_cell_proofs_len {
759            return Err(c_kzg::Error::MismatchLength(format!(
760                "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
761                self.cell_proofs.len(),
762                blobs_len,
763                expected_cell_proofs_len
764            ))
765            .into());
766        }
767
768        // calculate versioned hashes by zipping & iterating
769        for (versioned_hash, commitment) in
770            blob_versioned_hashes.iter().zip(self.commitments.iter())
771        {
772            // calculate & verify versioned hash
773            let calculated_versioned_hash =
774                crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
775            if *versioned_hash != calculated_versioned_hash {
776                return Err(BlobTransactionValidationError::WrongVersionedHash {
777                    have: *versioned_hash,
778                    expected: calculated_versioned_hash,
779                });
780            }
781        }
782
783        // Repeat cell ranges for each blob.
784        let cell_indices =
785            Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
786
787        // Repeat commitments for each cell.
788        let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
789        for commitment in &self.commitments {
790            commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
791        }
792
793        // SAFETY: ALL types have the same size
794        let res = unsafe {
795            let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
796            for blob in &self.blobs {
797                let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
798                let blob_cells = proof_settings.compute_cells(blob)?;
799                cells.extend_from_slice(blob_cells.as_ref());
800            }
801
802            proof_settings.verify_cell_kzg_proof_batch(
803                // commitments
804                core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
805                // cell indices
806                &cell_indices,
807                // cells
808                &cells,
809                // proofs
810                core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
811            )?
812        };
813
814        res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
815    }
816
817    /// Returns an iterator over the versioned hashes of the commitments.
818    pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
819        VersionedHashIter::new(&self.commitments)
820    }
821
822    /// Returns the index of the versioned hash in the commitments vector.
823    pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
824        self.commitments.iter().position(|commitment| {
825            crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
826        })
827    }
828
829    /// Returns the blob corresponding to the versioned hash, if it exists.
830    pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
831        self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
832    }
833
834    /// Returns the requested cells and proofs for the blob at `blob_index`, if it exists.
835    ///
836    /// This uses the default KZG settings.
837    #[cfg(feature = "kzg")]
838    pub fn blob_cells_and_proofs(
839        &self,
840        blob_index: usize,
841        cell_mask: BlobCellMask,
842    ) -> Result<Option<BlobCellsAndProofsV1>, c_kzg::Error> {
843        use crate::eip4844::env_settings::EnvKzgSettings;
844
845        self.blob_cells_and_proofs_with_settings(
846            blob_index,
847            cell_mask,
848            EnvKzgSettings::Default.get(),
849        )
850    }
851
852    /// Returns the requested cells and proofs for the blob at `blob_index`, if it exists.
853    #[cfg(feature = "kzg")]
854    pub fn blob_cells_and_proofs_with_settings(
855        &self,
856        blob_index: usize,
857        cell_mask: BlobCellMask,
858        settings: &c_kzg::KzgSettings,
859    ) -> Result<Option<BlobCellsAndProofsV1>, c_kzg::Error> {
860        let Some(blob) = self.blobs.get(blob_index) else { return Ok(None) };
861
862        let proof_start = blob_index * CELLS_PER_EXT_BLOB;
863        let Some(proofs) = self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
864        else {
865            return Ok(None);
866        };
867
868        if cell_mask.count() == 0 {
869            return Ok(Some(BlobCellsAndProofsV1::default()));
870        }
871
872        // SAFETY: Blob and c_kzg::Blob have the same memory layout.
873        let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
874        let cells = settings.compute_cells(blob)?;
875
876        Ok(Some(Self::blob_cells_and_proofs_from_computed_cells(cell_mask, cells.as_ref(), proofs)))
877    }
878
879    /// Returns the requested cells and proofs from precomputed cells.
880    #[cfg(feature = "kzg")]
881    fn blob_cells_and_proofs_from_computed_cells(
882        cell_mask: BlobCellMask,
883        cells: &[c_kzg::Cell],
884        proofs: &[Bytes48],
885    ) -> BlobCellsAndProofsV1 {
886        let mut blob_cells = Vec::with_capacity(cell_mask.count());
887        let mut selected_proofs = Vec::with_capacity(cell_mask.count());
888        for cell_index in cell_mask.selected_indices() {
889            blob_cells.push(Some(Cell::new(cells[cell_index].to_bytes())));
890            selected_proofs.push(Some(proofs[cell_index]));
891        }
892
893        BlobCellsAndProofsV1 { blob_cells, proofs: selected_proofs }
894    }
895
896    /// Matches versioned hashes and returns an iterator of (index, [`BlobAndProofV2`]) pairs
897    /// where index is the position in `versioned_hashes` that matched the versioned hash in the
898    /// sidecar.
899    ///
900    /// This is used for the `engine_getBlobsV2` RPC endpoint of the engine API
901    pub fn match_versioned_hashes<'a>(
902        &'a self,
903        versioned_hashes: &'a [B256],
904    ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
905        self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
906            versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
907                if blob_versioned_hash == *target_hash {
908                    let maybe_blob = self.blobs.get(i);
909                    let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
910                    let maybe_proofs = Some(&self.cell_proofs[proof_range])
911                        .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
912                    if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
913                        return Some((
914                            j,
915                            BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
916                        ));
917                    }
918                }
919                None
920            })
921        })
922    }
923
924    /// Matches versioned hashes and returns (index, [`BlobCellsAndProofsV1`]) pairs where index is
925    /// the position in `versioned_hashes` that matched the versioned hash in the sidecar.
926    ///
927    /// This is used for the `engine_getBlobsV4` RPC endpoint of the engine API.
928    ///
929    /// This uses the default KZG settings.
930    #[cfg(feature = "kzg")]
931    pub fn match_versioned_hashes_cells<'a>(
932        &'a self,
933        versioned_hashes: &'a [B256],
934        cell_mask: BlobCellMask,
935    ) -> Result<impl Iterator<Item = (usize, BlobCellsAndProofsV1)> + 'a, c_kzg::Error> {
936        use crate::eip4844::env_settings::EnvKzgSettings;
937
938        self.match_versioned_hashes_cells_with_settings(
939            versioned_hashes,
940            cell_mask,
941            EnvKzgSettings::Default.get(),
942        )
943    }
944
945    /// Matches versioned hashes and returns (index, [`BlobCellsAndProofsV1`]) pairs where index is
946    /// the position in `versioned_hashes` that matched the versioned hash in the sidecar.
947    #[cfg(feature = "kzg")]
948    pub fn match_versioned_hashes_cells_with_settings<'a>(
949        &'a self,
950        versioned_hashes: &'a [B256],
951        cell_mask: BlobCellMask,
952        settings: &c_kzg::KzgSettings,
953    ) -> Result<impl Iterator<Item = (usize, BlobCellsAndProofsV1)> + 'a, c_kzg::Error> {
954        let mut matches = Vec::new();
955        let mut cells_and_proofs_by_blob = Vec::<(usize, BlobCellsAndProofsV1)>::new();
956
957        for (blob_index, commitment) in self.commitments.iter().enumerate() {
958            let blob_versioned_hash = crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
959            for (matched_index, target_hash) in versioned_hashes.iter().enumerate() {
960                if blob_versioned_hash != *target_hash {
961                    continue;
962                }
963
964                let Some(blob) = self.blobs.get(blob_index) else { continue };
965                let proof_start = blob_index * CELLS_PER_EXT_BLOB;
966                let Some(proofs) =
967                    self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
968                else {
969                    continue;
970                };
971
972                let cells_and_proofs = if cell_mask.count() == 0 {
973                    BlobCellsAndProofsV1::default()
974                } else if let Some((_, cells_and_proofs)) =
975                    cells_and_proofs_by_blob.iter().find(|(index, _)| *index == blob_index)
976                {
977                    cells_and_proofs.clone()
978                } else {
979                    // SAFETY: Blob and c_kzg::Blob have the same memory layout.
980                    let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
981                    let cells = settings.compute_cells(blob)?;
982                    let cells_and_proofs = Self::blob_cells_and_proofs_from_computed_cells(
983                        cell_mask,
984                        cells.as_ref(),
985                        proofs,
986                    );
987                    cells_and_proofs_by_blob.push((blob_index, cells_and_proofs.clone()));
988                    cells_and_proofs
989                };
990
991                matches.push((matched_index, cells_and_proofs));
992            }
993        }
994
995        Ok(matches.into_iter())
996    }
997
998    /// Outputs the RLP length of [BlobTransactionSidecarEip7594] fields without a RLP header.
999    #[doc(hidden)]
1000    pub fn rlp_encoded_fields_length(&self) -> usize {
1001        // wrapper version + blobs + commitments + cell proofs
1002        1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
1003    }
1004
1005    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, __without__ a
1006    /// RLP header.
1007    ///
1008    /// This encodes the fields in the following order:
1009    /// - `wrapper_version`
1010    /// - `blobs`
1011    /// - `commitments`
1012    /// - `cell_proofs`
1013    #[inline]
1014    #[doc(hidden)]
1015    pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
1016        // Put version byte.
1017        out.put_u8(EIP_7594_WRAPPER_VERSION);
1018        // Encode the blobs, commitments, and cell proofs
1019        self.blobs.encode(out);
1020        self.commitments.encode(out);
1021        self.cell_proofs.encode(out);
1022    }
1023
1024    /// Creates an RLP header for the [BlobTransactionSidecarEip7594].
1025    fn rlp_header(&self) -> Header {
1026        Header { list: true, payload_length: self.rlp_encoded_fields_length() }
1027    }
1028
1029    /// Calculates the length of the [BlobTransactionSidecarEip7594] when encoded as
1030    /// RLP.
1031    pub fn rlp_encoded_length(&self) -> usize {
1032        self.rlp_header().length() + self.rlp_encoded_fields_length()
1033    }
1034
1035    /// Encodes the [BlobTransactionSidecarEip7594] as RLP bytes.
1036    pub fn rlp_encode(&self, out: &mut dyn BufMut) {
1037        self.rlp_header().encode(out);
1038        self.rlp_encode_fields(out);
1039    }
1040
1041    /// RLP decode the fields of a [BlobTransactionSidecarEip7594].
1042    #[doc(hidden)]
1043    pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1044        Ok(Self {
1045            blobs: Decodable::decode(buf)?,
1046            commitments: Decodable::decode(buf)?,
1047            cell_proofs: Decodable::decode(buf)?,
1048        })
1049    }
1050
1051    /// Decodes the [BlobTransactionSidecarEip7594] from RLP bytes.
1052    pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1053        let header = Header::decode(buf)?;
1054        if !header.list {
1055            return Err(alloy_rlp::Error::UnexpectedString);
1056        }
1057        if buf.len() < header.payload_length {
1058            return Err(alloy_rlp::Error::InputTooShort);
1059        }
1060        let remaining = buf.len();
1061
1062        let this = Self::decode_7594(buf)?;
1063        if buf.len() + header.payload_length != remaining {
1064            return Err(alloy_rlp::Error::UnexpectedLength);
1065        }
1066
1067        Ok(this)
1068    }
1069}
1070
1071impl Encodable for BlobTransactionSidecarEip7594 {
1072    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, without a RLP header.
1073    fn encode(&self, out: &mut dyn BufMut) {
1074        self.rlp_encode(out);
1075    }
1076
1077    fn length(&self) -> usize {
1078        self.rlp_encoded_length()
1079    }
1080}
1081
1082impl Decodable for BlobTransactionSidecarEip7594 {
1083    /// Decodes the inner [BlobTransactionSidecarEip7594] fields from RLP bytes, without a RLP
1084    /// header.
1085    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1086        Self::rlp_decode(buf)
1087    }
1088}
1089
1090impl Encodable7594 for BlobTransactionSidecarEip7594 {
1091    fn encode_7594_len(&self) -> usize {
1092        self.rlp_encoded_fields_length()
1093    }
1094
1095    fn encode_7594(&self, out: &mut dyn BufMut) {
1096        self.rlp_encode_fields(out);
1097    }
1098}
1099
1100impl Decodable7594 for BlobTransactionSidecarEip7594 {
1101    fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1102        let wrapper_version: u8 = Decodable::decode(buf)?;
1103        if wrapper_version != EIP_7594_WRAPPER_VERSION {
1104            return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
1105        }
1106        Self::rlp_decode_fields(buf)
1107    }
1108}
1109
1110/// Bincode-compatible [`BlobTransactionSidecarVariant`] serde implementation.
1111#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
1112pub mod serde_bincode_compat {
1113    use crate::eip4844::{Blob, Bytes48};
1114    use alloc::{borrow::Cow, vec::Vec};
1115    use serde::{Deserialize, Deserializer, Serialize, Serializer};
1116    use serde_with::{DeserializeAs, SerializeAs};
1117
1118    /// Bincode-compatible [`super::BlobTransactionSidecarVariant`] serde implementation.
1119    ///
1120    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
1121    /// ```rust
1122    /// use alloy_eips::eip7594::{serde_bincode_compat, BlobTransactionSidecarVariant};
1123    /// use serde::{Deserialize, Serialize};
1124    /// use serde_with::serde_as;
1125    ///
1126    /// #[serde_as]
1127    /// #[derive(Serialize, Deserialize)]
1128    /// struct Data {
1129    ///     #[serde_as(as = "serde_bincode_compat::BlobTransactionSidecarVariant")]
1130    ///     sidecar: BlobTransactionSidecarVariant,
1131    /// }
1132    /// ```
1133    #[derive(Debug, Serialize, Deserialize)]
1134    pub struct BlobTransactionSidecarVariant<'a> {
1135        /// The blob data (common to both variants).
1136        pub blobs: Cow<'a, Vec<Blob>>,
1137        /// The blob commitments (common to both variants).
1138        pub commitments: Cow<'a, Vec<Bytes48>>,
1139        /// The blob proofs (EIP-4844 only).
1140        pub proofs: Option<Cow<'a, Vec<Bytes48>>>,
1141        /// The cell proofs (EIP-7594 only).
1142        pub cell_proofs: Option<Cow<'a, Vec<Bytes48>>>,
1143    }
1144
1145    impl<'a> From<&'a super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'a> {
1146        fn from(value: &'a super::BlobTransactionSidecarVariant) -> Self {
1147            match value {
1148                super::BlobTransactionSidecarVariant::Eip4844(sidecar) => Self {
1149                    blobs: Cow::Borrowed(&sidecar.blobs),
1150                    commitments: Cow::Borrowed(&sidecar.commitments),
1151                    proofs: Some(Cow::Borrowed(&sidecar.proofs)),
1152                    cell_proofs: None,
1153                },
1154                super::BlobTransactionSidecarVariant::Eip7594(sidecar) => Self {
1155                    blobs: Cow::Borrowed(&sidecar.blobs),
1156                    commitments: Cow::Borrowed(&sidecar.commitments),
1157                    proofs: None,
1158                    cell_proofs: Some(Cow::Borrowed(&sidecar.cell_proofs)),
1159                },
1160            }
1161        }
1162    }
1163
1164    impl<'a> BlobTransactionSidecarVariant<'a> {
1165        fn try_into_inner(self) -> Result<super::BlobTransactionSidecarVariant, &'static str> {
1166            match (self.proofs, self.cell_proofs) {
1167                (Some(proofs), None) => Ok(super::BlobTransactionSidecarVariant::Eip4844(
1168                    crate::eip4844::BlobTransactionSidecar {
1169                        blobs: self.blobs.into_owned(),
1170                        commitments: self.commitments.into_owned(),
1171                        proofs: proofs.into_owned(),
1172                    },
1173                )),
1174                (None, Some(cell_proofs)) => Ok(super::BlobTransactionSidecarVariant::Eip7594(
1175                    super::BlobTransactionSidecarEip7594 {
1176                        blobs: self.blobs.into_owned(),
1177                        commitments: self.commitments.into_owned(),
1178                        cell_proofs: cell_proofs.into_owned(),
1179                    },
1180                )),
1181                (None, None) => Err("Missing both 'proofs' and 'cell_proofs'"),
1182                (Some(_), Some(_)) => Err("Both 'proofs' and 'cell_proofs' cannot be present"),
1183            }
1184        }
1185    }
1186
1187    impl<'a> From<BlobTransactionSidecarVariant<'a>> for super::BlobTransactionSidecarVariant {
1188        fn from(value: BlobTransactionSidecarVariant<'a>) -> Self {
1189            value.try_into_inner().expect("Invalid BlobTransactionSidecarVariant")
1190        }
1191    }
1192
1193    impl SerializeAs<super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'_> {
1194        fn serialize_as<S>(
1195            source: &super::BlobTransactionSidecarVariant,
1196            serializer: S,
1197        ) -> Result<S::Ok, S::Error>
1198        where
1199            S: Serializer,
1200        {
1201            BlobTransactionSidecarVariant::from(source).serialize(serializer)
1202        }
1203    }
1204
1205    impl<'de> DeserializeAs<'de, super::BlobTransactionSidecarVariant>
1206        for BlobTransactionSidecarVariant<'de>
1207    {
1208        fn deserialize_as<D>(
1209            deserializer: D,
1210        ) -> Result<super::BlobTransactionSidecarVariant, D::Error>
1211        where
1212            D: Deserializer<'de>,
1213        {
1214            let value = BlobTransactionSidecarVariant::deserialize(deserializer)?;
1215            value.try_into_inner().map_err(serde::de::Error::custom)
1216        }
1217    }
1218}
1219
1220#[cfg(test)]
1221mod tests {
1222    use super::*;
1223    #[cfg(feature = "kzg")]
1224    use crate::eip4844::{
1225        builder::{SidecarBuilder, SimpleCoder},
1226        env_settings::EnvKzgSettings,
1227    };
1228
1229    #[test]
1230    fn sidecar_variant_rlp_roundtrip() {
1231        let mut encoded = Vec::new();
1232
1233        // 4844
1234        let empty_sidecar_4844 =
1235            BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
1236        empty_sidecar_4844.encode(&mut encoded);
1237        assert_eq!(
1238            empty_sidecar_4844,
1239            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1240        );
1241
1242        let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
1243            vec![Blob::default()],
1244            vec![Bytes48::ZERO],
1245            vec![Bytes48::ZERO],
1246        ));
1247        encoded.clear();
1248        sidecar_4844.encode(&mut encoded);
1249        assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1250
1251        // 7594
1252        let empty_sidecar_7594 =
1253            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
1254        encoded.clear();
1255        empty_sidecar_7594.encode(&mut encoded);
1256        assert_eq!(
1257            empty_sidecar_7594,
1258            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1259        );
1260
1261        let sidecar_7594 =
1262            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
1263                vec![Blob::default()],
1264                vec![Bytes48::ZERO],
1265                core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
1266            ));
1267        encoded.clear();
1268        sidecar_7594.encode(&mut encoded);
1269        assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1270    }
1271
1272    #[test]
1273    #[cfg(feature = "serde")]
1274    fn sidecar_variant_json_deserialize_sanity() {
1275        let mut eip4844 = BlobTransactionSidecar::default();
1276        eip4844.blobs.push(Blob::repeat_byte(0x2));
1277
1278        let json = serde_json::to_string(&eip4844).unwrap();
1279        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1280        assert!(variant.is_eip4844());
1281        let jsonvariant = serde_json::to_string(&variant).unwrap();
1282        assert_eq!(json, jsonvariant);
1283
1284        let mut eip7594 = BlobTransactionSidecarEip7594::default();
1285        eip7594.blobs.push(Blob::repeat_byte(0x4));
1286        let json = serde_json::to_string(&eip7594).unwrap();
1287        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1288        assert!(variant.is_eip7594());
1289        let jsonvariant = serde_json::to_string(&variant).unwrap();
1290        assert_eq!(json, jsonvariant);
1291    }
1292
1293    #[test]
1294    fn rlp_7594_roundtrip() {
1295        let mut encoded = Vec::new();
1296
1297        let sidecar_4844 = BlobTransactionSidecar::default();
1298        sidecar_4844.encode_7594(&mut encoded);
1299        assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1300
1301        let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
1302        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1303        encoded.clear();
1304        sidecar_variant_4844.encode_7594(&mut encoded);
1305        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1306
1307        let sidecar_7594 = BlobTransactionSidecarEip7594::default();
1308        encoded.clear();
1309        sidecar_7594.encode_7594(&mut encoded);
1310        assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1311
1312        let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
1313        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1314        encoded.clear();
1315        sidecar_variant_7594.encode_7594(&mut encoded);
1316        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1317    }
1318
1319    #[test]
1320    #[cfg(feature = "kzg")]
1321    fn validate_7594_sidecar() {
1322        let sidecar =
1323            SidecarBuilder::<SimpleCoder>::from_slice(b"Blobs are fun!").build_7594().unwrap();
1324        let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1325
1326        sidecar.validate(&versioned_hashes, EnvKzgSettings::Default.get()).unwrap();
1327    }
1328
1329    #[test]
1330    #[cfg(feature = "kzg")]
1331    fn compute_cells_for_7594_sidecar() {
1332        let settings = EnvKzgSettings::Default.get();
1333        let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1334            vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1335            settings,
1336        )
1337        .unwrap();
1338
1339        let cells = sidecar.compute_cells_with_settings(settings).unwrap();
1340        assert_eq!(cells.len(), sidecar.blobs.len() * CELLS_PER_EXT_BLOB);
1341        assert_eq!(sidecar.compute_cells().unwrap(), cells);
1342
1343        for (blob_index, blob) in sidecar.blobs.iter().enumerate() {
1344            // SAFETY: Blob and c_kzg::Blob have the same memory layout.
1345            let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
1346            let expected_cells = settings.compute_cells(blob).unwrap();
1347            let start = blob_index * CELLS_PER_EXT_BLOB;
1348            let end = start + CELLS_PER_EXT_BLOB;
1349
1350            for (cell, expected_cell) in cells[start..end].iter().zip(expected_cells.iter()) {
1351                assert_eq!(*cell, Cell::new(expected_cell.to_bytes()));
1352            }
1353        }
1354    }
1355
1356    #[test]
1357    fn blob_cell_mask_selects_indices() {
1358        let selected = (1u128 << 0) | (1u128 << 7);
1359        let mask = BlobCellMask::new(B128::from(selected));
1360
1361        assert_eq!(mask.bits(), selected);
1362        assert_eq!(mask.count(), 2);
1363        assert!(mask.contains(0));
1364        assert!(mask.contains(7));
1365        assert!(!mask.contains(1));
1366        assert_eq!(mask.selected_indices().collect::<Vec<_>>(), vec![0, 7]);
1367    }
1368
1369    #[test]
1370    #[cfg(feature = "kzg")]
1371    fn match_versioned_hashes_cells_for_7594_sidecar() {
1372        let settings = EnvKzgSettings::Default.get();
1373        let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1374            vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1375            settings,
1376        )
1377        .unwrap();
1378        let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1379        let cell_mask = BlobCellMask::from_bits((1u128 << 0) | (1u128 << 7));
1380
1381        let cells_and_proofs =
1382            sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
1383        assert_eq!(cells_and_proofs.blob_cells.len(), 2);
1384        assert_eq!(cells_and_proofs.proofs.len(), 2);
1385        assert_eq!(
1386            cells_and_proofs.proofs,
1387            vec![Some(sidecar.cell_proofs[0]), Some(sidecar.cell_proofs[7])]
1388        );
1389
1390        // SAFETY: Blob and c_kzg::Blob have the same memory layout.
1391        let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(&sidecar.blobs[0]) };
1392        let expected_cells = settings.compute_cells(blob).unwrap();
1393        assert_eq!(
1394            cells_and_proofs.blob_cells,
1395            vec![
1396                Some(Cell::new(expected_cells[0].to_bytes())),
1397                Some(Cell::new(expected_cells[7].to_bytes()))
1398            ]
1399        );
1400
1401        let request = vec![versioned_hashes[0], B256::ZERO, versioned_hashes[0]];
1402        let matches = sidecar
1403            .match_versioned_hashes_cells_with_settings(&request, cell_mask, settings)
1404            .unwrap()
1405            .collect::<Vec<_>>();
1406        assert_eq!(matches.len(), 2);
1407        assert_eq!(matches[0], (0, cells_and_proofs.clone()));
1408        assert_eq!(matches[1], (2, cells_and_proofs.clone()));
1409
1410        let default_matches = sidecar
1411            .match_versioned_hashes_cells(&[versioned_hashes[0]], cell_mask)
1412            .unwrap()
1413            .collect::<Vec<_>>();
1414        assert_eq!(default_matches, vec![(0, cells_and_proofs)]);
1415    }
1416
1417    #[test]
1418    #[cfg(feature = "kzg")]
1419    fn match_versioned_hashes_cells_only_computes_matched_blobs() {
1420        let settings = EnvKzgSettings::Default.get();
1421        let mut sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1422            vec![Blob::repeat_byte(0x01)],
1423            settings,
1424        )
1425        .unwrap();
1426        let versioned_hash = sidecar.versioned_hashes().next().unwrap();
1427        let cell_mask = BlobCellMask::from_bits(1);
1428
1429        let invalid_blob = Blob::repeat_byte(0xff);
1430        // SAFETY: Blob and c_kzg::Blob have the same memory layout.
1431        let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(&invalid_blob) };
1432        assert!(settings.compute_cells(blob).is_err());
1433
1434        sidecar.blobs.push(invalid_blob);
1435        sidecar.commitments.push(Bytes48::ZERO);
1436        sidecar.cell_proofs.extend(core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB));
1437
1438        let cells_and_proofs =
1439            sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
1440        let matches = sidecar
1441            .match_versioned_hashes_cells_with_settings(&[versioned_hash], cell_mask, settings)
1442            .unwrap()
1443            .collect::<Vec<_>>();
1444        assert_eq!(matches, vec![(0, cells_and_proofs)]);
1445    }
1446}