1use crate::{
2 eip4844::{
3 kzg_to_versioned_hash, Blob, BlobAndProofV2, BlobTransactionSidecar, Bytes48,
4 BYTES_PER_BLOB, 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
12#[cfg(feature = "kzg")]
13use crate::eip4844::BlobTransactionValidationError;
14
15use super::{Decodable7594, Encodable7594};
16
17#[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 Eip4844(BlobTransactionSidecar),
28 Eip7594(BlobTransactionSidecarEip7594),
30}
31
32impl BlobTransactionSidecarVariant {
33 pub const fn is_eip4844(&self) -> bool {
35 matches!(self, Self::Eip4844(_))
36 }
37
38 pub const fn is_eip7594(&self) -> bool {
40 matches!(self, Self::Eip7594(_))
41 }
42
43 pub const fn as_eip4844(&self) -> Option<&BlobTransactionSidecar> {
45 match self {
46 Self::Eip4844(sidecar) => Some(sidecar),
47 _ => None,
48 }
49 }
50
51 pub const fn as_eip7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
53 match self {
54 Self::Eip7594(sidecar) => Some(sidecar),
55 _ => None,
56 }
57 }
58
59 pub fn into_eip4844(self) -> Option<BlobTransactionSidecar> {
61 match self {
62 Self::Eip4844(sidecar) => Some(sidecar),
63 _ => None,
64 }
65 }
66
67 pub fn into_eip7594(self) -> Option<BlobTransactionSidecarEip7594> {
69 match self {
70 Self::Eip7594(sidecar) => Some(sidecar),
71 _ => None,
72 }
73 }
74
75 #[inline]
77 pub fn size(&self) -> usize {
78 match self {
79 Self::Eip4844(sidecar) => sidecar.size(),
80 Self::Eip7594(sidecar) => sidecar.size(),
81 }
82 }
83
84 #[cfg(feature = "kzg")]
86 pub fn validate(
87 &self,
88 blob_versioned_hashes: &[B256],
89 proof_settings: &c_kzg::KzgSettings,
90 ) -> Result<(), BlobTransactionValidationError> {
91 match self {
92 Self::Eip4844(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
93 Self::Eip7594(sidecar) => sidecar.validate(blob_versioned_hashes, proof_settings),
94 }
95 }
96
97 pub fn versioned_hashes(&self) -> Vec<B256> {
99 match self {
100 Self::Eip4844(sidecar) => sidecar.versioned_hashes().collect(),
101 Self::Eip7594(sidecar) => sidecar.versioned_hashes().collect(),
102 }
103 }
104
105 #[doc(hidden)]
107 pub fn rlp_encoded_fields_length(&self) -> usize {
108 match self {
109 Self::Eip4844(sidecar) => sidecar.rlp_encoded_fields_length(),
110 Self::Eip7594(sidecar) => sidecar.rlp_encoded_fields_length(),
111 }
112 }
113
114 #[inline]
117 #[doc(hidden)]
118 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
119 match self {
120 Self::Eip4844(sidecar) => sidecar.rlp_encode_fields(out),
121 Self::Eip7594(sidecar) => sidecar.rlp_encode_fields(out),
122 }
123 }
124
125 #[doc(hidden)]
127 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
128 Self::decode_7594(buf)
129 }
130}
131
132impl Encodable for BlobTransactionSidecarVariant {
133 fn encode(&self, out: &mut dyn BufMut) {
135 match self {
136 Self::Eip4844(sidecar) => sidecar.encode(out),
137 Self::Eip7594(sidecar) => sidecar.encode(out),
138 }
139 }
140
141 fn length(&self) -> usize {
142 match self {
143 Self::Eip4844(sidecar) => sidecar.rlp_encoded_length(),
144 Self::Eip7594(sidecar) => sidecar.rlp_encoded_length(),
145 }
146 }
147}
148
149impl Decodable for BlobTransactionSidecarVariant {
150 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
152 let header = Header::decode(buf)?;
153 if !header.list {
154 return Err(alloy_rlp::Error::UnexpectedString);
155 }
156 if buf.len() < header.payload_length {
157 return Err(alloy_rlp::Error::InputTooShort);
158 }
159 let remaining = buf.len();
160 let this = Self::rlp_decode_fields(buf)?;
161 if buf.len() + header.payload_length != remaining {
162 return Err(alloy_rlp::Error::UnexpectedLength);
163 }
164
165 Ok(this)
166 }
167}
168
169impl Encodable7594 for BlobTransactionSidecarVariant {
170 fn encode_7594_len(&self) -> usize {
171 self.rlp_encoded_fields_length()
172 }
173
174 fn encode_7594(&self, out: &mut dyn BufMut) {
175 self.rlp_encode_fields(out);
176 }
177}
178
179impl Decodable7594 for BlobTransactionSidecarVariant {
180 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
181 if buf.first() == Some(&EIP_7594_WRAPPER_VERSION) {
182 Ok(Self::Eip7594(Decodable7594::decode_7594(buf)?))
183 } else {
184 Ok(Self::Eip4844(Decodable7594::decode_7594(buf)?))
185 }
186 }
187}
188
189#[cfg(feature = "serde")]
190impl<'de> serde::Deserialize<'de> for BlobTransactionSidecarVariant {
191 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
192 where
193 D: serde::Deserializer<'de>,
194 {
195 #[derive(serde::Deserialize)]
196 #[serde(field_identifier, rename_all = "camelCase")]
197 enum Field {
198 Blobs,
199 Commitments,
200 Proofs,
201 CellProofs,
202 }
203
204 struct VariantVisitor;
205
206 impl<'de> serde::de::Visitor<'de> for VariantVisitor {
207 type Value = BlobTransactionSidecarVariant;
208
209 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210 formatter
211 .write_str("a valid blob transaction sidecar (EIP-4844 or EIP-7594 variant)")
212 }
213
214 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
215 where
216 M: serde::de::MapAccess<'de>,
217 {
218 let mut blobs = None;
219 let mut commitments = None;
220 let mut proofs = None;
221 let mut cell_proofs = None;
222
223 while let Some(key) = map.next_key()? {
224 match key {
225 Field::Blobs => blobs = Some(map.next_value()?),
226 Field::Commitments => commitments = Some(map.next_value()?),
227 Field::Proofs => proofs = Some(map.next_value()?),
228 Field::CellProofs => cell_proofs = Some(map.next_value()?),
229 }
230 }
231
232 let blobs = blobs.ok_or_else(|| serde::de::Error::missing_field("blobs"))?;
233 let commitments =
234 commitments.ok_or_else(|| serde::de::Error::missing_field("commitments"))?;
235
236 match (cell_proofs, proofs) {
237 (Some(cp), None) => {
238 Ok(BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594 {
239 blobs,
240 commitments,
241 cell_proofs: cp,
242 }))
243 }
244 (None, Some(pf)) => {
245 Ok(BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar {
246 blobs,
247 commitments,
248 proofs: pf,
249 }))
250 }
251 (None, None) => {
252 Err(serde::de::Error::custom("Missing 'cellProofs' or 'proofs'"))
253 }
254 (Some(_), Some(_)) => Err(serde::de::Error::custom(
255 "Both 'cellProofs' and 'proofs' cannot be present",
256 )),
257 }
258 }
259 }
260
261 const FIELDS: &[&str] = &["blobs", "commitments", "proofs", "cellProofs"];
262 deserializer.deserialize_struct("BlobTransactionSidecarVariant", FIELDS, VariantVisitor)
263 }
264}
265
266#[derive(Clone, Default, PartialEq, Eq, Hash)]
270#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
271#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
272#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
273pub struct BlobTransactionSidecarEip7594 {
274 #[cfg_attr(
276 all(debug_assertions, feature = "serde"),
277 serde(deserialize_with = "crate::eip4844::deserialize_blobs")
278 )]
279 pub blobs: Vec<Blob>,
280 pub commitments: Vec<Bytes48>,
282 pub cell_proofs: Vec<Bytes48>,
287}
288
289impl core::fmt::Debug for BlobTransactionSidecarEip7594 {
290 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
291 f.debug_struct("BlobTransactionSidecarEip7594")
292 .field("blobs", &self.blobs.len())
293 .field("commitments", &self.commitments)
294 .field("cell_proofs", &self.cell_proofs)
295 .finish()
296 }
297}
298
299impl BlobTransactionSidecarEip7594 {
300 pub const fn new(
303 blobs: Vec<Blob>,
304 commitments: Vec<Bytes48>,
305 cell_proofs: Vec<Bytes48>,
306 ) -> Self {
307 Self { blobs, commitments, cell_proofs }
308 }
309
310 #[inline]
312 pub fn size(&self) -> usize {
313 self.blobs.len() * BYTES_PER_BLOB + self.commitments.len() * BYTES_PER_COMMITMENT + self.cell_proofs.len() * BYTES_PER_PROOF }
317
318 #[cfg(feature = "kzg")]
332 pub fn validate(
333 &self,
334 blob_versioned_hashes: &[B256],
335 proof_settings: &c_kzg::KzgSettings,
336 ) -> Result<(), BlobTransactionValidationError> {
337 if blob_versioned_hashes.len() != self.commitments.len() {
339 return Err(c_kzg::Error::MismatchLength(format!(
340 "There are {} versioned commitment hashes and {} commitments",
341 blob_versioned_hashes.len(),
342 self.commitments.len()
343 ))
344 .into());
345 }
346
347 let blobs_len = self.blobs.len();
348 let expected_cell_proofs_len = blobs_len * CELLS_PER_EXT_BLOB;
349 if self.cell_proofs.len() != expected_cell_proofs_len {
350 return Err(c_kzg::Error::MismatchLength(format!(
351 "There are {} cell proofs and {} blobs. Expected {} cell proofs.",
352 self.cell_proofs.len(),
353 blobs_len,
354 expected_cell_proofs_len
355 ))
356 .into());
357 }
358
359 for (versioned_hash, commitment) in
361 blob_versioned_hashes.iter().zip(self.commitments.iter())
362 {
363 let commitment = c_kzg::KzgCommitment::from(commitment.0);
364
365 let calculated_versioned_hash = kzg_to_versioned_hash(commitment.as_slice());
367 if *versioned_hash != calculated_versioned_hash {
368 return Err(BlobTransactionValidationError::WrongVersionedHash {
369 have: *versioned_hash,
370 expected: calculated_versioned_hash,
371 });
372 }
373 }
374
375 let cell_indices =
377 Vec::from_iter((0..blobs_len).flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64));
378
379 let mut commitments = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
381 for commitment in &self.commitments {
382 commitments.extend(core::iter::repeat_n(*commitment, CELLS_PER_EXT_BLOB));
383 }
384
385 let res = unsafe {
387 let mut cells = Vec::with_capacity(blobs_len * CELLS_PER_EXT_BLOB);
388 for blob in &self.blobs {
389 let blob = core::mem::transmute::<&Blob, &c_kzg::Blob>(blob);
390 cells.extend(proof_settings.compute_cells(blob)?.into_iter());
391 }
392
393 proof_settings.verify_cell_kzg_proof_batch(
394 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(&commitments),
396 &cell_indices,
398 &cells,
400 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.cell_proofs.as_slice()),
402 )?
403 };
404
405 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
406 }
407
408 pub fn versioned_hashes(&self) -> impl Iterator<Item = B256> + '_ {
410 self.commitments.iter().map(|c| kzg_to_versioned_hash(c.as_slice()))
411 }
412
413 pub fn match_versioned_hashes<'a>(
419 &'a self,
420 versioned_hashes: &'a [B256],
421 ) -> impl Iterator<Item = (usize, BlobAndProofV2)> + 'a {
422 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
423 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
424 if blob_versioned_hash == *target_hash {
425 let maybe_blob = self.blobs.get(i);
426 let proof_range = i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB;
427 let maybe_proofs = Some(&self.cell_proofs[proof_range])
428 .filter(|proofs| proofs.len() == CELLS_PER_EXT_BLOB);
429 if let Some((blob, proofs)) = maybe_blob.copied().zip(maybe_proofs) {
430 return Some((
431 j,
432 BlobAndProofV2 { blob: Box::new(blob), proofs: proofs.to_vec() },
433 ));
434 }
435 }
436 None
437 })
438 })
439 }
440
441 #[doc(hidden)]
443 pub fn rlp_encoded_fields_length(&self) -> usize {
444 1 + self.blobs.length() + self.commitments.length() + self.cell_proofs.length()
446 }
447
448 #[inline]
457 #[doc(hidden)]
458 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
459 out.put_u8(EIP_7594_WRAPPER_VERSION);
461 self.blobs.encode(out);
463 self.commitments.encode(out);
464 self.cell_proofs.encode(out);
465 }
466
467 fn rlp_header(&self) -> Header {
469 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
470 }
471
472 pub fn rlp_encoded_length(&self) -> usize {
475 self.rlp_header().length() + self.rlp_encoded_fields_length()
476 }
477
478 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
480 self.rlp_header().encode(out);
481 self.rlp_encode_fields(out);
482 }
483
484 #[doc(hidden)]
486 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
487 Ok(Self {
488 blobs: Decodable::decode(buf)?,
489 commitments: Decodable::decode(buf)?,
490 cell_proofs: Decodable::decode(buf)?,
491 })
492 }
493
494 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
496 let header = Header::decode(buf)?;
497 if !header.list {
498 return Err(alloy_rlp::Error::UnexpectedString);
499 }
500 if buf.len() < header.payload_length {
501 return Err(alloy_rlp::Error::InputTooShort);
502 }
503 let remaining = buf.len();
504
505 let this = Self::decode_7594(buf)?;
506 if buf.len() + header.payload_length != remaining {
507 return Err(alloy_rlp::Error::UnexpectedLength);
508 }
509
510 Ok(this)
511 }
512}
513
514impl Encodable for BlobTransactionSidecarEip7594 {
515 fn encode(&self, out: &mut dyn BufMut) {
517 self.rlp_encode(out);
518 }
519
520 fn length(&self) -> usize {
521 self.rlp_encoded_length()
522 }
523}
524
525impl Decodable for BlobTransactionSidecarEip7594 {
526 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
529 Self::rlp_decode(buf)
530 }
531}
532
533impl Encodable7594 for BlobTransactionSidecarEip7594 {
534 fn encode_7594_len(&self) -> usize {
535 self.rlp_encoded_fields_length()
536 }
537
538 fn encode_7594(&self, out: &mut dyn BufMut) {
539 self.rlp_encode_fields(out);
540 }
541}
542
543impl Decodable7594 for BlobTransactionSidecarEip7594 {
544 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
545 let wrapper_version: u8 = Decodable::decode(buf)?;
546 if wrapper_version != EIP_7594_WRAPPER_VERSION {
547 return Err(alloy_rlp::Error::Custom("invalid wrapper version"));
548 }
549 Self::rlp_decode_fields(buf)
550 }
551}
552
553#[cfg(test)]
554mod tests {
555 use super::*;
556
557 #[test]
558 fn sidecar_variant_rlp_roundtrip() {
559 let mut encoded = Vec::new();
560
561 let empty_sidecar_4844 =
563 BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default());
564 empty_sidecar_4844.encode(&mut encoded);
565 assert_eq!(
566 empty_sidecar_4844,
567 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
568 );
569
570 let sidecar_4844 = BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::new(
571 vec![Blob::default()],
572 vec![Bytes48::ZERO],
573 vec![Bytes48::ZERO],
574 ));
575 encoded.clear();
576 sidecar_4844.encode(&mut encoded);
577 assert_eq!(sidecar_4844, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
578
579 let empty_sidecar_7594 =
581 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default());
582 encoded.clear();
583 empty_sidecar_7594.encode(&mut encoded);
584 assert_eq!(
585 empty_sidecar_7594,
586 BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap()
587 );
588
589 let sidecar_7594 =
590 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::new(
591 vec![Blob::default()],
592 vec![Bytes48::ZERO],
593 core::iter::repeat_n(Bytes48::ZERO, CELLS_PER_EXT_BLOB).collect(),
594 ));
595 encoded.clear();
596 sidecar_7594.encode(&mut encoded);
597 assert_eq!(sidecar_7594, BlobTransactionSidecarVariant::decode(&mut &encoded[..]).unwrap());
598 }
599
600 #[test]
601 #[cfg(feature = "serde")]
602 fn sidecar_variant_json_deserialize_sanity() {
603 let eip4844 = BlobTransactionSidecar::default();
604 let json = serde_json::to_string(&eip4844).unwrap();
605 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
606 assert!(variant.is_eip4844());
607 let jsonvariant = serde_json::to_string(&variant).unwrap();
608 assert_eq!(json, jsonvariant);
609
610 let eip7594 = BlobTransactionSidecarEip7594::default();
611 let json = serde_json::to_string(&eip7594).unwrap();
612 let variant: BlobTransactionSidecarVariant = serde_json::from_str(&json).unwrap();
613 assert!(variant.is_eip7594());
614 let jsonvariant = serde_json::to_string(&variant).unwrap();
615 assert_eq!(json, jsonvariant);
616 }
617
618 #[test]
619 fn rlp_7594_roundtrip() {
620 let mut encoded = Vec::new();
621
622 let sidecar_4844 = BlobTransactionSidecar::default();
623 sidecar_4844.encode_7594(&mut encoded);
624 assert_eq!(sidecar_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
625
626 let sidecar_variant_4844 = BlobTransactionSidecarVariant::Eip4844(sidecar_4844);
627 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
628 encoded.clear();
629 sidecar_variant_4844.encode_7594(&mut encoded);
630 assert_eq!(sidecar_variant_4844, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
631
632 let sidecar_7594 = BlobTransactionSidecarEip7594::default();
633 encoded.clear();
634 sidecar_7594.encode_7594(&mut encoded);
635 assert_eq!(sidecar_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
636
637 let sidecar_variant_7594 = BlobTransactionSidecarVariant::Eip7594(sidecar_7594);
638 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
639 encoded.clear();
640 sidecar_variant_7594.encode_7594(&mut encoded);
641 assert_eq!(sidecar_variant_7594, Decodable7594::decode_7594(&mut &encoded[..]).unwrap());
642 }
643}