Skip to main content

alloy_eips/eip7594/
sidecar.rs

1use crate::{
2    eip4844::{
3        Blob, BlobAndProofV2, BlobTransactionSidecar, Bytes48, BYTES_PER_BLOB,
4        BYTES_PER_COMMITMENT, BYTES_PER_PROOF,
5    },
6    eip7594::{CELLS_PER_EXT_BLOB, EIP_7594_WRAPPER_VERSION},
7};
8use alloc::{boxed::Box, vec::Vec};
9use alloy_primitives::{B128, B256};
10use alloy_rlp::{BufMut, Decodable, Encodable, Header};
11
12use super::{Decodable7594, Encodable7594};
13use crate::eip4844::VersionedHashIter;
14#[cfg(feature = "kzg")]
15use crate::eip4844::{AsAlloy, AsCkzg, BlobTransactionValidationError};
16
17/// 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
540impl BlobTransactionSidecarEip7594 {
541    /// Constructs a new [BlobTransactionSidecarEip7594] from a set of blobs, commitments, and
542    /// cell proofs.
543    pub const fn new(
544        blobs: Vec<Blob>,
545        commitments: Vec<Bytes48>,
546        cell_proofs: Vec<Bytes48>,
547    ) -> Self {
548        Self { blobs, commitments, cell_proofs }
549    }
550
551    /// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecarEip7594].
552    #[inline]
553    pub const fn size(&self) -> usize {
554        self.blobs.capacity() * BYTES_PER_BLOB
555            + self.commitments.capacity() * BYTES_PER_COMMITMENT
556            + self.cell_proofs.capacity() * BYTES_PER_PROOF
557    }
558
559    /// Shrinks the sidecar vectors to fit their current contents.
560    #[inline]
561    pub fn shrink_to_fit(&mut self) {
562        self.blobs.shrink_to_fit();
563        self.commitments.shrink_to_fit();
564        self.cell_proofs.shrink_to_fit();
565    }
566
567    /// Tries to create a new [`BlobTransactionSidecarEip7594`] from the hex encoded blob str.
568    ///
569    /// See also [`Blob::from_hex`](c_kzg::Blob::from_hex)
570    #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
571    pub fn try_from_blobs_hex<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
572    where
573        I: IntoIterator<Item = B>,
574        B: AsRef<str>,
575    {
576        let mut converted = Vec::new();
577        for blob in blobs {
578            converted.push(crate::eip4844::utils::hex_to_blob(blob)?);
579        }
580        Self::try_from_blobs(converted)
581    }
582
583    /// Tries to create a new [`BlobTransactionSidecarEip7594`] from the given blob
584    /// bytes.
585    ///
586    /// See also [`Blob::from_bytes`](c_kzg::Blob::from_bytes)
587    #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
588    pub fn try_from_blobs_bytes<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
589    where
590        I: IntoIterator<Item = B>,
591        B: AsRef<[u8]>,
592    {
593        let mut converted = Vec::new();
594        for blob in blobs {
595            converted.push(crate::eip4844::utils::bytes_to_blob(blob)?);
596        }
597        Self::try_from_blobs(converted)
598    }
599
600    /// Tries to create a new [`BlobTransactionSidecarEip7594`] from the given
601    /// blobs and KZG settings.
602    #[cfg(feature = "kzg")]
603    pub fn try_from_blobs_with_settings(
604        blobs: Vec<Blob>,
605        settings: &c_kzg::KzgSettings,
606    ) -> Result<Self, c_kzg::Error> {
607        if let [blob] = blobs.as_slice() {
608            let blob = blob.as_ckzg();
609            let commitment = settings.blob_to_kzg_commitment(blob)?;
610            let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
611            let commitments = vec![Bytes48::from_ckzg(commitment.to_bytes())];
612            let proofs = c_kzg::KzgProof::boxed_slice_as_alloy(kzg_proofs).into();
613            return Ok(Self::new(blobs, commitments, proofs));
614        }
615
616        let mut commitments = Vec::with_capacity(blobs.len());
617        let mut proofs = Vec::with_capacity(blobs.len() * CELLS_PER_EXT_BLOB);
618        for blob in &blobs {
619            let blob = blob.as_ckzg();
620            let commitment = settings.blob_to_kzg_commitment(blob)?;
621            let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
622
623            commitments.push(Bytes48::from_ckzg(commitment.to_bytes()));
624            proofs.extend_from_slice(c_kzg::KzgProof::slice_as_alloy(kzg_proofs.as_ref()));
625        }
626
627        Ok(Self::new(blobs, commitments, proofs))
628    }
629
630    /// Tries to create a new [`BlobTransactionSidecarEip7594`] from the given
631    /// blobs.
632    ///
633    /// This uses the global/default KZG settings, see also
634    /// [`EnvKzgSettings::Default`](crate::eip4844::env_settings::EnvKzgSettings).
635    #[cfg(feature = "kzg")]
636    pub fn try_from_blobs(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error> {
637        use crate::eip4844::env_settings::EnvKzgSettings;
638
639        Self::try_from_blobs_with_settings(blobs, EnvKzgSettings::Default.get())
640    }
641
642    /// Computes the EIP-7594 cells for all blobs using the default KZG settings.
643    ///
644    /// The returned cells use the same blob-major flattened layout as [`Self::cell_proofs`]:
645    /// every blob contributes one contiguous [`CELLS_PER_EXT_BLOB`]-cell chunk. For blob index
646    /// `i` and cell index `j`, the cell is at `i * CELLS_PER_EXT_BLOB + j`.
647    ///
648    /// In other words, the layout is `[blob0_cell0, ..., blob0_cell127, blob1_cell0, ...]`.
649    #[cfg(feature = "kzg")]
650    pub fn compute_cells(&self) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
651        use crate::eip4844::env_settings::EnvKzgSettings;
652
653        self.compute_cells_with_settings(EnvKzgSettings::Default.get())
654    }
655
656    /// Computes the EIP-7594 cells for all blobs using the given KZG settings.
657    ///
658    /// The returned cells use the same blob-major flattened layout as [`Self::cell_proofs`]:
659    /// every blob contributes one contiguous [`CELLS_PER_EXT_BLOB`]-cell chunk. For blob index
660    /// `i` and cell index `j`, the cell is at `i * CELLS_PER_EXT_BLOB + j`.
661    ///
662    /// In other words, the layout is `[blob0_cell0, ..., blob0_cell127, blob1_cell0, ...]`.
663    #[cfg(feature = "kzg")]
664    pub fn compute_cells_with_settings(
665        &self,
666        settings: &c_kzg::KzgSettings,
667    ) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
668        if let [blob] = self.blobs.as_slice() {
669            let blob_cells = settings.compute_cells(blob.as_ckzg())?;
670            return Ok(c_kzg::Cell::boxed_slice_as_alloy(blob_cells).into());
671        }
672
673        let mut cells = Vec::with_capacity(self.blobs.len() * CELLS_PER_EXT_BLOB);
674        for blob in &self.blobs {
675            let blob_cells = settings.compute_cells(blob.as_ckzg())?;
676            cells.extend_from_slice(c_kzg::Cell::slice_as_alloy(blob_cells.as_ref()));
677        }
678        Ok(cells)
679    }
680
681    /// Computes the EIP-7594 cells for all blobs and returns only the cells selected by
682    /// `cell_mask`.
683    ///
684    /// The returned cells keep the blob-major order from [`Self::compute_cells`] but omit cells
685    /// whose indices are not selected by `cell_mask`.
686    ///
687    /// This uses the default KZG settings.
688    #[cfg(feature = "kzg")]
689    pub fn compute_matching_cells(
690        &self,
691        cell_mask: BlobCellMask,
692    ) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
693        use crate::eip4844::env_settings::EnvKzgSettings;
694
695        self.compute_matching_cells_with_settings(cell_mask, EnvKzgSettings::Default.get())
696    }
697
698    /// Computes the EIP-7594 cells for all blobs with the given KZG settings and returns only the
699    /// cells selected by `cell_mask`.
700    ///
701    /// The returned cells keep the blob-major order from [`Self::compute_cells_with_settings`] but
702    /// omit cells whose indices are not selected by `cell_mask`.
703    #[cfg(feature = "kzg")]
704    pub fn compute_matching_cells_with_settings(
705        &self,
706        cell_mask: BlobCellMask,
707        settings: &c_kzg::KzgSettings,
708    ) -> Result<Vec<crate::eip7594::Cell>, c_kzg::Error> {
709        let cells = self.compute_cells_with_settings(settings)?;
710        Ok(cell_mask
711            .matching_cells_from_computed_cells(&cells)
712            .expect("computed cells must contain full extended blob cell chunks"))
713    }
714
715    /// Verifies that the versioned hashes are valid for this sidecar's blob data, commitments, and
716    /// proofs.
717    ///
718    /// Takes as input the [KzgSettings](c_kzg::KzgSettings), which should contain the parameters
719    /// derived from the KZG trusted setup.
720    ///
721    /// This ensures that the blob transaction payload has the expected number of blob data
722    /// elements, commitments, and proofs. The cells are constructed from each blob and verified
723    /// against the commitments and proofs.
724    ///
725    /// Returns [BlobTransactionValidationError::InvalidProof] if any blob KZG proof in the response
726    /// fails to verify, or if the versioned hashes in the transaction do not match the actual
727    /// commitment versioned hashes.
728    #[cfg(feature = "kzg")]
729    pub fn validate(
730        &self,
731        blob_versioned_hashes: &[B256],
732        proof_settings: &c_kzg::KzgSettings,
733    ) -> Result<(), BlobTransactionValidationError> {
734        // Ensure the versioned hashes and commitments have the same length.
735        if blob_versioned_hashes.len() != self.commitments.len() {
736            return Err(c_kzg::Error::MismatchLength(format!(
737                "There are {} versioned commitment hashes and {} commitments",
738                blob_versioned_hashes.len(),
739                self.commitments.len()
740            ))
741            .into());
742        }
743
744        let blobs_len = self.blobs.len();
745        let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
746        if self.cell_proofs.len() != expected_cell_proofs_len {
747            return Err(c_kzg::Error::MismatchLength(format!(
748                "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
749                self.cell_proofs.len(),
750                blobs_len,
751                expected_cell_proofs_len
752            ))
753            .into());
754        }
755
756        // calculate versioned hashes by zipping & iterating
757        for (versioned_hash, commitment) in
758            blob_versioned_hashes.iter().zip(self.commitments.iter())
759        {
760            // calculate & verify versioned hash
761            let calculated_versioned_hash =
762                crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
763            if *versioned_hash != calculated_versioned_hash {
764                return Err(BlobTransactionValidationError::WrongVersionedHash {
765                    have: *versioned_hash,
766                    expected: calculated_versioned_hash,
767                });
768            }
769        }
770
771        // Repeat cell ranges for each blob.
772        let cell_indices =
773            Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
774
775        // Repeat commitments for each cell.
776        let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
777        for commitment in &self.commitments {
778            commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
779        }
780
781        let cells = if let [blob] = self.blobs.as_slice() {
782            let cells: Box<[c_kzg::Cell]> = proof_settings.compute_cells(blob.as_ckzg())?;
783            cells.into()
784        } else {
785            let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
786            for blob in &self.blobs {
787                let blob_cells = proof_settings.compute_cells(blob.as_ckzg())?;
788                cells.extend_from_slice(blob_cells.as_ref());
789            }
790            cells
791        };
792
793        let res = proof_settings.verify_cell_kzg_proof_batch(
794            Bytes48::slice_as_ckzg(&commitments),
795            &cell_indices,
796            &cells,
797            Bytes48::slice_as_ckzg(self.cell_proofs.as_slice()),
798        )?;
799
800        res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
801    }
802
803    /// Returns an iterator over the versioned hashes of the commitments.
804    pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
805        VersionedHashIter::new(&self.commitments)
806    }
807
808    /// Returns the index of the versioned hash in the commitments vector.
809    pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
810        self.commitments.iter().position(|commitment| {
811            crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
812        })
813    }
814
815    /// Returns the blob corresponding to the versioned hash, if it exists.
816    pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
817        self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
818    }
819
820    /// Returns the requested cells and proofs for the blob at `blob_index`, if it exists.
821    ///
822    /// This uses the default KZG settings.
823    #[cfg(feature = "kzg")]
824    pub fn blob_cells_and_proofs(
825        &self,
826        blob_index: usize,
827        cell_mask: BlobCellMask,
828    ) -> Result<Option<crate::eip4844::BlobCellsAndProofsV1>, c_kzg::Error> {
829        use crate::eip4844::env_settings::EnvKzgSettings;
830
831        self.blob_cells_and_proofs_with_settings(
832            blob_index,
833            cell_mask,
834            EnvKzgSettings::Default.get(),
835        )
836    }
837
838    /// Returns the requested cells and proofs for the blob at `blob_index`, if it exists.
839    #[cfg(feature = "kzg")]
840    pub fn blob_cells_and_proofs_with_settings(
841        &self,
842        blob_index: usize,
843        cell_mask: BlobCellMask,
844        settings: &c_kzg::KzgSettings,
845    ) -> Result<Option<crate::eip4844::BlobCellsAndProofsV1>, c_kzg::Error> {
846        let Some(blob) = self.blobs.get(blob_index) else { return Ok(None) };
847
848        let proof_start = blob_index * CELLS_PER_EXT_BLOB;
849        let Some(proofs) = self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
850        else {
851            return Ok(None);
852        };
853
854        if cell_mask.count() == 0 {
855            return Ok(Some(crate::eip4844::BlobCellsAndProofsV1::default()));
856        }
857
858        let cells = settings.compute_cells(blob.as_ckzg())?;
859
860        Ok(Some(Self::blob_cells_and_proofs_from_computed_cells(cell_mask, cells.as_ref(), proofs)))
861    }
862
863    /// Returns the requested cells and proofs from precomputed cells.
864    #[cfg(feature = "kzg")]
865    fn blob_cells_and_proofs_from_computed_cells(
866        cell_mask: BlobCellMask,
867        cells: &[c_kzg::Cell],
868        proofs: &[Bytes48],
869    ) -> crate::eip4844::BlobCellsAndProofsV1 {
870        // The response needs two owned vectors, and `count()` exactly matches
871        // `selected_indices()`, so this avoids reallocations while staying simple.
872        let mut blob_cells = Vec::with_capacity(cell_mask.count());
873        let mut selected_proofs = Vec::with_capacity(cell_mask.count());
874        for cell_index in cell_mask.selected_indices() {
875            blob_cells
876                .push(cells.get(cell_index).map(|cell| crate::eip7594::Cell::new(cell.to_bytes())));
877            selected_proofs.push(proofs.get(cell_index).copied());
878        }
879
880        crate::eip4844::BlobCellsAndProofsV1 { blob_cells, proofs: selected_proofs }
881    }
882
883    /// Matches versioned hashes and returns an iterator of (index, [`BlobAndProofV2`]) pairs
884    /// where index is the position in `versioned_hashes` that matched the versioned hash in the
885    /// sidecar.
886    ///
887    /// This is used for the `engine_getBlobsV2` RPC endpoint of the engine API
888    pub fn match_versioned_hashes<'a>(
889        &'a self,
890        versioned_hashes: &'a [B256],
891    ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
892        self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
893            versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
894                if blob_versioned_hash == *target_hash {
895                    let maybe_blob = self.blobs.get(i);
896                    let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
897                    let maybe_proofs = self
898                        .cell_proofs
899                        .get(proof_range)
900                        .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
901                    if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
902                        return Some((
903                            j,
904                            BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
905                        ));
906                    }
907                }
908                None
909            })
910        })
911    }
912
913    /// Matches versioned hashes and returns (index, [`crate::eip4844::BlobCellsAndProofsV1`])
914    /// pairs where index is the position in `versioned_hashes` that matched the versioned hash in
915    /// the sidecar.
916    ///
917    /// This is used for the `engine_getBlobsV4` RPC endpoint of the engine API.
918    ///
919    /// This uses the default KZG settings.
920    #[cfg(feature = "kzg")]
921    pub fn match_versioned_hashes_cells<'a>(
922        &'a self,
923        versioned_hashes: &'a [B256],
924        cell_mask: BlobCellMask,
925    ) -> Result<
926        impl Iterator<Item = (usize, crate::eip4844::BlobCellsAndProofsV1)> + 'a,
927        c_kzg::Error,
928    > {
929        use crate::eip4844::env_settings::EnvKzgSettings;
930
931        self.match_versioned_hashes_cells_with_settings(
932            versioned_hashes,
933            cell_mask,
934            EnvKzgSettings::Default.get(),
935        )
936    }
937
938    /// Matches versioned hashes and returns (index, [`crate::eip4844::BlobCellsAndProofsV1`])
939    /// pairs where index is the position in `versioned_hashes` that matched the versioned hash in
940    /// the sidecar.
941    #[cfg(feature = "kzg")]
942    pub fn match_versioned_hashes_cells_with_settings<'a>(
943        &'a self,
944        versioned_hashes: &'a [B256],
945        cell_mask: BlobCellMask,
946        settings: &c_kzg::KzgSettings,
947    ) -> Result<
948        impl Iterator<Item = (usize, crate::eip4844::BlobCellsAndProofsV1)> + 'a,
949        c_kzg::Error,
950    > {
951        let mut matches = Vec::new();
952        let mut cells_and_proofs_by_blob =
953            Vec::<(usize, crate::eip4844::BlobCellsAndProofsV1)>::new();
954
955        for (blob_index, commitment) in self.commitments.iter().enumerate() {
956            let blob_versioned_hash = crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
957            for (matched_index, target_hash) in versioned_hashes.iter().enumerate() {
958                if blob_versioned_hash != *target_hash {
959                    continue;
960                }
961
962                let Some(blob) = self.blobs.get(blob_index) else { continue };
963                let proof_start = blob_index * CELLS_PER_EXT_BLOB;
964                let Some(proofs) =
965                    self.cell_proofs.get(proof_start..proof_start + CELLS_PER_EXT_BLOB)
966                else {
967                    continue;
968                };
969
970                let cells_and_proofs = if cell_mask.count() == 0 {
971                    crate::eip4844::BlobCellsAndProofsV1::default()
972                } else if let Some((_, cells_and_proofs)) =
973                    cells_and_proofs_by_blob.iter().find(|(index, _)| *index == blob_index)
974                {
975                    cells_and_proofs.clone()
976                } else {
977                    let cells = settings.compute_cells(blob.as_ckzg())?;
978                    let cells_and_proofs = Self::blob_cells_and_proofs_from_computed_cells(
979                        cell_mask,
980                        cells.as_ref(),
981                        proofs,
982                    );
983                    cells_and_proofs_by_blob.push((blob_index, cells_and_proofs.clone()));
984                    cells_and_proofs
985                };
986
987                matches.push((matched_index, cells_and_proofs));
988            }
989        }
990
991        Ok(matches.into_iter())
992    }
993
994    /// Outputs the RLP length of [BlobTransactionSidecarEip7594] fields without a RLP header.
995    #[doc(hidden)]
996    pub fn rlp_encoded_fields_length(&self) -> usize {
997        // wrapper version + blobs + commitments + cell proofs
998        1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
999    }
1000
1001    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, __without__ a
1002    /// RLP header.
1003    ///
1004    /// This encodes the fields in the following order:
1005    /// - `wrapper_version`
1006    /// - `blobs`
1007    /// - `commitments`
1008    /// - `cell_proofs`
1009    #[inline]
1010    #[doc(hidden)]
1011    pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
1012        // Put version byte.
1013        out.put_u8(EIP_7594_WRAPPER_VERSION);
1014        // Encode the blobs, commitments, and cell proofs
1015        self.blobs.encode(out);
1016        self.commitments.encode(out);
1017        self.cell_proofs.encode(out);
1018    }
1019
1020    /// Creates an RLP header for the [BlobTransactionSidecarEip7594].
1021    fn rlp_header(&self) -> Header {
1022        Header { list: true, payload_length: self.rlp_encoded_fields_length() }
1023    }
1024
1025    /// Calculates the length of the [BlobTransactionSidecarEip7594] when encoded as
1026    /// RLP.
1027    pub fn rlp_encoded_length(&self) -> usize {
1028        self.rlp_header().length() + self.rlp_encoded_fields_length()
1029    }
1030
1031    /// Encodes the [BlobTransactionSidecarEip7594] as RLP bytes.
1032    pub fn rlp_encode(&self, out: &mut dyn BufMut) {
1033        self.rlp_header().encode(out);
1034        self.rlp_encode_fields(out);
1035    }
1036
1037    /// RLP decode the fields of a [BlobTransactionSidecarEip7594].
1038    #[doc(hidden)]
1039    pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1040        Ok(Self {
1041            blobs: Decodable::decode(buf)?,
1042            commitments: Decodable::decode(buf)?,
1043            cell_proofs: Decodable::decode(buf)?,
1044        })
1045    }
1046
1047    /// Decodes the [BlobTransactionSidecarEip7594] from RLP bytes.
1048    pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1049        let header = Header::decode(buf)?;
1050        if !header.list {
1051            return Err(alloy_rlp::Error::UnexpectedString);
1052        }
1053        if buf.len() < header.payload_length {
1054            return Err(alloy_rlp::Error::InputTooShort);
1055        }
1056        let remaining = buf.len();
1057
1058        let this = Self::decode_7594(buf)?;
1059        if buf.len() + header.payload_length != remaining {
1060            return Err(alloy_rlp::Error::UnexpectedLength);
1061        }
1062
1063        Ok(this)
1064    }
1065}
1066
1067impl Encodable for BlobTransactionSidecarEip7594 {
1068    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, without a RLP header.
1069    fn encode(&self, out: &mut dyn BufMut) {
1070        self.rlp_encode(out);
1071    }
1072
1073    fn length(&self) -> usize {
1074        self.rlp_encoded_length()
1075    }
1076}
1077
1078impl Decodable for BlobTransactionSidecarEip7594 {
1079    /// Decodes the inner [BlobTransactionSidecarEip7594] fields from RLP bytes, without a RLP
1080    /// header.
1081    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1082        Self::rlp_decode(buf)
1083    }
1084}
1085
1086impl Encodable7594 for BlobTransactionSidecarEip7594 {
1087    fn encode_7594_len(&self) -> usize {
1088        self.rlp_encoded_fields_length()
1089    }
1090
1091    fn encode_7594(&self, out: &mut dyn BufMut) {
1092        self.rlp_encode_fields(out);
1093    }
1094}
1095
1096impl Decodable7594 for BlobTransactionSidecarEip7594 {
1097    fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
1098        let wrapper_version: u8 = Decodable::decode(buf)?;
1099        if wrapper_version != EIP_7594_WRAPPER_VERSION {
1100            return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
1101        }
1102        Self::rlp_decode_fields(buf)
1103    }
1104}
1105
1106/// Cell indices requested by `engine_getBlobsV4`.
1107#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
1108pub struct BlobCellMask {
1109    value: u128,
1110}
1111
1112impl BlobCellMask {
1113    /// Creates a mask from the Engine API 16-byte, big-endian bitarray.
1114    #[inline]
1115    pub fn new(indices_bitarray: B128) -> Self {
1116        Self { value: u128::from(indices_bitarray) }
1117    }
1118
1119    /// Creates a mask from the raw bit representation.
1120    #[inline]
1121    pub const fn from_bits(value: u128) -> Self {
1122        Self { value }
1123    }
1124
1125    /// Returns the raw bit representation.
1126    #[inline]
1127    pub const fn bits(self) -> u128 {
1128        self.value
1129    }
1130
1131    /// Returns the number of selected cells.
1132    #[inline]
1133    pub const fn count(self) -> usize {
1134        self.value.count_ones() as usize
1135    }
1136
1137    /// Returns true if the given cell index is selected.
1138    #[inline]
1139    pub const fn contains(self, index: usize) -> bool {
1140        index < CELLS_PER_EXT_BLOB && self.value & (1u128 << index) != 0
1141    }
1142
1143    /// Iterates selected cell indices in ascending order.
1144    #[inline]
1145    pub fn selected_indices(self) -> impl Iterator<Item = usize> {
1146        let mut bits = self.value;
1147        core::iter::from_fn(move || {
1148            if bits == 0 {
1149                return None;
1150            }
1151
1152            let index = bits.trailing_zeros() as usize;
1153            bits &= bits - 1;
1154            Some(index)
1155        })
1156    }
1157
1158    /// Returns the selected cells from precomputed blob-major flattened cells.
1159    ///
1160    /// The `cells` slice must use the layout returned by `compute_cells`: each blob contributes
1161    /// one contiguous [`CELLS_PER_EXT_BLOB`]-cell chunk, so `cells.len()` must be evenly divisible
1162    /// by [`CELLS_PER_EXT_BLOB`] (128). This method returns `None` if `cells` ends with an
1163    /// incomplete chunk.
1164    ///
1165    /// The returned cells keep the same chunk order and include only the cell indices selected by
1166    /// this mask.
1167    pub fn matching_cells_from_computed_cells(
1168        self,
1169        cells: &[crate::eip7594::Cell],
1170    ) -> Option<Vec<crate::eip7594::Cell>> {
1171        let chunks = cells.chunks_exact(CELLS_PER_EXT_BLOB);
1172        if !chunks.remainder().is_empty() {
1173            return None;
1174        }
1175
1176        let mut matching_cells = Vec::with_capacity(chunks.len() * self.count());
1177        for blob_cells in chunks {
1178            for cell_index in self.selected_indices() {
1179                let cell = blob_cells
1180                    .get(cell_index)
1181                    .expect("cell mask index must be within extended blob cells");
1182                matching_cells.push(*cell);
1183            }
1184        }
1185
1186        Some(matching_cells)
1187    }
1188}
1189
1190/// Bincode-compatible [`BlobTransactionSidecarVariant`] serde implementation.
1191#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
1192pub mod serde_bincode_compat {
1193    use crate::eip4844::{Blob, Bytes48};
1194    use alloc::{borrow::Cow, vec::Vec};
1195    use serde::{Deserialize, Deserializer, Serialize, Serializer};
1196    use serde_with::{DeserializeAs, SerializeAs};
1197
1198    /// Bincode-compatible [`super::BlobTransactionSidecarVariant`] serde implementation.
1199    ///
1200    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
1201    /// ```rust
1202    /// use alloy_eips::eip7594::{serde_bincode_compat, BlobTransactionSidecarVariant};
1203    /// use serde::{Deserialize, Serialize};
1204    /// use serde_with::serde_as;
1205    ///
1206    /// #[serde_as]
1207    /// #[derive(Serialize, Deserialize)]
1208    /// struct Data {
1209    ///     #[serde_as(as = "serde_bincode_compat::BlobTransactionSidecarVariant")]
1210    ///     sidecar: BlobTransactionSidecarVariant,
1211    /// }
1212    /// ```
1213    #[derive(Debug, Serialize, Deserialize)]
1214    pub struct BlobTransactionSidecarVariant<'a> {
1215        /// The blob data (common to both variants).
1216        pub blobs: Cow<'a, Vec<Blob>>,
1217        /// The blob commitments (common to both variants).
1218        pub commitments: Cow<'a, Vec<Bytes48>>,
1219        /// The blob proofs (EIP-4844 only).
1220        pub proofs: Option<Cow<'a, Vec<Bytes48>>>,
1221        /// The cell proofs (EIP-7594 only).
1222        pub cell_proofs: Option<Cow<'a, Vec<Bytes48>>>,
1223    }
1224
1225    impl<'a> From<&'a super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'a> {
1226        fn from(value: &'a super::BlobTransactionSidecarVariant) -> Self {
1227            match value {
1228                super::BlobTransactionSidecarVariant::Eip4844(sidecar) => Self {
1229                    blobs: Cow::Borrowed(&sidecar.blobs),
1230                    commitments: Cow::Borrowed(&sidecar.commitments),
1231                    proofs: Some(Cow::Borrowed(&sidecar.proofs)),
1232                    cell_proofs: None,
1233                },
1234                super::BlobTransactionSidecarVariant::Eip7594(sidecar) => Self {
1235                    blobs: Cow::Borrowed(&sidecar.blobs),
1236                    commitments: Cow::Borrowed(&sidecar.commitments),
1237                    proofs: None,
1238                    cell_proofs: Some(Cow::Borrowed(&sidecar.cell_proofs)),
1239                },
1240            }
1241        }
1242    }
1243
1244    impl<'a> BlobTransactionSidecarVariant<'a> {
1245        fn try_into_inner(self) -> Result<super::BlobTransactionSidecarVariant, &'static str> {
1246            match (self.proofs, self.cell_proofs) {
1247                (Some(proofs), None) => Ok(super::BlobTransactionSidecarVariant::Eip4844(
1248                    crate::eip4844::BlobTransactionSidecar {
1249                        blobs: self.blobs.into_owned(),
1250                        commitments: self.commitments.into_owned(),
1251                        proofs: proofs.into_owned(),
1252                    },
1253                )),
1254                (None, Some(cell_proofs)) => Ok(super::BlobTransactionSidecarVariant::Eip7594(
1255                    super::BlobTransactionSidecarEip7594 {
1256                        blobs: self.blobs.into_owned(),
1257                        commitments: self.commitments.into_owned(),
1258                        cell_proofs: cell_proofs.into_owned(),
1259                    },
1260                )),
1261                (None, None) => Err("Missing both 'proofs' and 'cell_proofs'"),
1262                (Some(_), Some(_)) => Err("Both 'proofs' and 'cell_proofs' cannot be present"),
1263            }
1264        }
1265    }
1266
1267    impl<'a> From<BlobTransactionSidecarVariant<'a>> for super::BlobTransactionSidecarVariant {
1268        fn from(value: BlobTransactionSidecarVariant<'a>) -> Self {
1269            value.try_into_inner().expect("Invalid BlobTransactionSidecarVariant")
1270        }
1271    }
1272
1273    impl SerializeAs<super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'_> {
1274        fn serialize_as<S>(
1275            source: &super::BlobTransactionSidecarVariant,
1276            serializer: S,
1277        ) -> Result<S::Ok, S::Error>
1278        where
1279            S: Serializer,
1280        {
1281            BlobTransactionSidecarVariant::from(source).serialize(serializer)
1282        }
1283    }
1284
1285    impl<'de> DeserializeAs<'de, super::BlobTransactionSidecarVariant>
1286        for BlobTransactionSidecarVariant<'de>
1287    {
1288        fn deserialize_as<D>(
1289            deserializer: D,
1290        ) -> Result<super::BlobTransactionSidecarVariant, D::Error>
1291        where
1292            D: Deserializer<'de>,
1293        {
1294            let value = BlobTransactionSidecarVariant::deserialize(deserializer)?;
1295            value.try_into_inner().map_err(serde::de::Error::custom)
1296        }
1297    }
1298}
1299
1300#[cfg(test)]
1301mod tests {
1302    use super::*;
1303    #[cfg(feature = "kzg")]
1304    use crate::eip4844::{
1305        builder::{SidecarBuilder, SimpleCoder},
1306        env_settings::EnvKzgSettings,
1307    };
1308
1309    #[test]
1310    fn sidecar_variant_rlp_roundtrip() {
1311        let mut encoded = Vec::new();
1312
1313        // 4844
1314        let empty_sidecar_4844 =
1315            BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
1316        empty_sidecar_4844.encode(&mut encoded);
1317        assert_eq!(
1318            empty_sidecar_4844,
1319            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1320        );
1321
1322        let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
1323            vec![Blob::default()],
1324            vec![Bytes48::ZERO],
1325            vec![Bytes48::ZERO],
1326        ));
1327        encoded.clear();
1328        sidecar_4844.encode(&mut encoded);
1329        assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1330
1331        // 7594
1332        let empty_sidecar_7594 =
1333            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
1334        encoded.clear();
1335        empty_sidecar_7594.encode(&mut encoded);
1336        assert_eq!(
1337            empty_sidecar_7594,
1338            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1339        );
1340
1341        let sidecar_7594 =
1342            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
1343                vec![Blob::default()],
1344                vec![Bytes48::ZERO],
1345                core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
1346            ));
1347        encoded.clear();
1348        sidecar_7594.encode(&mut encoded);
1349        assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1350    }
1351
1352    #[test]
1353    #[cfg(feature = "serde")]
1354    fn sidecar_variant_json_deserialize_sanity() {
1355        let mut eip4844 = BlobTransactionSidecar::default();
1356        eip4844.blobs.push(Blob::repeat_byte(0x2));
1357
1358        let json = serde_json::to_string(&eip4844).unwrap();
1359        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1360        assert!(variant.is_eip4844());
1361        let jsonvariant = serde_json::to_string(&variant).unwrap();
1362        assert_eq!(json, jsonvariant);
1363
1364        let mut eip7594 = BlobTransactionSidecarEip7594::default();
1365        eip7594.blobs.push(Blob::repeat_byte(0x4));
1366        let json = serde_json::to_string(&eip7594).unwrap();
1367        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1368        assert!(variant.is_eip7594());
1369        let jsonvariant = serde_json::to_string(&variant).unwrap();
1370        assert_eq!(json, jsonvariant);
1371    }
1372
1373    #[test]
1374    fn rlp_7594_roundtrip() {
1375        let mut encoded = Vec::new();
1376
1377        let sidecar_4844 = BlobTransactionSidecar::default();
1378        sidecar_4844.encode_7594(&mut encoded);
1379        assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1380
1381        let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
1382        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1383        encoded.clear();
1384        sidecar_variant_4844.encode_7594(&mut encoded);
1385        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1386
1387        let sidecar_7594 = BlobTransactionSidecarEip7594::default();
1388        encoded.clear();
1389        sidecar_7594.encode_7594(&mut encoded);
1390        assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1391
1392        let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
1393        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1394        encoded.clear();
1395        sidecar_variant_7594.encode_7594(&mut encoded);
1396        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1397    }
1398
1399    #[test]
1400    #[cfg(feature = "kzg")]
1401    fn validate_7594_sidecar() {
1402        let sidecar =
1403            SidecarBuilder::<SimpleCoder>::from_slice(b"Blobs are fun!").build_7594().unwrap();
1404        let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1405
1406        sidecar.validate(&versioned_hashes, EnvKzgSettings::Default.get()).unwrap();
1407    }
1408
1409    #[test]
1410    #[cfg(feature = "kzg")]
1411    fn compute_cells_for_7594_sidecar() {
1412        let settings = EnvKzgSettings::Default.get();
1413        let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1414            vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1415            settings,
1416        )
1417        .unwrap();
1418
1419        let cells = sidecar.compute_cells_with_settings(settings).unwrap();
1420        assert_eq!(cells.len(), sidecar.blobs.len() * CELLS_PER_EXT_BLOB);
1421        assert_eq!(sidecar.compute_cells().unwrap(), cells);
1422
1423        let cell_mask = BlobCellMask::from_bits((1u128 << 0) | (1u128 << 7));
1424        let matching_cells =
1425            sidecar.compute_matching_cells_with_settings(cell_mask, settings).unwrap();
1426        let expected_matching_cells = cells
1427            .chunks_exact(CELLS_PER_EXT_BLOB)
1428            .flat_map(|blob_cells| [blob_cells[0], blob_cells[7]])
1429            .collect::<Vec<_>>();
1430        assert_eq!(
1431            cell_mask.matching_cells_from_computed_cells(&cells),
1432            Some(expected_matching_cells.clone())
1433        );
1434        assert_eq!(matching_cells, expected_matching_cells);
1435        assert_eq!(sidecar.compute_matching_cells(cell_mask).unwrap(), expected_matching_cells);
1436        assert!(sidecar.compute_matching_cells(BlobCellMask::default()).unwrap().is_empty());
1437
1438        for (blob_index, blob) in sidecar.blobs.iter().enumerate() {
1439            let expected_cells = settings.compute_cells(blob.as_ckzg()).unwrap();
1440            let start = blob_index * CELLS_PER_EXT_BLOB;
1441            let end = start + CELLS_PER_EXT_BLOB;
1442
1443            for (cell, expected_cell) in cells[start..end].iter().zip(expected_cells.iter()) {
1444                assert_eq!(*cell, crate::eip7594::Cell::new(expected_cell.to_bytes()));
1445            }
1446        }
1447    }
1448
1449    #[test]
1450    fn blob_cell_mask_selects_indices() {
1451        let selected = (1u128 << 0) | (1u128 << 7);
1452        let mask = BlobCellMask::new(B128::from(selected));
1453
1454        assert_eq!(mask.bits(), selected);
1455        assert_eq!(mask.count(), 2);
1456        assert!(mask.contains(0));
1457        assert!(mask.contains(7));
1458        assert!(!mask.contains(1));
1459        assert_eq!(mask.selected_indices().collect::<Vec<_>>(), vec![0, 7]);
1460
1461        let cells = (0..CELLS_PER_EXT_BLOB * 2)
1462            .map(|i| crate::eip7594::Cell::repeat_byte(i as u8))
1463            .collect::<Vec<_>>();
1464        assert_eq!(
1465            mask.matching_cells_from_computed_cells(&cells),
1466            Some(vec![
1467                cells[0],
1468                cells[7],
1469                cells[CELLS_PER_EXT_BLOB],
1470                cells[CELLS_PER_EXT_BLOB + 7]
1471            ])
1472        );
1473        assert_eq!(mask.matching_cells_from_computed_cells(&cells[..cells.len() - 1]), None);
1474    }
1475
1476    #[test]
1477    fn match_versioned_hashes_skips_incomplete_proof_chunks() {
1478        let sidecar = BlobTransactionSidecarEip7594::new(
1479            vec![Blob::repeat_byte(0x01)],
1480            vec![Bytes48::repeat_byte(0x02)],
1481            vec![Bytes48::repeat_byte(0x03)],
1482        );
1483        let versioned_hash = sidecar.versioned_hashes().next().unwrap();
1484
1485        let matches = sidecar.match_versioned_hashes(&[versioned_hash]).collect::<Vec<_>>();
1486        assert!(matches.is_empty());
1487    }
1488
1489    #[test]
1490    #[cfg(feature = "kzg")]
1491    fn match_versioned_hashes_cells_for_7594_sidecar() {
1492        let settings = EnvKzgSettings::Default.get();
1493        let sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1494            vec![Blob::repeat_byte(0x01), Blob::repeat_byte(0x02)],
1495            settings,
1496        )
1497        .unwrap();
1498        let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1499        let cell_mask = BlobCellMask::from_bits((1u128 << 0) | (1u128 << 7));
1500
1501        let cells_and_proofs =
1502            sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
1503        assert_eq!(cells_and_proofs.blob_cells.len(), 2);
1504        assert_eq!(cells_and_proofs.proofs.len(), 2);
1505        assert_eq!(
1506            cells_and_proofs.proofs,
1507            vec![Some(sidecar.cell_proofs[0]), Some(sidecar.cell_proofs[7])]
1508        );
1509
1510        let expected_cells = settings.compute_cells(sidecar.blobs[0].as_ckzg()).unwrap();
1511        assert_eq!(
1512            cells_and_proofs.blob_cells,
1513            vec![
1514                Some(crate::eip7594::Cell::new(expected_cells[0].to_bytes())),
1515                Some(crate::eip7594::Cell::new(expected_cells[7].to_bytes()))
1516            ]
1517        );
1518
1519        let request = vec![versioned_hashes[0], B256::ZERO, versioned_hashes[0]];
1520        let matches = sidecar
1521            .match_versioned_hashes_cells_with_settings(&request, cell_mask, settings)
1522            .unwrap()
1523            .collect::<Vec<_>>();
1524        assert_eq!(matches.len(), 2);
1525        assert_eq!(matches[0], (0, cells_and_proofs.clone()));
1526        assert_eq!(matches[1], (2, cells_and_proofs.clone()));
1527
1528        let default_matches = sidecar
1529            .match_versioned_hashes_cells(&[versioned_hashes[0]], cell_mask)
1530            .unwrap()
1531            .collect::<Vec<_>>();
1532        assert_eq!(default_matches, vec![(0, cells_and_proofs)]);
1533    }
1534
1535    #[test]
1536    #[cfg(feature = "kzg")]
1537    fn match_versioned_hashes_cells_only_computes_matched_blobs() {
1538        let settings = EnvKzgSettings::Default.get();
1539        let mut sidecar = BlobTransactionSidecarEip7594::try_from_blobs_with_settings(
1540            vec![Blob::repeat_byte(0x01)],
1541            settings,
1542        )
1543        .unwrap();
1544        let versioned_hash = sidecar.versioned_hashes().next().unwrap();
1545        let cell_mask = BlobCellMask::from_bits(1);
1546
1547        let invalid_blob = Blob::repeat_byte(0xff);
1548        assert!(settings.compute_cells(invalid_blob.as_ckzg()).is_err());
1549
1550        sidecar.blobs.push(invalid_blob);
1551        sidecar.commitments.push(Bytes48::ZERO);
1552        sidecar.cell_proofs.extend(core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB));
1553
1554        let cells_and_proofs =
1555            sidecar.blob_cells_and_proofs_with_settings(0, cell_mask, settings).unwrap().unwrap();
1556        let matches = sidecar
1557            .match_versioned_hashes_cells_with_settings(&[versioned_hash], cell_mask, settings)
1558            .unwrap()
1559            .collect::<Vec<_>>();
1560        assert_eq!(matches, vec![(0, cells_and_proofs)]);
1561    }
1562}