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