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    /// Tries to create a new [`BlobTransactionSidecarEip7594`] from the hex encoded blob str.
554    ///
555    /// See also [`Blob::from_hex`](c_kzg::Blob::from_hex)
556    #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
557    pub fn try_from_blobs_hex<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
558    where
559        I: IntoIterator<Item = B>,
560        B: AsRef<str>,
561    {
562        blobs
563            .into_iter()
564            .map(crate::eip4844::utils::hex_to_blob)
565            .collect::<Result<Vec<_>, _>>()
566            .and_then(Self::try_from_blobs)
567    }
568
569    /// Tries to create a new [`BlobTransactionSidecarEip7594`] from the given blob
570    /// bytes.
571    ///
572    /// See also [`Blob::from_bytes`](c_kzg::Blob::from_bytes)
573    #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
574    pub fn try_from_blobs_bytes<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
575    where
576        I: IntoIterator<Item = B>,
577        B: AsRef<[u8]>,
578    {
579        blobs
580            .into_iter()
581            .map(crate::eip4844::utils::bytes_to_blob)
582            .collect::<Result<Vec<_>, _>>()
583            .and_then(Self::try_from_blobs)
584    }
585
586    /// Tries to create a new [`BlobTransactionSidecarEip7594`] from the given
587    /// blobs and KZG settings.
588    #[cfg(feature = "kzg")]
589    pub fn try_from_blobs_with_settings(
590        blobs: Vec<Blob>,
591        settings: &c_kzg::KzgSettings,
592    ) -> Result<Self, c_kzg::Error> {
593        let mut commitments = Vec::with_capacity(blobs.len());
594        let mut proofs = Vec::with_capacity(blobs.len());
595        for blob in &blobs {
596            // SAFETY: same size
597            let blob = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
598            let commitment = settings.blob_to_kzg_commitment(blob)?;
599            let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob)?;
600
601            // SAFETY: same size
602            unsafe {
603                commitments
604                    .push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(commitment.to_bytes()));
605                for kzg_proof in kzg_proofs.iter() {
606                    proofs.push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(
607                        kzg_proof.to_bytes(),
608                    ));
609                }
610            }
611        }
612
613        Ok(Self::new(blobs, commitments, proofs))
614    }
615
616    /// Tries to create a new [`BlobTransactionSidecarEip7594`] from the given
617    /// blobs.
618    ///
619    /// This uses the global/default KZG settings, see also
620    /// [`EnvKzgSettings::Default`](crate::eip4844::env_settings::EnvKzgSettings).
621    #[cfg(feature = "kzg")]
622    pub fn try_from_blobs(blobs: Vec<Blob>) -> Result<Self, c_kzg::Error> {
623        use crate::eip4844::env_settings::EnvKzgSettings;
624
625        Self::try_from_blobs_with_settings(blobs, EnvKzgSettings::Default.get())
626    }
627
628    /// Verifies that the versioned hashes are valid for this sidecar's blob data, commitments, and
629    /// proofs.
630    ///
631    /// Takes as input the [KzgSettings](c_kzg::KzgSettings), which should contain the parameters
632    /// derived from the KZG trusted setup.
633    ///
634    /// This ensures that the blob transaction payload has the expected number of blob data
635    /// elements, commitments, and proofs. The cells are constructed from each blob and verified
636    /// against the commitments and proofs.
637    ///
638    /// Returns [BlobTransactionValidationError::InvalidProof] if any blob KZG proof in the response
639    /// fails to verify, or if the versioned hashes in the transaction do not match the actual
640    /// commitment versioned hashes.
641    #[cfg(feature = "kzg")]
642    pub fn validate(
643        &self,
644        blob_versioned_hashes: &[B256],
645        proof_settings: &c_kzg::KzgSettings,
646    ) -> Result<(), BlobTransactionValidationError> {
647        // Ensure the versioned hashes and commitments have the same length.
648        if blob_versioned_hashes.len() != self.commitments.len() {
649            return Err(c_kzg::Error::MismatchLength(format!(
650                "There are {} versioned commitment hashes and {} commitments",
651                blob_versioned_hashes.len(),
652                self.commitments.len()
653            ))
654            .into());
655        }
656
657        let blobs_len = self.blobs.len();
658        let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
659        if self.cell_proofs.len() != expected_cell_proofs_len {
660            return Err(c_kzg::Error::MismatchLength(format!(
661                "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
662                self.cell_proofs.len(),
663                blobs_len,
664                expected_cell_proofs_len
665            ))
666            .into());
667        }
668
669        // calculate versioned hashes by zipping & iterating
670        for (versioned_hash, commitment) in
671            blob_versioned_hashes.iter().zip(self.commitments.iter())
672        {
673            // calculate & verify versioned hash
674            let calculated_versioned_hash =
675                crate::eip4844::kzg_to_versioned_hash(commitment.as_slice());
676            if *versioned_hash != calculated_versioned_hash {
677                return Err(BlobTransactionValidationError::WrongVersionedHash {
678                    have: *versioned_hash,
679                    expected: calculated_versioned_hash,
680                });
681            }
682        }
683
684        // Repeat cell ranges for each blob.
685        let cell_indices =
686            Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
687
688        // Repeat commitments for each cell.
689        let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
690        for commitment in &self.commitments {
691            commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
692        }
693
694        // SAFETY: ALL types have the same size
695        let res = unsafe {
696            let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
697            for blob in &self.blobs {
698                let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
699                cells.extend(proof_settings.compute_cells(blob)?.into_iter());
700            }
701
702            proof_settings.verify_cell_kzg_proof_batch(
703                // commitments
704                core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
705                // cell indices
706                &cell_indices,
707                // cells
708                &cells,
709                // proofs
710                core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
711            )?
712        };
713
714        res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
715    }
716
717    /// Returns an iterator over the versioned hashes of the commitments.
718    pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
719        VersionedHashIter::new(&self.commitments)
720    }
721
722    /// Returns the index of the versioned hash in the commitments vector.
723    pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
724        self.commitments.iter().position(|commitment| {
725            crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
726        })
727    }
728
729    /// Returns the blob corresponding to the versioned hash, if it exists.
730    pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
731        self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
732    }
733
734    /// Matches versioned hashes and returns an iterator of (index, [`BlobAndProofV2`]) pairs
735    /// where index is the position in `versioned_hashes` that matched the versioned hash in the
736    /// sidecar.
737    ///
738    /// This is used for the `engine_getBlobsV2` RPC endpoint of the engine API
739    pub fn match_versioned_hashes<'a>(
740        &'a self,
741        versioned_hashes: &'a [B256],
742    ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
743        self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
744            versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
745                if blob_versioned_hash == *target_hash {
746                    let maybe_blob = self.blobs.get(i);
747                    let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
748                    let maybe_proofs = Some(&self.cell_proofs[proof_range])
749                        .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
750                    if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
751                        return Some((
752                            j,
753                            BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
754                        ));
755                    }
756                }
757                None
758            })
759        })
760    }
761
762    /// Outputs the RLP length of [BlobTransactionSidecarEip7594] fields without a RLP header.
763    #[doc(hidden)]
764    pub fn rlp_encoded_fields_length(&self) -> usize {
765        // wrapper version + blobs + commitments + cell proofs
766        1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
767    }
768
769    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, __without__ a
770    /// RLP header.
771    ///
772    /// This encodes the fields in the following order:
773    /// - `wrapper_version`
774    /// - `blobs`
775    /// - `commitments`
776    /// - `cell_proofs`
777    #[inline]
778    #[doc(hidden)]
779    pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
780        // Put version byte.
781        out.put_u8(EIP_7594_WRAPPER_VERSION);
782        // Encode the blobs, commitments, and cell proofs
783        self.blobs.encode(out);
784        self.commitments.encode(out);
785        self.cell_proofs.encode(out);
786    }
787
788    /// Creates an RLP header for the [BlobTransactionSidecarEip7594].
789    fn rlp_header(&self) -> Header {
790        Header { list: true, payload_length: self.rlp_encoded_fields_length() }
791    }
792
793    /// Calculates the length of the [BlobTransactionSidecarEip7594] when encoded as
794    /// RLP.
795    pub fn rlp_encoded_length(&self) -> usize {
796        self.rlp_header().length() + self.rlp_encoded_fields_length()
797    }
798
799    /// Encodes the [BlobTransactionSidecarEip7594] as RLP bytes.
800    pub fn rlp_encode(&self, out: &mut dyn BufMut) {
801        self.rlp_header().encode(out);
802        self.rlp_encode_fields(out);
803    }
804
805    /// RLP decode the fields of a [BlobTransactionSidecarEip7594].
806    #[doc(hidden)]
807    pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
808        Ok(Self {
809            blobs: Decodable::decode(buf)?,
810            commitments: Decodable::decode(buf)?,
811            cell_proofs: Decodable::decode(buf)?,
812        })
813    }
814
815    /// Decodes the [BlobTransactionSidecarEip7594] from RLP bytes.
816    pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
817        let header = Header::decode(buf)?;
818        if !header.list {
819            return Err(alloy_rlp::Error::UnexpectedString);
820        }
821        if buf.len() < header.payload_length {
822            return Err(alloy_rlp::Error::InputTooShort);
823        }
824        let remaining = buf.len();
825
826        let this = Self::decode_7594(buf)?;
827        if buf.len() + header.payload_length != remaining {
828            return Err(alloy_rlp::Error::UnexpectedLength);
829        }
830
831        Ok(this)
832    }
833}
834
835impl Encodable for BlobTransactionSidecarEip7594 {
836    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, without a RLP header.
837    fn encode(&self, out: &mut dyn BufMut) {
838        self.rlp_encode(out);
839    }
840
841    fn length(&self) -> usize {
842        self.rlp_encoded_length()
843    }
844}
845
846impl Decodable for BlobTransactionSidecarEip7594 {
847    /// Decodes the inner [BlobTransactionSidecarEip7594] fields from RLP bytes, without a RLP
848    /// header.
849    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
850        Self::rlp_decode(buf)
851    }
852}
853
854impl Encodable7594 for BlobTransactionSidecarEip7594 {
855    fn encode_7594_len(&self) -> usize {
856        self.rlp_encoded_fields_length()
857    }
858
859    fn encode_7594(&self, out: &mut dyn BufMut) {
860        self.rlp_encode_fields(out);
861    }
862}
863
864impl Decodable7594 for BlobTransactionSidecarEip7594 {
865    fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
866        let wrapper_version: u8 = Decodable::decode(buf)?;
867        if wrapper_version != EIP_7594_WRAPPER_VERSION {
868            return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
869        }
870        Self::rlp_decode_fields(buf)
871    }
872}
873
874/// Bincode-compatible [`BlobTransactionSidecarVariant`] serde implementation.
875#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
876pub mod serde_bincode_compat {
877    use crate::eip4844::{Blob, Bytes48};
878    use alloc::{borrow::Cow, vec::Vec};
879    use serde::{Deserialize, Deserializer, Serialize, Serializer};
880    use serde_with::{DeserializeAs, SerializeAs};
881
882    /// Bincode-compatible [`super::BlobTransactionSidecarVariant`] serde implementation.
883    ///
884    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
885    /// ```rust
886    /// use alloy_eips::eip7594::{serde_bincode_compat, BlobTransactionSidecarVariant};
887    /// use serde::{Deserialize, Serialize};
888    /// use serde_with::serde_as;
889    ///
890    /// #[serde_as]
891    /// #[derive(Serialize, Deserialize)]
892    /// struct Data {
893    ///     #[serde_as(as = "serde_bincode_compat::BlobTransactionSidecarVariant")]
894    ///     sidecar: BlobTransactionSidecarVariant,
895    /// }
896    /// ```
897    #[derive(Debug, Serialize, Deserialize)]
898    pub struct BlobTransactionSidecarVariant<'a> {
899        /// The blob data (common to both variants).
900        pub blobs: Cow<'a, Vec<Blob>>,
901        /// The blob commitments (common to both variants).
902        pub commitments: Cow<'a, Vec<Bytes48>>,
903        /// The blob proofs (EIP-4844 only).
904        pub proofs: Option<Cow<'a, Vec<Bytes48>>>,
905        /// The cell proofs (EIP-7594 only).
906        pub cell_proofs: Option<Cow<'a, Vec<Bytes48>>>,
907    }
908
909    impl<'a> From<&'a super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'a> {
910        fn from(value: &'a super::BlobTransactionSidecarVariant) -> Self {
911            match value {
912                super::BlobTransactionSidecarVariant::Eip4844(sidecar) => Self {
913                    blobs: Cow::Borrowed(&sidecar.blobs),
914                    commitments: Cow::Borrowed(&sidecar.commitments),
915                    proofs: Some(Cow::Borrowed(&sidecar.proofs)),
916                    cell_proofs: None,
917                },
918                super::BlobTransactionSidecarVariant::Eip7594(sidecar) => Self {
919                    blobs: Cow::Borrowed(&sidecar.blobs),
920                    commitments: Cow::Borrowed(&sidecar.commitments),
921                    proofs: None,
922                    cell_proofs: Some(Cow::Borrowed(&sidecar.cell_proofs)),
923                },
924            }
925        }
926    }
927
928    impl<'a> BlobTransactionSidecarVariant<'a> {
929        fn try_into_inner(self) -> Result<super::BlobTransactionSidecarVariant, &'static str> {
930            match (self.proofs, self.cell_proofs) {
931                (Some(proofs), None) => Ok(super::BlobTransactionSidecarVariant::Eip4844(
932                    crate::eip4844::BlobTransactionSidecar {
933                        blobs: self.blobs.into_owned(),
934                        commitments: self.commitments.into_owned(),
935                        proofs: proofs.into_owned(),
936                    },
937                )),
938                (None, Some(cell_proofs)) => Ok(super::BlobTransactionSidecarVariant::Eip7594(
939                    super::BlobTransactionSidecarEip7594 {
940                        blobs: self.blobs.into_owned(),
941                        commitments: self.commitments.into_owned(),
942                        cell_proofs: cell_proofs.into_owned(),
943                    },
944                )),
945                (None, None) => Err("Missing both 'proofs' and 'cell_proofs'"),
946                (Some(_), Some(_)) => Err("Both 'proofs' and 'cell_proofs' cannot be present"),
947            }
948        }
949    }
950
951    impl<'a> From<BlobTransactionSidecarVariant<'a>> for super::BlobTransactionSidecarVariant {
952        fn from(value: BlobTransactionSidecarVariant<'a>) -> Self {
953            value.try_into_inner().expect("Invalid BlobTransactionSidecarVariant")
954        }
955    }
956
957    impl SerializeAs<super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'_> {
958        fn serialize_as<S>(
959            source: &super::BlobTransactionSidecarVariant,
960            serializer: S,
961        ) -> Result<S::Ok, S::Error>
962        where
963            S: Serializer,
964        {
965            BlobTransactionSidecarVariant::from(source).serialize(serializer)
966        }
967    }
968
969    impl<'de> DeserializeAs<'de, super::BlobTransactionSidecarVariant>
970        for BlobTransactionSidecarVariant<'de>
971    {
972        fn deserialize_as<D>(
973            deserializer: D,
974        ) -> Result<super::BlobTransactionSidecarVariant, D::Error>
975        where
976            D: Deserializer<'de>,
977        {
978            let value = BlobTransactionSidecarVariant::deserialize(deserializer)?;
979            value.try_into_inner().map_err(serde::de::Error::custom)
980        }
981    }
982}
983
984#[cfg(test)]
985mod tests {
986    use super::*;
987
988    #[test]
989    fn sidecar_variant_rlp_roundtrip() {
990        let mut encoded = Vec::new();
991
992        // 4844
993        let empty_sidecar_4844 =
994            BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
995        empty_sidecar_4844.encode(&mut encoded);
996        assert_eq!(
997            empty_sidecar_4844,
998            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
999        );
1000
1001        let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
1002            vec![Blob::default()],
1003            vec![Bytes48::ZERO],
1004            vec![Bytes48::ZERO],
1005        ));
1006        encoded.clear();
1007        sidecar_4844.encode(&mut encoded);
1008        assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1009
1010        // 7594
1011        let empty_sidecar_7594 =
1012            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
1013        encoded.clear();
1014        empty_sidecar_7594.encode(&mut encoded);
1015        assert_eq!(
1016            empty_sidecar_7594,
1017            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1018        );
1019
1020        let sidecar_7594 =
1021            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
1022                vec![Blob::default()],
1023                vec![Bytes48::ZERO],
1024                core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
1025            ));
1026        encoded.clear();
1027        sidecar_7594.encode(&mut encoded);
1028        assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1029    }
1030
1031    #[test]
1032    #[cfg(feature = "serde")]
1033    fn sidecar_variant_json_deserialize_sanity() {
1034        let mut eip4844 = BlobTransactionSidecar::default();
1035        eip4844.blobs.push(Blob::repeat_byte(0x2));
1036
1037        let json = serde_json::to_string(&eip4844).unwrap();
1038        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1039        assert!(variant.is_eip4844());
1040        let jsonvariant = serde_json::to_string(&variant).unwrap();
1041        assert_eq!(json, jsonvariant);
1042
1043        let mut eip7594 = BlobTransactionSidecarEip7594::default();
1044        eip7594.blobs.push(Blob::repeat_byte(0x4));
1045        let json = serde_json::to_string(&eip7594).unwrap();
1046        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1047        assert!(variant.is_eip7594());
1048        let jsonvariant = serde_json::to_string(&variant).unwrap();
1049        assert_eq!(json, jsonvariant);
1050    }
1051
1052    #[test]
1053    fn rlp_7594_roundtrip() {
1054        let mut encoded = Vec::new();
1055
1056        let sidecar_4844 = BlobTransactionSidecar::default();
1057        sidecar_4844.encode_7594(&mut encoded);
1058        assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1059
1060        let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
1061        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1062        encoded.clear();
1063        sidecar_variant_4844.encode_7594(&mut encoded);
1064        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1065
1066        let sidecar_7594 = BlobTransactionSidecarEip7594::default();
1067        encoded.clear();
1068        sidecar_7594.encode_7594(&mut encoded);
1069        assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1070
1071        let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
1072        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1073        encoded.clear();
1074        sidecar_variant_7594.encode_7594(&mut encoded);
1075        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1076    }
1077}