1use crate::{
4 eip4844::{
5 kzg_to_versioned_hash, Blob, BlobAndProofV1, Bytes48, BYTES_PER_BLOB, BYTES_PER_COMMITMENT,
6 BYTES_PER_PROOF,
7 },
8 eip7594::{Decodable7594, Encodable7594},
9};
10use alloc::{boxed::Box, vec::Vec};
11use alloy_primitives::{bytes::BufMut, B256};
12use alloy_rlp::{Decodable, Encodable, Header};
13
14#[cfg(any(test, feature = "arbitrary"))]
15use crate::eip4844::MAX_BLOBS_PER_BLOCK_DENCUN;
16
17#[cfg(feature = "kzg")]
19pub(crate) const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
20
21#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct IndexedBlobHash {
25 pub index: u64,
27 pub hash: B256,
29}
30
31#[derive(Clone, Default, PartialEq, Eq, Hash)]
35#[repr(C)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37#[doc(alias = "BlobTxSidecar")]
38pub struct BlobTransactionSidecar {
39 #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::eip4844::deserialize_blobs"))]
41 pub blobs: Vec<Blob>,
42 pub commitments: Vec<Bytes48>,
44 pub proofs: Vec<Bytes48>,
46}
47
48impl core::fmt::Debug for BlobTransactionSidecar {
49 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50 f.debug_struct("BlobTransactionSidecar")
51 .field("blobs", &self.blobs.len())
52 .field("commitments", &self.commitments)
53 .field("proofs", &self.proofs)
54 .finish()
55 }
56}
57
58impl BlobTransactionSidecar {
59 pub fn match_versioned_hashes<'a>(
65 &'a self,
66 versioned_hashes: &'a [B256],
67 ) -> impl Iterator<Item = (usize, BlobAndProofV1)> + 'a {
68 self.versioned_hashes().enumerate().flat_map(move |(i, blob_versioned_hash)| {
69 versioned_hashes.iter().enumerate().filter_map(move |(j, target_hash)| {
70 if blob_versioned_hash == *target_hash {
71 if let Some((blob, proof)) =
72 self.blobs.get(i).copied().zip(self.proofs.get(i).copied())
73 {
74 return Some((j, BlobAndProofV1 { blob: Box::new(blob), proof }));
75 }
76 }
77 None
78 })
79 })
80 }
81
82 #[cfg(feature = "kzg")]
87 pub fn try_into_7594(
88 self,
89 settings: &c_kzg::KzgSettings,
90 ) -> Result<crate::eip7594::BlobTransactionSidecarEip7594, c_kzg::Error> {
91 use crate::eip7594::CELLS_PER_EXT_BLOB;
92
93 let mut cell_proofs = Vec::with_capacity(self.blobs.len() * CELLS_PER_EXT_BLOB);
94
95 for blob in self.blobs.iter() {
96 let blob_kzg = unsafe { core::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
98
99 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob_kzg)?;
101
102 unsafe {
104 for kzg_proof in kzg_proofs.iter() {
105 cell_proofs.push(core::mem::transmute::<c_kzg::Bytes48, Bytes48>(
106 kzg_proof.to_bytes(),
107 ));
108 }
109 }
110 }
111
112 Ok(crate::eip7594::BlobTransactionSidecarEip7594::new(
113 self.blobs,
114 self.commitments,
115 cell_proofs,
116 ))
117 }
118}
119
120impl IntoIterator for BlobTransactionSidecar {
121 type Item = BlobTransactionSidecarItem;
122 type IntoIter = alloc::vec::IntoIter<BlobTransactionSidecarItem>;
123
124 fn into_iter(self) -> Self::IntoIter {
125 self.blobs
126 .into_iter()
127 .zip(self.commitments)
128 .zip(self.proofs)
129 .enumerate()
130 .map(|(index, ((blob, commitment), proof))| BlobTransactionSidecarItem {
131 index: index as u64,
132 blob: Box::new(blob),
133 kzg_commitment: commitment,
134 kzg_proof: proof,
135 })
136 .collect::<Vec<_>>()
137 .into_iter()
138 }
139}
140
141#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
143#[repr(C)]
144#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
145pub struct BlobTransactionSidecarItem {
146 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
148 pub index: u64,
149 #[cfg_attr(feature = "serde", serde(deserialize_with = "super::deserialize_blob"))]
151 pub blob: Box<Blob>,
152 pub kzg_commitment: Bytes48,
154 pub kzg_proof: Bytes48,
156}
157
158#[cfg(feature = "kzg")]
159impl BlobTransactionSidecarItem {
160 pub fn to_kzg_versioned_hash(&self) -> [u8; 32] {
162 use sha2::Digest;
163 let commitment = self.kzg_commitment.as_slice();
164 let mut hash: [u8; 32] = sha2::Sha256::digest(commitment).into();
165 hash[0] = VERSIONED_HASH_VERSION_KZG;
166 hash
167 }
168
169 pub fn verify_blob_kzg_proof(&self) -> Result<(), BlobTransactionValidationError> {
171 let binding = crate::eip4844::env_settings::EnvKzgSettings::Default;
172 let settings = binding.get();
173
174 let blob = c_kzg::Blob::from_bytes(self.blob.as_slice())
175 .map_err(BlobTransactionValidationError::KZGError)?;
176
177 let commitment = c_kzg::Bytes48::from_bytes(self.kzg_commitment.as_slice())
178 .map_err(BlobTransactionValidationError::KZGError)?;
179
180 let proof = c_kzg::Bytes48::from_bytes(self.kzg_proof.as_slice())
181 .map_err(BlobTransactionValidationError::KZGError)?;
182
183 let result = settings
184 .verify_blob_kzg_proof(&blob, &commitment, &proof)
185 .map_err(BlobTransactionValidationError::KZGError)?;
186
187 result.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
188 }
189
190 pub fn verify_blob(
192 &self,
193 hash: &IndexedBlobHash,
194 ) -> Result<(), BlobTransactionValidationError> {
195 if self.index != hash.index {
196 let blob_hash_part = B256::from_slice(&self.blob[0..32]);
197 return Err(BlobTransactionValidationError::WrongVersionedHash {
198 have: blob_hash_part,
199 expected: hash.hash,
200 });
201 }
202
203 let computed_hash = self.to_kzg_versioned_hash();
204 if computed_hash != hash.hash {
205 return Err(BlobTransactionValidationError::WrongVersionedHash {
206 have: computed_hash.into(),
207 expected: hash.hash,
208 });
209 }
210
211 self.verify_blob_kzg_proof()
212 }
213}
214
215#[cfg(any(test, feature = "arbitrary"))]
216impl<'a> arbitrary::Arbitrary<'a> for BlobTransactionSidecar {
217 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
218 let num_blobs = u.int_in_range(1..=MAX_BLOBS_PER_BLOCK_DENCUN)?;
219 let mut blobs = Vec::with_capacity(num_blobs);
220 for _ in 0..num_blobs {
221 blobs.push(Blob::arbitrary(u)?);
222 }
223
224 let mut commitments = Vec::with_capacity(num_blobs);
225 let mut proofs = Vec::with_capacity(num_blobs);
226 for _ in 0..num_blobs {
227 commitments.push(Bytes48::arbitrary(u)?);
228 proofs.push(Bytes48::arbitrary(u)?);
229 }
230
231 Ok(Self { blobs, commitments, proofs })
232 }
233}
234
235impl BlobTransactionSidecar {
236 pub const fn new(blobs: Vec<Blob>, commitments: Vec<Bytes48>, proofs: Vec<Bytes48>) -> Self {
238 Self { blobs, commitments, proofs }
239 }
240
241 #[cfg(feature = "kzg")]
243 pub fn from_kzg(
244 blobs: Vec<c_kzg::Blob>,
245 commitments: Vec<c_kzg::Bytes48>,
246 proofs: Vec<c_kzg::Bytes48>,
247 ) -> Self {
248 unsafe fn transmute_vec<U, T>(input: Vec<T>) -> Vec<U> {
250 let mut v = core::mem::ManuallyDrop::new(input);
251 Vec::from_raw_parts(v.as_mut_ptr() as *mut U, v.len(), v.capacity())
252 }
253
254 unsafe {
256 let blobs = transmute_vec::<Blob, c_kzg::Blob>(blobs);
257 let commitments = transmute_vec::<Bytes48, c_kzg::Bytes48>(commitments);
258 let proofs = transmute_vec::<Bytes48, c_kzg::Bytes48>(proofs);
259 Self { blobs, commitments, proofs }
260 }
261 }
262
263 #[cfg(feature = "kzg")]
277 pub fn validate(
278 &self,
279 blob_versioned_hashes: &[B256],
280 proof_settings: &c_kzg::KzgSettings,
281 ) -> Result<(), BlobTransactionValidationError> {
282 if blob_versioned_hashes.len() != self.commitments.len() {
284 return Err(c_kzg::Error::MismatchLength(format!(
285 "There are {} versioned commitment hashes and {} commitments",
286 blob_versioned_hashes.len(),
287 self.commitments.len()
288 ))
289 .into());
290 }
291
292 for (versioned_hash, commitment) in
294 blob_versioned_hashes.iter().zip(self.commitments.iter())
295 {
296 let calculated_versioned_hash = kzg_to_versioned_hash(commitment.as_slice());
298 if *versioned_hash != calculated_versioned_hash {
299 return Err(BlobTransactionValidationError::WrongVersionedHash {
300 have: *versioned_hash,
301 expected: calculated_versioned_hash,
302 });
303 }
304 }
305
306 let res = unsafe {
308 proof_settings.verify_blob_kzg_proof_batch(
309 core::mem::transmute::<&[Blob], &[c_kzg::Blob]>(self.blobs.as_slice()),
311 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.commitments.as_slice()),
313 core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.proofs.as_slice()),
315 )
316 }
317 .map_err(BlobTransactionValidationError::KZGError)?;
318
319 res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof)
320 }
321
322 pub fn versioned_hashes(&self) -> VersionedHashIter<'_> {
324 VersionedHashIter::new(&self.commitments)
325 }
326
327 pub fn versioned_hash_for_blob(&self, blob_index: usize) -> Option<B256> {
330 self.commitments.get(blob_index).map(|c| kzg_to_versioned_hash(c.as_slice()))
331 }
332
333 pub fn versioned_hash_index(&self, hash: &B256) -> Option<usize> {
335 self.commitments
336 .iter()
337 .position(|commitment| kzg_to_versioned_hash(commitment.as_slice()) == *hash)
338 }
339
340 pub fn blob_by_versioned_hash(&self, hash: &B256) -> Option<&Blob> {
342 self.versioned_hash_index(hash).and_then(|index| self.blobs.get(index))
343 }
344
345 #[inline]
347 pub fn size(&self) -> usize {
348 self.blobs.len() * BYTES_PER_BLOB + self.commitments.len() * BYTES_PER_COMMITMENT + self.proofs.len() * BYTES_PER_PROOF }
352
353 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
357 pub fn try_from_blobs_hex<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
358 where
359 I: IntoIterator<Item = B>,
360 B: AsRef<str>,
361 {
362 let mut b = Vec::new();
363 for blob in blobs {
364 b.push(c_kzg::Blob::from_hex(blob.as_ref())?)
365 }
366 Self::try_from_blobs(b)
367 }
368
369 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
373 pub fn try_from_blobs_bytes<I, B>(blobs: I) -> Result<Self, c_kzg::Error>
374 where
375 I: IntoIterator<Item = B>,
376 B: AsRef<[u8]>,
377 {
378 let mut b = Vec::new();
379 for blob in blobs {
380 b.push(c_kzg::Blob::from_bytes(blob.as_ref())?)
381 }
382 Self::try_from_blobs(b)
383 }
384
385 #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))]
390 pub fn try_from_blobs(blobs: Vec<c_kzg::Blob>) -> Result<Self, c_kzg::Error> {
391 use crate::eip4844::env_settings::EnvKzgSettings;
392
393 let kzg_settings = EnvKzgSettings::Default;
394
395 let commitments = blobs
396 .iter()
397 .map(|blob| {
398 kzg_settings.get().blob_to_kzg_commitment(&blob.clone()).map(|blob| blob.to_bytes())
399 })
400 .collect::<Result<Vec<_>, _>>()?;
401
402 let proofs = blobs
403 .iter()
404 .zip(commitments.iter())
405 .map(|(blob, commitment)| {
406 kzg_settings
407 .get()
408 .compute_blob_kzg_proof(blob, commitment)
409 .map(|blob| blob.to_bytes())
410 })
411 .collect::<Result<Vec<_>, _>>()?;
412
413 Ok(Self::from_kzg(blobs, commitments, proofs))
414 }
415
416 #[doc(hidden)]
419 pub fn rlp_encoded_fields_length(&self) -> usize {
420 self.blobs.length() + self.commitments.length() + self.proofs.length()
421 }
422
423 #[inline]
430 #[doc(hidden)]
431 pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
432 self.blobs.encode(out);
434 self.commitments.encode(out);
435 self.proofs.encode(out);
436 }
437
438 fn rlp_header(&self) -> Header {
440 Header { list: true, payload_length: self.rlp_encoded_fields_length() }
441 }
442
443 pub fn rlp_encoded_length(&self) -> usize {
446 self.rlp_header().length() + self.rlp_encoded_fields_length()
447 }
448
449 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
451 self.rlp_header().encode(out);
452 self.rlp_encode_fields(out);
453 }
454
455 #[doc(hidden)]
457 pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
458 Ok(Self {
459 blobs: Decodable::decode(buf)?,
460 commitments: Decodable::decode(buf)?,
461 proofs: Decodable::decode(buf)?,
462 })
463 }
464
465 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
467 let header = Header::decode(buf)?;
468 if !header.list {
469 return Err(alloy_rlp::Error::UnexpectedString);
470 }
471 if buf.len() < header.payload_length {
472 return Err(alloy_rlp::Error::InputTooShort);
473 }
474 let remaining = buf.len();
475 let this = Self::rlp_decode_fields(buf)?;
476
477 if buf.len() + header.payload_length != remaining {
478 return Err(alloy_rlp::Error::UnexpectedLength);
479 }
480
481 Ok(this)
482 }
483}
484
485impl Encodable for BlobTransactionSidecar {
486 fn encode(&self, out: &mut dyn BufMut) {
488 self.rlp_encode(out);
489 }
490
491 fn length(&self) -> usize {
492 self.rlp_encoded_length()
493 }
494}
495
496impl Decodable for BlobTransactionSidecar {
497 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
499 Self::rlp_decode(buf)
500 }
501}
502
503impl Encodable7594 for BlobTransactionSidecar {
504 fn encode_7594_len(&self) -> usize {
505 self.rlp_encoded_fields_length()
506 }
507
508 fn encode_7594(&self, out: &mut dyn BufMut) {
509 self.rlp_encode_fields(out);
510 }
511}
512
513impl Decodable7594 for BlobTransactionSidecar {
514 fn decode_7594(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
515 Self::rlp_decode_fields(buf)
516 }
517}
518
519#[cfg(all(debug_assertions, feature = "serde"))]
523pub(crate) fn deserialize_blobs_map<'de, M: serde::de::MapAccess<'de>>(
524 map_access: &mut M,
525) -> Result<Vec<Blob>, M::Error> {
526 let raw_blobs: Vec<alloy_primitives::Bytes> = map_access.next_value()?;
527 let mut blobs = Vec::with_capacity(raw_blobs.len());
528 for blob in raw_blobs {
529 blobs.push(Blob::try_from(blob.as_ref()).map_err(serde::de::Error::custom)?);
530 }
531 Ok(blobs)
532}
533
534#[cfg(all(not(debug_assertions), feature = "serde"))]
535#[inline(always)]
536pub(crate) fn deserialize_blobs_map<'de, M: serde::de::MapAccess<'de>>(
537 map_access: &mut M,
538) -> Result<Vec<Blob>, M::Error> {
539 map_access.next_value()
540}
541
542#[derive(Debug)]
544#[cfg(feature = "kzg")]
545pub enum BlobTransactionValidationError {
546 InvalidProof,
548 KZGError(c_kzg::Error),
550 NotBlobTransaction(u8),
552 MissingSidecar,
554 WrongVersionedHash {
556 have: B256,
558 expected: B256,
560 },
561}
562
563#[cfg(feature = "kzg")]
564impl core::error::Error for BlobTransactionValidationError {}
565
566#[cfg(feature = "kzg")]
567impl core::fmt::Display for BlobTransactionValidationError {
568 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
569 match self {
570 Self::InvalidProof => f.write_str("invalid KZG proof"),
571 Self::KZGError(err) => {
572 write!(f, "KZG error: {err:?}")
573 }
574 Self::NotBlobTransaction(err) => {
575 write!(f, "unable to verify proof for non blob transaction: {err}")
576 }
577 Self::MissingSidecar => {
578 f.write_str("eip4844 tx variant without sidecar being used for verification.")
579 }
580 Self::WrongVersionedHash { have, expected } => {
581 write!(f, "wrong versioned hash: have {have}, expected {expected}")
582 }
583 }
584 }
585}
586
587#[cfg(feature = "kzg")]
588impl From<c_kzg::Error> for BlobTransactionValidationError {
589 fn from(source: c_kzg::Error) -> Self {
590 Self::KZGError(source)
591 }
592}
593
594#[derive(Debug, Clone)]
596pub struct VersionedHashIter<'a> {
597 commitments: core::slice::Iter<'a, Bytes48>,
599}
600
601impl<'a> Iterator for VersionedHashIter<'a> {
602 type Item = B256;
603
604 fn next(&mut self) -> Option<Self::Item> {
605 self.commitments.next().map(|c| kzg_to_versioned_hash(c.as_slice()))
606 }
607}
608
609impl<'a> VersionedHashIter<'a> {
611 pub fn new(commitments: &'a [Bytes48]) -> Self {
613 Self { commitments: commitments.iter() }
614 }
615}
616
617#[cfg(test)]
618mod tests {
619 use super::*;
620 use arbitrary::Arbitrary;
621
622 #[test]
623 #[cfg(feature = "serde")]
624 fn deserialize_blob() {
625 let blob = BlobTransactionSidecar {
626 blobs: vec![Blob::default(), Blob::default(), Blob::default(), Blob::default()],
627 commitments: vec![
628 Bytes48::default(),
629 Bytes48::default(),
630 Bytes48::default(),
631 Bytes48::default(),
632 ],
633 proofs: vec![
634 Bytes48::default(),
635 Bytes48::default(),
636 Bytes48::default(),
637 Bytes48::default(),
638 ],
639 };
640
641 let s = serde_json::to_string(&blob).unwrap();
642 let deserialized: BlobTransactionSidecar = serde_json::from_str(&s).unwrap();
643 assert_eq!(blob, deserialized);
644 }
645
646 #[test]
647 fn test_arbitrary_blob() {
648 let mut unstructured = arbitrary::Unstructured::new(b"unstructured blob");
649 let _blob = BlobTransactionSidecar::arbitrary(&mut unstructured).unwrap();
650 }
651
652 #[test]
653 #[cfg(feature = "serde")]
654 fn test_blob_item_serde_roundtrip() {
655 let blob_item = BlobTransactionSidecarItem {
656 index: 0,
657 blob: Box::new(Blob::default()),
658 kzg_commitment: Bytes48::default(),
659 kzg_proof: Bytes48::default(),
660 };
661
662 let s = serde_json::to_string(&blob_item).unwrap();
663 let deserialized: BlobTransactionSidecarItem = serde_json::from_str(&s).unwrap();
664 assert_eq!(blob_item, deserialized);
665 }
666}