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::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 BlobTransactionSidecarVariant {
33    /// Returns true if this is a [`BlobTransactionSidecarVariant::Eip4844`].
34    pub const fn is_eip4844(&self) -> bool {
35        matches!(self, Self::Eip4844(_))
36    }
37
38    /// Returns true if this is a [`BlobTransactionSidecarVariant::Eip7594`].
39    pub const fn is_eip7594(&self) -> bool {
40        matches!(self, Self::Eip7594(_))
41    }
42
43    /// Returns the EIP-4844 sidecar if it is [`Self::Eip4844`].
44    pub const fn as_eip4844(&self) -> Option<&BlobTransactionSidecar> {
45        match self {
46            Self::Eip4844(sidecar) => Some(sidecar),
47            _ => None,
48        }
49    }
50
51    /// Returns the EIP-7594 sidecar if it is [`Self::Eip7594`].
52    pub const fn as_eip7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
53        match self {
54            Self::Eip7594(sidecar) => Some(sidecar),
55            _ => None,
56        }
57    }
58
59    /// Converts into EIP-4844 sidecar if it is [`Self::Eip4844`].
60    pub fn into_eip4844(self) -> Option<BlobTransactionSidecar> {
61        match self {
62            Self::Eip4844(sidecar) => Some(sidecar),
63            _ => None,
64        }
65    }
66
67    /// Converts the EIP-7594 sidecar if it is [`Self::Eip7594`].
68    pub fn into_eip7594(self) -> Option<BlobTransactionSidecarEip7594> {
69        match self {
70            Self::Eip7594(sidecar) => Some(sidecar),
71            _ => None,
72        }
73    }
74
75    /// Get a reference to the blobs
76    pub fn blobs(&self) -> &[Blob] {
77        match self {
78            Self::Eip4844(sidecar) => &sidecar.blobs,
79            Self::Eip7594(sidecar) => &sidecar.blobs,
80        }
81    }
82
83    /// Consume self and return the blobs
84    pub fn into_blobs(self) -> Vec<Blob> {
85        match self {
86            Self::Eip4844(sidecar) => sidecar.blobs,
87            Self::Eip7594(sidecar) => sidecar.blobs,
88        }
89    }
90
91    /// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecarVariant].
92    #[inline]
93    pub const fn size(&self) -> usize {
94        match self {
95            Self::Eip4844(sidecar) => sidecar.size(),
96            Self::Eip7594(sidecar) => sidecar.size(),
97        }
98    }
99
100    /// Attempts to convert this sidecar into the EIP-7594 format using default KZG settings.
101    ///
102    /// This method converts an EIP-4844 sidecar to EIP-7594 by computing cell KZG proofs from
103    /// the blob data. If the sidecar is already in EIP-7594 format, it returns itself unchanged.
104    ///
105    /// The conversion requires computing `CELLS_PER_EXT_BLOB` cell proofs for each blob using
106    /// the KZG trusted setup. The default KZG settings are loaded from the environment.
107    ///
108    /// # Returns
109    ///
110    /// - `Ok(Self)` - The sidecar in EIP-7594 format (either converted or unchanged)
111    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
112    ///
113    /// # Examples
114    ///
115    /// ```no_run
116    /// # use alloy_eips::eip7594::BlobTransactionSidecarVariant;
117    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
118    /// # fn example(sidecar: BlobTransactionSidecarVariant) -> Result<(), c_kzg::Error> {
119    /// // Convert an EIP-4844 sidecar to EIP-7594 format
120    /// let eip7594_sidecar = sidecar.try_convert_into_eip7594()?;
121    ///
122    /// // Verify it's now in EIP-7594 format
123    /// assert!(eip7594_sidecar.is_eip7594());
124    /// # Ok(())
125    /// # }
126    /// ```
127    #[cfg(feature = "kzg")]
128    pub fn try_convert_into_eip7594(self) -> Result<Self, c_kzg::Error> {
129        self.try_convert_into_eip7594_with_settings(
130            crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
131        )
132    }
133
134    /// Attempts to convert this sidecar into the EIP-7594 format using custom KZG settings.
135    ///
136    /// This method converts an EIP-4844 sidecar to EIP-7594 by computing cell KZG proofs from
137    /// the blob data using the provided KZG settings. If the sidecar is already in EIP-7594
138    /// format, it returns itself unchanged.
139    ///
140    /// The conversion requires computing `CELLS_PER_EXT_BLOB` cell proofs for each blob using
141    /// the provided KZG trusted setup parameters.
142    ///
143    /// Use this method when you need to specify custom KZG settings rather than using the
144    /// defaults. For most use cases, [`try_convert_into_eip7594`](Self::try_convert_into_eip7594)
145    /// is sufficient.
146    ///
147    /// # Arguments
148    ///
149    /// * `settings` - The KZG settings to use for computing cell proofs
150    ///
151    /// # Returns
152    ///
153    /// - `Ok(Self)` - The sidecar in EIP-7594 format (either converted or unchanged)
154    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
155    ///
156    /// # Examples
157    ///
158    /// ```no_run
159    /// # use alloy_eips::eip7594::BlobTransactionSidecarVariant;
160    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
161    /// # use alloy_eips::eip4844::env_settings::EnvKzgSettings;
162    /// # fn example(sidecar: BlobTransactionSidecarVariant) -> Result<(), c_kzg::Error> {
163    /// // Load custom KZG settings
164    /// let kzg_settings = EnvKzgSettings::Default.get();
165    ///
166    /// // Convert using custom settings
167    /// let eip7594_sidecar = sidecar.try_convert_into_eip7594_with_settings(kzg_settings)?;
168    ///
169    /// // Verify it's now in EIP-7594 format
170    /// assert!(eip7594_sidecar.is_eip7594());
171    /// # Ok(())
172    /// # }
173    /// ```
174    #[cfg(feature = "kzg")]
175    pub fn try_convert_into_eip7594_with_settings(
176        self,
177        settings: &c_kzg::KzgSettings,
178    ) -> Result<Self, c_kzg::Error> {
179        match self {
180            Self::Eip4844(legacy) => legacy.try_into_7594(settings).map(Self::Eip7594),
181            sidecar @ Self::Eip7594(_) => Ok(sidecar),
182        }
183    }
184
185    /// Consumes this sidecar and returns a [`BlobTransactionSidecarEip7594`] using default KZG
186    /// settings.
187    ///
188    /// This method converts an EIP-4844 sidecar to EIP-7594 by computing cell KZG proofs from
189    /// the blob data. If the sidecar is already in EIP-7594 format, it extracts and returns the
190    /// inner [`BlobTransactionSidecarEip7594`].
191    ///
192    /// Unlike [`try_convert_into_eip7594`](Self::try_convert_into_eip7594), this method returns
193    /// the concrete [`BlobTransactionSidecarEip7594`] type rather than the enum variant.
194    ///
195    /// The conversion requires computing `CELLS_PER_EXT_BLOB` cell proofs for each blob using
196    /// the KZG trusted setup. The default KZG settings are loaded from the environment.
197    ///
198    /// # Returns
199    ///
200    /// - `Ok(BlobTransactionSidecarEip7594)` - The sidecar in EIP-7594 format
201    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
202    ///
203    /// # Examples
204    ///
205    /// ```no_run
206    /// # use alloy_eips::eip7594::BlobTransactionSidecarVariant;
207    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
208    /// # fn example(sidecar: BlobTransactionSidecarVariant) -> Result<(), c_kzg::Error> {
209    /// // Convert and extract the EIP-7594 sidecar
210    /// let eip7594_sidecar = sidecar.try_into_eip7594()?;
211    ///
212    /// // Now we have the concrete BlobTransactionSidecarEip7594 type
213    /// assert_eq!(eip7594_sidecar.blobs.len(), eip7594_sidecar.commitments.len());
214    /// # Ok(())
215    /// # }
216    /// ```
217    #[cfg(feature = "kzg")]
218    pub fn try_into_eip7594(self) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
219        self.try_into_eip7594_with_settings(
220            crate::eip4844::env_settings::EnvKzgSettings::Default.get(),
221        )
222    }
223
224    /// Consumes this sidecar and returns a [`BlobTransactionSidecarEip7594`] using custom KZG
225    /// settings.
226    ///
227    /// This method converts an EIP-4844 sidecar to EIP-7594 by computing cell KZG proofs from
228    /// the blob data using the provided KZG settings. If the sidecar is already in EIP-7594
229    /// format, it extracts and returns the inner [`BlobTransactionSidecarEip7594`].
230    ///
231    /// Unlike [`try_convert_into_eip7594_with_settings`](Self::try_convert_into_eip7594_with_settings),
232    /// this method returns the concrete [`BlobTransactionSidecarEip7594`] type rather than the
233    /// enum variant.
234    ///
235    /// The conversion requires computing `CELLS_PER_EXT_BLOB` cell proofs for each blob using
236    /// the provided KZG trusted setup parameters.
237    ///
238    /// Use this method when you need to specify custom KZG settings rather than using the
239    /// defaults. For most use cases, [`try_into_eip7594`](Self::try_into_eip7594) is sufficient.
240    ///
241    /// # Arguments
242    ///
243    /// * `settings` - The KZG settings to use for computing cell proofs
244    ///
245    /// # Returns
246    ///
247    /// - `Ok(BlobTransactionSidecarEip7594)` - The sidecar in EIP-7594 format
248    /// - `Err(c_kzg::Error)` - If KZG proof computation fails
249    ///
250    /// # Examples
251    ///
252    /// ```no_run
253    /// # use alloy_eips::eip7594::BlobTransactionSidecarVariant;
254    /// # use alloy_eips::eip4844::BlobTransactionSidecar;
255    /// # use alloy_eips::eip4844::env_settings::EnvKzgSettings;
256    /// # fn example(sidecar: BlobTransactionSidecarVariant) -> Result<(), c_kzg::Error> {
257    /// // Load custom KZG settings
258    /// let kzg_settings = EnvKzgSettings::Default.get();
259    ///
260    /// // Convert and extract using custom settings
261    /// let eip7594_sidecar = sidecar.try_into_eip7594_with_settings(kzg_settings)?;
262    ///
263    /// // Now we have the concrete BlobTransactionSidecarEip7594 type
264    /// assert_eq!(eip7594_sidecar.blobs.len(), eip7594_sidecar.commitments.len());
265    /// # Ok(())
266    /// # }
267    /// ```
268    #[cfg(feature = "kzg")]
269    pub fn try_into_eip7594_with_settings(
270        self,
271        settings: &c_kzg::KzgSettings,
272    ) -> Result<BlobTransactionSidecarEip7594, c_kzg::Error> {
273        match self {
274            Self::Eip4844(legacy) => legacy.try_into_7594(settings),
275            Self::Eip7594(sidecar) => Ok(sidecar),
276        }
277    }
278
279    /// Verifies that the sidecar is valid. See relevant methods for each variant for more info.
280    #[cfg(feature = "kzg")]
281    pub fn validate(
282        &self,
283        blob_versioned_hashes: &[B256],
284        proof_settings: &c_kzg::KzgSettings,
285    ) -> Result<(), BlobTransactionValidationError> {
286        match self {
287            Self::Eip4844(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
288            Self::Eip7594(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
289        }
290    }
291
292    /// Returns the commitments of the sidecar.
293    pub fn commitments(&self) -> &[Bytes48] {
294        match self {
295            Self::Eip4844(sidecar) => &sidecar.commitments,
296            Self::Eip7594(sidecar) => &sidecar.commitments,
297        }
298    }
299
300    /// Returns an iterator over the versioned hashes of the commitments.
301    pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
302        VersionedHashIter::new(self.commitments())
303    }
304
305    /// Returns the index of the versioned hash in the commitments vector.
306    pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
307        match self {
308            Self::Eip4844(s) => s.versioned_hash_index(hash),
309            Self::Eip7594(s) => s.versioned_hash_index(hash),
310        }
311    }
312
313    /// Returns the blob corresponding to the versioned hash, if it exists.
314    pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
315        match self {
316            Self::Eip4844(s) => s.blob_by_versioned_hash(hash),
317            Self::Eip7594(s) => s.blob_by_versioned_hash(hash),
318        }
319    }
320
321    /// Outputs the RLP length of the [BlobTransactionSidecarVariant] fields, without a RLP header.
322    #[doc(hidden)]
323    pub fn rlp_encoded_fields_length(&self) -> usize {
324        match self {
325            Self::Eip4844(sidecar) => sidecar.rlp_encoded_fields_length(),
326            Self::Eip7594(sidecar) => sidecar.rlp_encoded_fields_length(),
327        }
328    }
329
330    /// Returns the [`Self::rlp_encode_fields`] RLP bytes.
331    #[inline]
332    #[doc(hidden)]
333    pub fn rlp_encoded_fields(&self) -> Vec<u8> {
334        let mut buf = Vec::with_capacity(self.rlp_encoded_fields_length());
335        self.rlp_encode_fields(&mut buf);
336        buf
337    }
338
339    /// Encodes the inner [BlobTransactionSidecarVariant] fields as RLP bytes, __without__ a RLP
340    /// header.
341    #[inline]
342    #[doc(hidden)]
343    pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
344        match self {
345            Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
346            Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
347        }
348    }
349
350    /// RLP decode the fields of a [BlobTransactionSidecarVariant] based on the wrapper version.
351    #[doc(hidden)]
352    pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
353        Self::decode_7594(buf)
354    }
355}
356
357impl Encodable for BlobTransactionSidecarVariant {
358    /// Encodes the [BlobTransactionSidecar] fields as RLP bytes, without a RLP header.
359    fn encode(&self, out: &mut dyn BufMut) {
360        match self {
361            Self::Eip4844(sidecar) => sidecar.encode(out),
362            Self::Eip7594(sidecar) => sidecar.encode(out),
363        }
364    }
365
366    fn length(&self) -> usize {
367        match self {
368            Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
369            Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
370        }
371    }
372}
373
374impl Decodable for BlobTransactionSidecarVariant {
375    /// Decodes the inner [BlobTransactionSidecar] fields from RLP bytes, without a RLP header.
376    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
377        let header = Header::decode(buf)?;
378        if !header.list {
379            return Err(alloy_rlp::Error::UnexpectedString);
380        }
381        if buf.len() < header.payload_length {
382            return Err(alloy_rlp::Error::InputTooShort);
383        }
384        let remaining = buf.len();
385        let this = Self::rlp_decode_fields(buf)?;
386        if buf.len() + header.payload_length != remaining {
387            return Err(alloy_rlp::Error::UnexpectedLength);
388        }
389
390        Ok(this)
391    }
392}
393
394impl Encodable7594 for BlobTransactionSidecarVariant {
395    fn encode_7594_len(&self) -> usize {
396        self.rlp_encoded_fields_length()
397    }
398
399    fn encode_7594(&self, out: &mut dyn BufMut) {
400        self.rlp_encode_fields(out);
401    }
402}
403
404impl Decodable7594 for BlobTransactionSidecarVariant {
405    fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
406        if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
407            Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
408        } else {
409            Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
410        }
411    }
412}
413
414#[cfg(feature = "kzg")]
415impl TryFrom<BlobTransactionSidecarVariant> for BlobTransactionSidecarEip7594 {
416    type Error = c_kzg::Error;
417
418    fn try_from(value: BlobTransactionSidecarVariant) -> Result<Self, Self::Error> {
419        value.try_into_eip7594()
420    }
421}
422
423#[cfg(feature = "serde")]
424impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
425    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
426    where
427        D: serde::Deserializer<'de>,
428    {
429        use core::fmt;
430
431        #[derive(serde::Deserialize, fmt::Debug)]
432        #[serde(field_identifier, rename_all = "camelCase")]
433        enum Field {
434            Blobs,
435            Commitments,
436            Proofs,
437            CellProofs,
438        }
439
440        struct VariantVisitor;
441
442        impl<'de> serde::de::Visitor<'de> for VariantVisitor {
443            type Value = BlobTransactionSidecarVariant;
444
445            fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
446                formatter
447                    .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
448            }
449
450            fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
451            where
452                M: serde::de::MapAccess<'de>,
453            {
454                let mut blobs = None;
455                let mut commitments = None;
456                let mut proofs = None;
457                let mut cell_proofs = None;
458
459                while let Some(key) = map.next_key()? {
460                    match key {
461                        Field::Blobs => {
462                            blobs = Some(crate::eip4844::deserialize_blobs_map(&mut map)?);
463                        }
464                        Field::Commitments => commitments = Some(map.next_value()?),
465                        Field::Proofs => proofs = Some(map.next_value()?),
466                        Field::CellProofs => cell_proofs = Some(map.next_value()?),
467                    }
468                }
469
470                let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
471                let commitments =
472                    commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
473
474                match (cell_proofs, proofs) {
475                    (Some(cp), None) => {
476                        Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
477                            blobs,
478                            commitments,
479                            cell_proofs: cp,
480                        }))
481                    }
482                    (None, Some(pf)) => {
483                        Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
484                            blobs,
485                            commitments,
486                            proofs: pf,
487                        }))
488                    }
489                    (None, None) => {
490                        Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
491                    }
492                    (Some(_), Some(_)) => Err(serde::de::Error::custom(
493                        "Both 'cellProofs' and 'proofs' cannot be present",
494                    )),
495                }
496            }
497        }
498
499        const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
500        deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
501    }
502}
503
504/// This represents a set of blobs, and its corresponding commitments and cell proofs.
505///
506/// This type encodes and decodes the fields without an rlp header.
507#[derive(Clone, Default, PartialEq, Eq, Hash)]
508#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
509#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
510#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
511pub struct BlobTransactionSidecarEip7594 {
512    /// The blob data.
513    #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::eip4844::deserialize_blobs"))]
514    pub blobs: Vec<Blob>,
515    /// The blob commitments.
516    pub commitments: Vec<Bytes48>,
517    /// List of cell proofs for all blobs in the sidecar, including the proofs for the extension
518    /// indices, for a total of `CELLS_PER_EXT_BLOB` proofs per blob (`CELLS_PER_EXT_BLOB` is the
519    /// number of cells for an extended blob, defined in
520    /// [the consensus specs](https://github.com/ethereum/consensus-specs/tree/9d377fd53d029536e57cfda1a4d2c700c59f86bf/specs/fulu/polynomial-commitments-sampling.md#cells))
521    pub cell_proofs: Vec<Bytes48>,
522}
523
524impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
525    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
526        f.debug_struct("BlobTransactionSidecarEip7594")
527            .field("blobs", &self.blobs.len())
528            .field("commitments", &self.commitments)
529            .field("cell_proofs", &self.cell_proofs)
530            .finish()
531    }
532}
533
534impl BlobTransactionSidecarEip7594 {
535    /// Constructs a new [BlobTransactionSidecarEip7594] from a set of blobs, commitments, and
536    /// cell proofs.
537    pub const fn new(
538        blobs: Vec<Blob>,
539        commitments: Vec<Bytes48>,
540        cell_proofs: Vec<Bytes48>,
541    ) -> Self {
542        Self { blobs, commitments, cell_proofs }
543    }
544
545    /// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecarEip7594].
546    #[inline]
547    pub const fn size(&self) -> usize {
548        self.blobs.capacity() * BYTES_PER_BLOB
549            + self.commitments.capacity() * BYTES_PER_COMMITMENT
550            + self.cell_proofs.capacity() * BYTES_PER_PROOF
551    }
552
553    /// Verifies that the versioned hashes are valid for this sidecar's blob data, commitments, and
554    /// proofs.
555    ///
556    /// Takes as input the [KzgSettings](c_kzg::KzgSettings), which should contain the parameters
557    /// derived from the KZG trusted setup.
558    ///
559    /// This ensures that the blob transaction payload has the expected number of blob data
560    /// elements, commitments, and proofs. The cells are constructed from each blob and verified
561    /// against the commitments and proofs.
562    ///
563    /// Returns [BlobTransactionValidationError::InvalidProof] if any blob KZG proof in the response
564    /// fails to verify, or if the versioned hashes in the transaction do not match the actual
565    /// commitment versioned hashes.
566    #[cfg(feature = "kzg")]
567    pub fn validate(
568        &self,
569        blob_versioned_hashes: &[B256],
570        proof_settings: &c_kzg::KzgSettings,
571    ) -> Result<(), BlobTransactionValidationError> {
572        // Ensure the versioned hashes and commitments have the same length.
573        if blob_versioned_hashes.len() != self.commitments.len() {
574            return Err(c_kzg::Error::MismatchLength(format!(
575                "There are {} versioned commitment hashes and {} commitments",
576                blob_versioned_hashes.len(),
577                self.commitments.len()
578            ))
579            .into());
580        }
581
582        let blobs_len = self.blobs.len();
583        let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
584        if self.cell_proofs.len() != expected_cell_proofs_len {
585            return Err(c_kzg::Error::MismatchLength(format!(
586                "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
587                self.cell_proofs.len(),
588                blobs_len,
589                expected_cell_proofs_len
590            ))
591            .into());
592        }
593
594        // calculate versioned hashes by zipping & iterating
595        for (versioned_hash, commitment) in
596            blob_versioned_hashes.iter().zip(self.commitments.iter())
597        {
598            // calculate & verify versioned hash
599            let calculated_versioned_hash =
600                crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
601            if *versioned_hash != calculated_versioned_hash {
602                return Err(BlobTransactionValidationError::WrongVersionedHash {
603                    have: *versioned_hash,
604                    expected: calculated_versioned_hash,
605                });
606            }
607        }
608
609        // Repeat cell ranges for each blob.
610        let cell_indices =
611            Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
612
613        // Repeat commitments for each cell.
614        let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
615        for commitment in &self.commitments {
616            commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
617        }
618
619        // SAFETY: ALL types have the same size
620        let res = unsafe {
621            let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
622            for blob in &self.blobs {
623                let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
624                cells.extend(proof_settings.compute_cells(blob)?.into_iter());
625            }
626
627            proof_settings.verify_cell_kzg_proof_batch(
628                // commitments
629                core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
630                // cell indices
631                &cell_indices,
632                // cells
633                &cells,
634                // proofs
635                core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
636            )?
637        };
638
639        res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
640    }
641
642    /// Returns an iterator over the versioned hashes of the commitments.
643    pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
644        VersionedHashIter::new(&self.commitments)
645    }
646
647    /// Returns the index of the versioned hash in the commitments vector.
648    pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
649        self.commitments.iter().position(|commitment| {
650            crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
651        })
652    }
653
654    /// Returns the blob corresponding to the versioned hash, if it exists.
655    pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
656        self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
657    }
658
659    /// Matches versioned hashes and returns an iterator of (index, [`BlobAndProofV2`]) pairs
660    /// where index is the position in `versioned_hashes` that matched the versioned hash in the
661    /// sidecar.
662    ///
663    /// This is used for the `engine_getBlobsV2` RPC endpoint of the engine API
664    pub fn match_versioned_hashes<'a>(
665        &'a self,
666        versioned_hashes: &'a [B256],
667    ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
668        self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
669            versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
670                if blob_versioned_hash == *target_hash {
671                    let maybe_blob = self.blobs.get(i);
672                    let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
673                    let maybe_proofs = Some(&self.cell_proofs[proof_range])
674                        .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
675                    if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
676                        return Some((
677                            j,
678                            BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
679                        ));
680                    }
681                }
682                None
683            })
684        })
685    }
686
687    /// Outputs the RLP length of [BlobTransactionSidecarEip7594] fields without a RLP header.
688    #[doc(hidden)]
689    pub fn rlp_encoded_fields_length(&self) -> usize {
690        // wrapper version + blobs + commitments + cell proofs
691        1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
692    }
693
694    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, __without__ a
695    /// RLP header.
696    ///
697    /// This encodes the fields in the following order:
698    /// - `wrapper_version`
699    /// - `blobs`
700    /// - `commitments`
701    /// - `cell_proofs`
702    #[inline]
703    #[doc(hidden)]
704    pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
705        // Put version byte.
706        out.put_u8(EIP_7594_WRAPPER_VERSION);
707        // Encode the blobs, commitments, and cell proofs
708        self.blobs.encode(out);
709        self.commitments.encode(out);
710        self.cell_proofs.encode(out);
711    }
712
713    /// Creates an RLP header for the [BlobTransactionSidecarEip7594].
714    fn rlp_header(&self) -> Header {
715        Header { list: true, payload_length: self.rlp_encoded_fields_length() }
716    }
717
718    /// Calculates the length of the [BlobTransactionSidecarEip7594] when encoded as
719    /// RLP.
720    pub fn rlp_encoded_length(&self) -> usize {
721        self.rlp_header().length() + self.rlp_encoded_fields_length()
722    }
723
724    /// Encodes the [BlobTransactionSidecarEip7594] as RLP bytes.
725    pub fn rlp_encode(&self, out: &mut dyn BufMut) {
726        self.rlp_header().encode(out);
727        self.rlp_encode_fields(out);
728    }
729
730    /// RLP decode the fields of a [BlobTransactionSidecarEip7594].
731    #[doc(hidden)]
732    pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
733        Ok(Self {
734            blobs: Decodable::decode(buf)?,
735            commitments: Decodable::decode(buf)?,
736            cell_proofs: Decodable::decode(buf)?,
737        })
738    }
739
740    /// Decodes the [BlobTransactionSidecarEip7594] from RLP bytes.
741    pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
742        let header = Header::decode(buf)?;
743        if !header.list {
744            return Err(alloy_rlp::Error::UnexpectedString);
745        }
746        if buf.len() < header.payload_length {
747            return Err(alloy_rlp::Error::InputTooShort);
748        }
749        let remaining = buf.len();
750
751        let this = Self::decode_7594(buf)?;
752        if buf.len() + header.payload_length != remaining {
753            return Err(alloy_rlp::Error::UnexpectedLength);
754        }
755
756        Ok(this)
757    }
758}
759
760impl Encodable for BlobTransactionSidecarEip7594 {
761    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, without a RLP header.
762    fn encode(&self, out: &mut dyn BufMut) {
763        self.rlp_encode(out);
764    }
765
766    fn length(&self) -> usize {
767        self.rlp_encoded_length()
768    }
769}
770
771impl Decodable for BlobTransactionSidecarEip7594 {
772    /// Decodes the inner [BlobTransactionSidecarEip7594] fields from RLP bytes, without a RLP
773    /// header.
774    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
775        Self::rlp_decode(buf)
776    }
777}
778
779impl Encodable7594 for BlobTransactionSidecarEip7594 {
780    fn encode_7594_len(&self) -> usize {
781        self.rlp_encoded_fields_length()
782    }
783
784    fn encode_7594(&self, out: &mut dyn BufMut) {
785        self.rlp_encode_fields(out);
786    }
787}
788
789impl Decodable7594 for BlobTransactionSidecarEip7594 {
790    fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
791        let wrapper_version: u8 = Decodable::decode(buf)?;
792        if wrapper_version != EIP_7594_WRAPPER_VERSION {
793            return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
794        }
795        Self::rlp_decode_fields(buf)
796    }
797}
798
799/// Bincode-compatible [`BlobTransactionSidecarVariant`] serde implementation.
800#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
801pub mod serde_bincode_compat {
802    use crate::eip4844::{Blob, Bytes48};
803    use alloc::{borrow::Cow, vec::Vec};
804    use serde::{Deserialize, Deserializer, Serialize, Serializer};
805    use serde_with::{DeserializeAs, SerializeAs};
806
807    /// Bincode-compatible [`super::BlobTransactionSidecarVariant`] serde implementation.
808    ///
809    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
810    /// ```rust
811    /// use alloy_eips::eip7594::{serde_bincode_compat, BlobTransactionSidecarVariant};
812    /// use serde::{Deserialize, Serialize};
813    /// use serde_with::serde_as;
814    ///
815    /// #[serde_as]
816    /// #[derive(Serialize, Deserialize)]
817    /// struct Data {
818    ///     #[serde_as(as = "serde_bincode_compat::BlobTransactionSidecarVariant")]
819    ///     sidecar: BlobTransactionSidecarVariant,
820    /// }
821    /// ```
822    #[derive(Debug, Serialize, Deserialize)]
823    pub struct BlobTransactionSidecarVariant<'a> {
824        /// The blob data (common to both variants).
825        pub blobs: Cow<'a, Vec<Blob>>,
826        /// The blob commitments (common to both variants).
827        pub commitments: Cow<'a, Vec<Bytes48>>,
828        /// The blob proofs (EIP-4844 only).
829        pub proofs: Option<Cow<'a, Vec<Bytes48>>>,
830        /// The cell proofs (EIP-7594 only).
831        pub cell_proofs: Option<Cow<'a, Vec<Bytes48>>>,
832    }
833
834    impl<'a> From<&'a super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'a> {
835        fn from(value: &'a super::BlobTransactionSidecarVariant) -> Self {
836            match value {
837                super::BlobTransactionSidecarVariant::Eip4844(sidecar) => Self {
838                    blobs: Cow::Borrowed(&sidecar.blobs),
839                    commitments: Cow::Borrowed(&sidecar.commitments),
840                    proofs: Some(Cow::Borrowed(&sidecar.proofs)),
841                    cell_proofs: None,
842                },
843                super::BlobTransactionSidecarVariant::Eip7594(sidecar) => Self {
844                    blobs: Cow::Borrowed(&sidecar.blobs),
845                    commitments: Cow::Borrowed(&sidecar.commitments),
846                    proofs: None,
847                    cell_proofs: Some(Cow::Borrowed(&sidecar.cell_proofs)),
848                },
849            }
850        }
851    }
852
853    impl<'a> BlobTransactionSidecarVariant<'a> {
854        fn try_into_inner(self) -> Result<super::BlobTransactionSidecarVariant, &'static str> {
855            match (self.proofs, self.cell_proofs) {
856                (Some(proofs), None) => Ok(super::BlobTransactionSidecarVariant::Eip4844(
857                    crate::eip4844::BlobTransactionSidecar {
858                        blobs: self.blobs.into_owned(),
859                        commitments: self.commitments.into_owned(),
860                        proofs: proofs.into_owned(),
861                    },
862                )),
863                (None, Some(cell_proofs)) => Ok(super::BlobTransactionSidecarVariant::Eip7594(
864                    super::BlobTransactionSidecarEip7594 {
865                        blobs: self.blobs.into_owned(),
866                        commitments: self.commitments.into_owned(),
867                        cell_proofs: cell_proofs.into_owned(),
868                    },
869                )),
870                (None, None) => Err("Missing both 'proofs' and 'cell_proofs'"),
871                (Some(_), Some(_)) => Err("Both 'proofs' and 'cell_proofs' cannot be present"),
872            }
873        }
874    }
875
876    impl<'a> From<BlobTransactionSidecarVariant<'a>> for super::BlobTransactionSidecarVariant {
877        fn from(value: BlobTransactionSidecarVariant<'a>) -> Self {
878            value.try_into_inner().expect("Invalid BlobTransactionSidecarVariant")
879        }
880    }
881
882    impl SerializeAs<super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'_> {
883        fn serialize_as<S>(
884            source: &super::BlobTransactionSidecarVariant,
885            serializer: S,
886        ) -> Result<S::Ok, S::Error>
887        where
888            S: Serializer,
889        {
890            BlobTransactionSidecarVariant::from(source).serialize(serializer)
891        }
892    }
893
894    impl<'de> DeserializeAs<'de, super::BlobTransactionSidecarVariant>
895        for BlobTransactionSidecarVariant<'de>
896    {
897        fn deserialize_as<D>(
898            deserializer: D,
899        ) -> Result<super::BlobTransactionSidecarVariant, D::Error>
900        where
901            D: Deserializer<'de>,
902        {
903            let value = BlobTransactionSidecarVariant::deserialize(deserializer)?;
904            value.try_into_inner().map_err(serde::de::Error::custom)
905        }
906    }
907}
908
909#[cfg(test)]
910mod tests {
911    use super::*;
912
913    #[test]
914    fn sidecar_variant_rlp_roundtrip() {
915        let mut encoded = Vec::new();
916
917        // 4844
918        let empty_sidecar_4844 =
919            BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
920        empty_sidecar_4844.encode(&mut encoded);
921        assert_eq!(
922            empty_sidecar_4844,
923            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
924        );
925
926        let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
927            vec![Blob::default()],
928            vec![Bytes48::ZERO],
929            vec![Bytes48::ZERO],
930        ));
931        encoded.clear();
932        sidecar_4844.encode(&mut encoded);
933        assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
934
935        // 7594
936        let empty_sidecar_7594 =
937            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
938        encoded.clear();
939        empty_sidecar_7594.encode(&mut encoded);
940        assert_eq!(
941            empty_sidecar_7594,
942            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
943        );
944
945        let sidecar_7594 =
946            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
947                vec![Blob::default()],
948                vec![Bytes48::ZERO],
949                core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
950            ));
951        encoded.clear();
952        sidecar_7594.encode(&mut encoded);
953        assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
954    }
955
956    #[test]
957    #[cfg(feature = "serde")]
958    fn sidecar_variant_json_deserialize_sanity() {
959        let mut eip4844 = BlobTransactionSidecar::default();
960        eip4844.blobs.push(Blob::repeat_byte(0x2));
961
962        let json = serde_json::to_string(&eip4844).unwrap();
963        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
964        assert!(variant.is_eip4844());
965        let jsonvariant = serde_json::to_string(&variant).unwrap();
966        assert_eq!(json, jsonvariant);
967
968        let mut eip7594 = BlobTransactionSidecarEip7594::default();
969        eip7594.blobs.push(Blob::repeat_byte(0x4));
970        let json = serde_json::to_string(&eip7594).unwrap();
971        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
972        assert!(variant.is_eip7594());
973        let jsonvariant = serde_json::to_string(&variant).unwrap();
974        assert_eq!(json, jsonvariant);
975    }
976
977    #[test]
978    fn rlp_7594_roundtrip() {
979        let mut encoded = Vec::new();
980
981        let sidecar_4844 = BlobTransactionSidecar::default();
982        sidecar_4844.encode_7594(&mut encoded);
983        assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
984
985        let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
986        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
987        encoded.clear();
988        sidecar_variant_4844.encode_7594(&mut encoded);
989        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
990
991        let sidecar_7594 = BlobTransactionSidecarEip7594::default();
992        encoded.clear();
993        sidecar_7594.encode_7594(&mut encoded);
994        assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
995
996        let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
997        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
998        encoded.clear();
999        sidecar_variant_7594.encode_7594(&mut encoded);
1000        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1001    }
1002}