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::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        let mut converted = Vec::new();
563        for blob in blobs {
564            converted.push(crate::eip4844::utils::hex_to_blob(blob)?);
565        }
566        Self::try_from_blobs(converted)
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        let mut converted = Vec::new();
580        for blob in blobs {
581            converted.push(crate::eip4844::utils::bytes_to_blob(blob)?);
582        }
583        Self::try_from_blobs(converted)
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                let blob_cells = proof_settings.compute_cells(blob)?;
700                cells.extend_from_slice(blob_cells.as_ref());
701            }
702
703            proof_settings.verify_cell_kzg_proof_batch(
704                // commitments
705                core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
706                // cell indices
707                &cell_indices,
708                // cells
709                &cells,
710                // proofs
711                core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
712            )?
713        };
714
715        res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
716    }
717
718    /// Returns an iterator over the versioned hashes of the commitments.
719    pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
720        VersionedHashIter::new(&self.commitments)
721    }
722
723    /// Returns the index of the versioned hash in the commitments vector.
724    pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
725        self.commitments.iter().position(|commitment| {
726            crate::eip4844::kzg_to_versioned_hash(commitment.as_slice()) == *hash
727        })
728    }
729
730    /// Returns the blob corresponding to the versioned hash, if it exists.
731    pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
732        self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
733    }
734
735    /// Matches versioned hashes and returns an iterator of (index, [`BlobAndProofV2`]) pairs
736    /// where index is the position in `versioned_hashes` that matched the versioned hash in the
737    /// sidecar.
738    ///
739    /// This is used for the `engine_getBlobsV2` RPC endpoint of the engine API
740    pub fn match_versioned_hashes<'a>(
741        &'a self,
742        versioned_hashes: &'a [B256],
743    ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
744        self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
745            versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
746                if blob_versioned_hash == *target_hash {
747                    let maybe_blob = self.blobs.get(i);
748                    let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
749                    let maybe_proofs = Some(&self.cell_proofs[proof_range])
750                        .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
751                    if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
752                        return Some((
753                            j,
754                            BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
755                        ));
756                    }
757                }
758                None
759            })
760        })
761    }
762
763    /// Outputs the RLP length of [BlobTransactionSidecarEip7594] fields without a RLP header.
764    #[doc(hidden)]
765    pub fn rlp_encoded_fields_length(&self) -> usize {
766        // wrapper version + blobs + commitments + cell proofs
767        1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
768    }
769
770    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, __without__ a
771    /// RLP header.
772    ///
773    /// This encodes the fields in the following order:
774    /// - `wrapper_version`
775    /// - `blobs`
776    /// - `commitments`
777    /// - `cell_proofs`
778    #[inline]
779    #[doc(hidden)]
780    pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
781        // Put version byte.
782        out.put_u8(EIP_7594_WRAPPER_VERSION);
783        // Encode the blobs, commitments, and cell proofs
784        self.blobs.encode(out);
785        self.commitments.encode(out);
786        self.cell_proofs.encode(out);
787    }
788
789    /// Creates an RLP header for the [BlobTransactionSidecarEip7594].
790    fn rlp_header(&self) -> Header {
791        Header { list: true, payload_length: self.rlp_encoded_fields_length() }
792    }
793
794    /// Calculates the length of the [BlobTransactionSidecarEip7594] when encoded as
795    /// RLP.
796    pub fn rlp_encoded_length(&self) -> usize {
797        self.rlp_header().length() + self.rlp_encoded_fields_length()
798    }
799
800    /// Encodes the [BlobTransactionSidecarEip7594] as RLP bytes.
801    pub fn rlp_encode(&self, out: &mut dyn BufMut) {
802        self.rlp_header().encode(out);
803        self.rlp_encode_fields(out);
804    }
805
806    /// RLP decode the fields of a [BlobTransactionSidecarEip7594].
807    #[doc(hidden)]
808    pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
809        Ok(Self {
810            blobs: Decodable::decode(buf)?,
811            commitments: Decodable::decode(buf)?,
812            cell_proofs: Decodable::decode(buf)?,
813        })
814    }
815
816    /// Decodes the [BlobTransactionSidecarEip7594] from RLP bytes.
817    pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
818        let header = Header::decode(buf)?;
819        if !header.list {
820            return Err(alloy_rlp::Error::UnexpectedString);
821        }
822        if buf.len() < header.payload_length {
823            return Err(alloy_rlp::Error::InputTooShort);
824        }
825        let remaining = buf.len();
826
827        let this = Self::decode_7594(buf)?;
828        if buf.len() + header.payload_length != remaining {
829            return Err(alloy_rlp::Error::UnexpectedLength);
830        }
831
832        Ok(this)
833    }
834}
835
836impl Encodable for BlobTransactionSidecarEip7594 {
837    /// Encodes the inner [BlobTransactionSidecarEip7594] fields as RLP bytes, without a RLP header.
838    fn encode(&self, out: &mut dyn BufMut) {
839        self.rlp_encode(out);
840    }
841
842    fn length(&self) -> usize {
843        self.rlp_encoded_length()
844    }
845}
846
847impl Decodable for BlobTransactionSidecarEip7594 {
848    /// Decodes the inner [BlobTransactionSidecarEip7594] fields from RLP bytes, without a RLP
849    /// header.
850    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
851        Self::rlp_decode(buf)
852    }
853}
854
855impl Encodable7594 for BlobTransactionSidecarEip7594 {
856    fn encode_7594_len(&self) -> usize {
857        self.rlp_encoded_fields_length()
858    }
859
860    fn encode_7594(&self, out: &mut dyn BufMut) {
861        self.rlp_encode_fields(out);
862    }
863}
864
865impl Decodable7594 for BlobTransactionSidecarEip7594 {
866    fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
867        let wrapper_version: u8 = Decodable::decode(buf)?;
868        if wrapper_version != EIP_7594_WRAPPER_VERSION {
869            return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
870        }
871        Self::rlp_decode_fields(buf)
872    }
873}
874
875/// Bincode-compatible [`BlobTransactionSidecarVariant`] serde implementation.
876#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
877pub mod serde_bincode_compat {
878    use crate::eip4844::{Blob, Bytes48};
879    use alloc::{borrow::Cow, vec::Vec};
880    use serde::{Deserialize, Deserializer, Serialize, Serializer};
881    use serde_with::{DeserializeAs, SerializeAs};
882
883    /// Bincode-compatible [`super::BlobTransactionSidecarVariant`] serde implementation.
884    ///
885    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
886    /// ```rust
887    /// use alloy_eips::eip7594::{serde_bincode_compat, BlobTransactionSidecarVariant};
888    /// use serde::{Deserialize, Serialize};
889    /// use serde_with::serde_as;
890    ///
891    /// #[serde_as]
892    /// #[derive(Serialize, Deserialize)]
893    /// struct Data {
894    ///     #[serde_as(as = "serde_bincode_compat::BlobTransactionSidecarVariant")]
895    ///     sidecar: BlobTransactionSidecarVariant,
896    /// }
897    /// ```
898    #[derive(Debug, Serialize, Deserialize)]
899    pub struct BlobTransactionSidecarVariant<'a> {
900        /// The blob data (common to both variants).
901        pub blobs: Cow<'a, Vec<Blob>>,
902        /// The blob commitments (common to both variants).
903        pub commitments: Cow<'a, Vec<Bytes48>>,
904        /// The blob proofs (EIP-4844 only).
905        pub proofs: Option<Cow<'a, Vec<Bytes48>>>,
906        /// The cell proofs (EIP-7594 only).
907        pub cell_proofs: Option<Cow<'a, Vec<Bytes48>>>,
908    }
909
910    impl<'a> From<&'a super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'a> {
911        fn from(value: &'a super::BlobTransactionSidecarVariant) -> Self {
912            match value {
913                super::BlobTransactionSidecarVariant::Eip4844(sidecar) => Self {
914                    blobs: Cow::Borrowed(&sidecar.blobs),
915                    commitments: Cow::Borrowed(&sidecar.commitments),
916                    proofs: Some(Cow::Borrowed(&sidecar.proofs)),
917                    cell_proofs: None,
918                },
919                super::BlobTransactionSidecarVariant::Eip7594(sidecar) => Self {
920                    blobs: Cow::Borrowed(&sidecar.blobs),
921                    commitments: Cow::Borrowed(&sidecar.commitments),
922                    proofs: None,
923                    cell_proofs: Some(Cow::Borrowed(&sidecar.cell_proofs)),
924                },
925            }
926        }
927    }
928
929    impl<'a> BlobTransactionSidecarVariant<'a> {
930        fn try_into_inner(self) -> Result<super::BlobTransactionSidecarVariant, &'static str> {
931            match (self.proofs, self.cell_proofs) {
932                (Some(proofs), None) => Ok(super::BlobTransactionSidecarVariant::Eip4844(
933                    crate::eip4844::BlobTransactionSidecar {
934                        blobs: self.blobs.into_owned(),
935                        commitments: self.commitments.into_owned(),
936                        proofs: proofs.into_owned(),
937                    },
938                )),
939                (None, Some(cell_proofs)) => Ok(super::BlobTransactionSidecarVariant::Eip7594(
940                    super::BlobTransactionSidecarEip7594 {
941                        blobs: self.blobs.into_owned(),
942                        commitments: self.commitments.into_owned(),
943                        cell_proofs: cell_proofs.into_owned(),
944                    },
945                )),
946                (None, None) => Err("Missing both 'proofs' and 'cell_proofs'"),
947                (Some(_), Some(_)) => Err("Both 'proofs' and 'cell_proofs' cannot be present"),
948            }
949        }
950    }
951
952    impl<'a> From<BlobTransactionSidecarVariant<'a>> for super::BlobTransactionSidecarVariant {
953        fn from(value: BlobTransactionSidecarVariant<'a>) -> Self {
954            value.try_into_inner().expect("Invalid BlobTransactionSidecarVariant")
955        }
956    }
957
958    impl SerializeAs<super::BlobTransactionSidecarVariant> for BlobTransactionSidecarVariant<'_> {
959        fn serialize_as<S>(
960            source: &super::BlobTransactionSidecarVariant,
961            serializer: S,
962        ) -> Result<S::Ok, S::Error>
963        where
964            S: Serializer,
965        {
966            BlobTransactionSidecarVariant::from(source).serialize(serializer)
967        }
968    }
969
970    impl<'de> DeserializeAs<'de, super::BlobTransactionSidecarVariant>
971        for BlobTransactionSidecarVariant<'de>
972    {
973        fn deserialize_as<D>(
974            deserializer: D,
975        ) -> Result<super::BlobTransactionSidecarVariant, D::Error>
976        where
977            D: Deserializer<'de>,
978        {
979            let value = BlobTransactionSidecarVariant::deserialize(deserializer)?;
980            value.try_into_inner().map_err(serde::de::Error::custom)
981        }
982    }
983}
984
985#[cfg(test)]
986mod tests {
987    use super::*;
988    #[cfg(feature = "kzg")]
989    use crate::eip4844::{
990        builder::{SidecarBuilder, SimpleCoder},
991        env_settings::EnvKzgSettings,
992    };
993
994    #[test]
995    fn sidecar_variant_rlp_roundtrip() {
996        let mut encoded = Vec::new();
997
998        // 4844
999        let empty_sidecar_4844 =
1000            BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
1001        empty_sidecar_4844.encode(&mut encoded);
1002        assert_eq!(
1003            empty_sidecar_4844,
1004            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1005        );
1006
1007        let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
1008            vec![Blob::default()],
1009            vec![Bytes48::ZERO],
1010            vec![Bytes48::ZERO],
1011        ));
1012        encoded.clear();
1013        sidecar_4844.encode(&mut encoded);
1014        assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1015
1016        // 7594
1017        let empty_sidecar_7594 =
1018            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
1019        encoded.clear();
1020        empty_sidecar_7594.encode(&mut encoded);
1021        assert_eq!(
1022            empty_sidecar_7594,
1023            BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
1024        );
1025
1026        let sidecar_7594 =
1027            BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
1028                vec![Blob::default()],
1029                vec![Bytes48::ZERO],
1030                core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
1031            ));
1032        encoded.clear();
1033        sidecar_7594.encode(&mut encoded);
1034        assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
1035    }
1036
1037    #[test]
1038    #[cfg(feature = "serde")]
1039    fn sidecar_variant_json_deserialize_sanity() {
1040        let mut eip4844 = BlobTransactionSidecar::default();
1041        eip4844.blobs.push(Blob::repeat_byte(0x2));
1042
1043        let json = serde_json::to_string(&eip4844).unwrap();
1044        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1045        assert!(variant.is_eip4844());
1046        let jsonvariant = serde_json::to_string(&variant).unwrap();
1047        assert_eq!(json, jsonvariant);
1048
1049        let mut eip7594 = BlobTransactionSidecarEip7594::default();
1050        eip7594.blobs.push(Blob::repeat_byte(0x4));
1051        let json = serde_json::to_string(&eip7594).unwrap();
1052        let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
1053        assert!(variant.is_eip7594());
1054        let jsonvariant = serde_json::to_string(&variant).unwrap();
1055        assert_eq!(json, jsonvariant);
1056    }
1057
1058    #[test]
1059    fn rlp_7594_roundtrip() {
1060        let mut encoded = Vec::new();
1061
1062        let sidecar_4844 = BlobTransactionSidecar::default();
1063        sidecar_4844.encode_7594(&mut encoded);
1064        assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1065
1066        let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
1067        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1068        encoded.clear();
1069        sidecar_variant_4844.encode_7594(&mut encoded);
1070        assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1071
1072        let sidecar_7594 = BlobTransactionSidecarEip7594::default();
1073        encoded.clear();
1074        sidecar_7594.encode_7594(&mut encoded);
1075        assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1076
1077        let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
1078        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1079        encoded.clear();
1080        sidecar_variant_7594.encode_7594(&mut encoded);
1081        assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
1082    }
1083
1084    #[test]
1085    #[cfg(feature = "kzg")]
1086    fn validate_7594_sidecar() {
1087        let sidecar =
1088            SidecarBuilder::<SimpleCoder>::from_slice(b"Blobs are fun!").build_7594().unwrap();
1089        let versioned_hashes = sidecar.versioned_hashes().collect::<Vec<_>>();
1090
1091        sidecar.validate(&versioned_hashes, EnvKzgSettings::Default.get()).unwrap();
1092    }
1093}