1use std::iter;
4
5use serde::{Deserialize, Serialize};
6
7mod commitment;
8mod msg_pay_for_blobs;
9
10use crate::consts::appconsts;
11use crate::consts::appconsts::AppVersion;
12use crate::nmt::Namespace;
13use crate::state::{AccAddress, AddressTrait};
14use crate::{bail_validation, Error, Result, Share};
15
16pub use self::commitment::Commitment;
17pub use self::msg_pay_for_blobs::MsgPayForBlobs;
18pub use celestia_proto::celestia::blob::v1::MsgPayForBlobs as RawMsgPayForBlobs;
19pub use celestia_proto::proto::blob::v1::BlobProto as RawBlob;
20pub use celestia_proto::proto::blob::v1::BlobTx as RawBlobTx;
21#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
22use wasm_bindgen::prelude::*;
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(try_from = "custom_serde::SerdeBlob", into = "custom_serde::SerdeBlob")]
30#[cfg_attr(
31 all(feature = "wasm-bindgen", target_arch = "wasm32"),
32 wasm_bindgen(getter_with_clone, inspectable)
33)]
34pub struct Blob {
35 pub namespace: Namespace,
37 pub data: Vec<u8>,
39 pub share_version: u8,
41 pub commitment: Commitment,
43 pub index: Option<u64>,
45 pub signer: Option<AccAddress>,
49}
50
51#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
53pub struct BlobParams {
54 pub gas_per_blob_byte: u32,
56 pub gov_max_square_size: u64,
58}
59
60impl Blob {
61 pub fn new(namespace: Namespace, data: Vec<u8>, app_version: AppVersion) -> Result<Blob> {
89 let share_version = appconsts::SHARE_VERSION_ZERO;
90 let commitment =
91 Commitment::from_blob(namespace, &data[..], share_version, None, app_version)?;
92
93 Ok(Blob {
94 namespace,
95 data,
96 share_version,
97 commitment,
98 index: None,
99 signer: None,
100 })
101 }
102
103 pub fn new_with_signer(
110 namespace: Namespace,
111 data: Vec<u8>,
112 signer: AccAddress,
113 app_version: AppVersion,
114 ) -> Result<Blob> {
115 let signer = Some(signer);
116 let share_version = appconsts::SHARE_VERSION_ONE;
117 let commitment = Commitment::from_blob(
118 namespace,
119 &data[..],
120 share_version,
121 signer.as_ref(),
122 app_version,
123 )?;
124
125 Ok(Blob {
126 namespace,
127 data,
128 share_version,
129 commitment,
130 index: None,
131 signer,
132 })
133 }
134
135 pub fn from_raw(raw: RawBlob, app_version: AppVersion) -> Result<Blob> {
137 let namespace = Namespace::new(raw.namespace_version as u8, &raw.namespace_id)?;
138 let share_version =
139 u8::try_from(raw.share_version).map_err(|_| Error::UnsupportedShareVersion(u8::MAX))?;
140 let signer = raw.signer.try_into().map(AccAddress::new).ok();
141 let commitment = Commitment::from_blob(
142 namespace,
143 &raw.data[..],
144 share_version,
145 signer.as_ref(),
146 app_version,
147 )?;
148
149 Ok(Blob {
150 namespace,
151 data: raw.data,
152 share_version,
153 commitment,
154 index: None,
155 signer,
156 })
157 }
158
159 pub fn validate(&self, app_version: AppVersion) -> Result<()> {
184 let computed_commitment = Commitment::from_blob(
185 self.namespace,
186 &self.data,
187 self.share_version,
188 self.signer.as_ref(),
189 app_version,
190 )?;
191
192 if self.commitment != computed_commitment {
193 bail_validation!("blob commitment != localy computed commitment")
194 }
195
196 Ok(())
197 }
198
199 pub fn to_shares(&self) -> Result<Vec<Share>> {
225 commitment::split_blob_to_shares(
226 self.namespace,
227 self.share_version,
228 &self.data,
229 self.signer.as_ref(),
230 )
231 }
232
233 pub fn reconstruct<'a, I>(shares: I, app_version: AppVersion) -> Result<Self>
258 where
259 I: IntoIterator<Item = &'a Share>,
260 {
261 let mut shares = shares.into_iter();
262 let first_share = shares.next().ok_or(Error::MissingShares)?;
263 let blob_len = first_share
264 .sequence_length()
265 .ok_or(Error::ExpectedShareWithSequenceStart)?;
266 let namespace = first_share.namespace();
267 if namespace.is_reserved() {
268 return Err(Error::UnexpectedReservedNamespace);
269 }
270 let share_version = first_share.info_byte().expect("non parity").version();
271 let signer = first_share.signer();
272
273 let shares_needed = shares_needed_for_blob(blob_len as usize, signer.is_some());
274 let mut data =
275 Vec::with_capacity(shares_needed * appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE);
276 data.extend_from_slice(first_share.payload().expect("non parity"));
277
278 for _ in 1..shares_needed {
279 let share = shares.next().ok_or(Error::MissingShares)?;
280 if share.namespace() != namespace {
281 return Err(Error::BlobSharesMetadataMismatch(format!(
282 "expected namespace ({:?}) got ({:?})",
283 namespace,
284 share.namespace()
285 )));
286 }
287 let version = share.info_byte().expect("non parity").version();
288 if version != share_version {
289 return Err(Error::BlobSharesMetadataMismatch(format!(
290 "expected share version ({}) got ({})",
291 share_version, version
292 )));
293 }
294 if share.sequence_length().is_some() {
295 return Err(Error::UnexpectedSequenceStart);
296 }
297 data.extend_from_slice(share.payload().expect("non parity"));
298 }
299
300 data.truncate(blob_len as usize);
302
303 if share_version == appconsts::SHARE_VERSION_ZERO {
304 Self::new(namespace, data, app_version)
305 } else if share_version == appconsts::SHARE_VERSION_ONE {
306 let signer = signer.ok_or(Error::MissingSigner)?;
308 Self::new_with_signer(namespace, data, signer, app_version)
309 } else {
310 Err(Error::UnsupportedShareVersion(share_version))
311 }
312 }
313
314 pub fn reconstruct_all<'a, I>(shares: I, app_version: AppVersion) -> Result<Vec<Self>>
347 where
348 I: IntoIterator<Item = &'a Share>,
349 {
350 let mut shares = shares
351 .into_iter()
352 .filter(|shr| !shr.namespace().is_reserved());
353 let mut blobs = Vec::with_capacity(2);
354
355 loop {
356 let mut blob = {
357 let Some(start) = shares.find(|&shr| shr.sequence_length().is_some()) else {
359 break;
360 };
361 iter::once(start).chain(&mut shares)
362 };
363 blobs.push(Blob::reconstruct(&mut blob, app_version)?);
364 }
365
366 Ok(blobs)
367 }
368
369 pub fn shares_len(&self) -> usize {
386 let Some(without_first_share) = self
387 .data
388 .len()
389 .checked_sub(appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE)
390 else {
391 return 1;
392 };
393 1 + without_first_share.div_ceil(appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE)
394 }
395}
396
397impl From<Blob> for RawBlob {
398 fn from(value: Blob) -> RawBlob {
399 RawBlob {
400 namespace_id: value.namespace.id().to_vec(),
401 namespace_version: value.namespace.version() as u32,
402 data: value.data,
403 share_version: value.share_version as u32,
404 signer: value
405 .signer
406 .map(|addr| addr.as_bytes().to_vec())
407 .unwrap_or_default(),
408 }
409 }
410}
411
412#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
413#[wasm_bindgen]
414impl Blob {
415 #[wasm_bindgen(constructor)]
417 pub fn js_new(
418 namespace: &Namespace,
419 data: Vec<u8>,
420 app_version: &appconsts::JsAppVersion,
421 ) -> Result<Blob> {
422 Self::new(*namespace, data, (*app_version).into())
423 }
424
425 #[wasm_bindgen(js_name = clone)]
427 pub fn js_clone(&self) -> Blob {
428 self.clone()
429 }
430}
431
432fn shares_needed_for_blob(blob_len: usize, has_signer: bool) -> usize {
433 let mut first_share_content = appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE;
434 if has_signer {
435 first_share_content -= appconsts::SIGNER_SIZE;
436 }
437
438 let Some(without_first_share) = blob_len.checked_sub(first_share_content) else {
439 return 1;
440 };
441 1 + without_first_share.div_ceil(appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE)
442}
443
444mod custom_serde {
445 use serde::de::Error as _;
446 use serde::ser::Error as _;
447 use serde::{Deserialize, Deserializer, Serialize, Serializer};
448 use tendermint_proto::serializers::bytes::base64string;
449
450 use crate::nmt::Namespace;
451 use crate::state::{AccAddress, AddressTrait};
452 use crate::{Error, Result};
453
454 use super::{commitment, Blob, Commitment};
455
456 mod index_serde {
457 use super::*;
458 pub fn serialize<S>(value: &Option<u64>, serializer: S) -> Result<S::Ok, S::Error>
460 where
461 S: Serializer,
462 {
463 let x = value
464 .map(i64::try_from)
465 .transpose()
466 .map_err(S::Error::custom)?
467 .unwrap_or(-1);
468 serializer.serialize_i64(x)
469 }
470
471 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
473 where
474 D: Deserializer<'de>,
475 {
476 i64::deserialize(deserializer).map(|val| if val >= 0 { Some(val as u64) } else { None })
477 }
478 }
479
480 mod signer_serde {
481 use super::*;
482
483 pub fn serialize<S>(value: &Option<AccAddress>, serializer: S) -> Result<S::Ok, S::Error>
485 where
486 S: Serializer,
487 {
488 if let Some(ref addr) = value.as_ref().map(|addr| addr.as_bytes()) {
489 base64string::serialize(addr, serializer)
490 } else {
491 serializer.serialize_none()
492 }
493 }
494
495 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<AccAddress>, D::Error>
497 where
498 D: Deserializer<'de>,
499 {
500 let bytes: Vec<u8> = base64string::deserialize(deserializer)?;
501 if bytes.is_empty() {
502 Ok(None)
503 } else {
504 let addr = AccAddress::new(bytes.try_into().map_err(D::Error::custom)?);
505 Ok(Some(addr))
506 }
507 }
508 }
509
510 #[derive(Serialize, Deserialize)]
512 pub(super) struct SerdeBlob {
513 namespace: Namespace,
514 #[serde(with = "base64string")]
515 data: Vec<u8>,
516 share_version: u8,
517 commitment: Commitment,
518 #[serde(default, with = "index_serde")]
520 index: Option<u64>,
521 #[serde(default, with = "signer_serde")]
522 signer: Option<AccAddress>,
523 }
524
525 impl From<Blob> for SerdeBlob {
526 fn from(value: Blob) -> Self {
527 Self {
528 namespace: value.namespace,
529 data: value.data,
530 share_version: value.share_version,
531 commitment: value.commitment,
532 index: value.index,
533 signer: value.signer,
534 }
535 }
536 }
537
538 impl TryFrom<SerdeBlob> for Blob {
539 type Error = Error;
540
541 fn try_from(value: SerdeBlob) -> Result<Self> {
542 commitment::validate_blob(value.share_version, value.signer.is_some(), None)?;
545
546 Ok(Blob {
547 namespace: value.namespace,
548 data: value.data,
549 share_version: value.share_version,
550 commitment: value.commitment,
551 index: value.index,
552 signer: value.signer,
553 })
554 }
555 }
556}
557
558#[cfg(test)]
559mod tests {
560 use super::*;
561 use crate::nmt::{NS_ID_SIZE, NS_SIZE};
562 use crate::test_utils::random_bytes;
563
564 #[cfg(target_arch = "wasm32")]
565 use wasm_bindgen_test::wasm_bindgen_test as test;
566
567 fn sample_blob() -> Blob {
568 serde_json::from_str(
569 r#"{
570 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAAAAADCBNOWAP3dM=",
571 "data": "8fIMqAB+kQo7+LLmHaDya8oH73hxem6lQWX1",
572 "share_version": 0,
573 "commitment": "D6YGsPWdxR8ju2OcOspnkgPG2abD30pSHxsFdiPqnVk=",
574 "index": -1
575 }"#,
576 )
577 .unwrap()
578 }
579
580 fn sample_blob_with_signer() -> Blob {
581 serde_json::from_str(
582 r#"{
583 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAALwwSWpxCuQb5+A=",
584 "data": "lQnnMKE=",
585 "share_version": 1,
586 "commitment": "dujykaNN+Ey7ET3QNdPG0g2uveriBvZusA3fLSOdMKU=",
587 "index": -1,
588 "signer": "Yjc3XldhbdYke5i8aSlggYxCCLE="
589 }"#,
590 )
591 .unwrap()
592 }
593
594 #[test]
595 fn create_from_raw() {
596 let expected = sample_blob();
597 let raw = RawBlob::from(expected.clone());
598 let created = Blob::from_raw(raw, AppVersion::V2).unwrap();
599
600 assert_eq!(created, expected);
601 }
602
603 #[test]
604 fn create_from_raw_with_signer() {
605 let expected = sample_blob_with_signer();
606
607 let raw = RawBlob::from(expected.clone());
608
609 Blob::from_raw(raw.clone(), AppVersion::V2).unwrap_err();
610 let created = Blob::from_raw(raw, AppVersion::V3).unwrap();
611
612 assert_eq!(created, expected);
613 }
614
615 #[test]
616 fn validate_blob() {
617 sample_blob().validate(AppVersion::V2).unwrap();
618 }
619
620 #[test]
621 fn validate_blob_with_signer() {
622 sample_blob_with_signer()
623 .validate(AppVersion::V2)
624 .unwrap_err();
625 sample_blob_with_signer().validate(AppVersion::V3).unwrap();
626 }
627
628 #[test]
629 fn validate_blob_commitment_mismatch() {
630 let mut blob = sample_blob();
631 blob.commitment = Commitment::new([7; 32]);
632
633 blob.validate(AppVersion::V2).unwrap_err();
634 }
635
636 #[test]
637 fn deserialize_blob_with_missing_index() {
638 serde_json::from_str::<Blob>(
639 r#"{
640 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAAAAADCBNOWAP3dM=",
641 "data": "8fIMqAB+kQo7+LLmHaDya8oH73hxem6lQWX1",
642 "share_version": 0,
643 "commitment": "D6YGsPWdxR8ju2OcOspnkgPG2abD30pSHxsFdiPqnVk="
644 }"#,
645 )
646 .unwrap();
647 }
648
649 #[test]
650 fn deserialize_blob_with_share_version_and_signer_mismatch() {
651 serde_json::from_str::<Blob>(
653 r#"{
654 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAALwwSWpxCuQb5+A=",
655 "data": "lQnnMKE=",
656 "share_version": 0,
657 "commitment": "dujykaNN+Ey7ET3QNdPG0g2uveriBvZusA3fLSOdMKU=",
658 "signer": "Yjc3XldhbdYke5i8aSlggYxCCLE="
659 }"#,
660 )
661 .unwrap_err();
662
663 serde_json::from_str::<Blob>(
665 r#"{
666 "namespace": "AAAAAAAAAAAAAAAAAAAAAAAAALwwSWpxCuQb5+A=",
667 "data": "lQnnMKE=",
668 "share_version": 1,
669 "commitment": "dujykaNN+Ey7ET3QNdPG0g2uveriBvZusA3fLSOdMKU=",
670 }"#,
671 )
672 .unwrap_err();
673 }
674
675 #[test]
676 fn reconstruct() {
677 for _ in 0..10 {
678 let len = rand::random::<usize>() % (1024 * 1024) + 1;
679 let data = random_bytes(len);
680 let ns = Namespace::const_v0(rand::random());
681 let blob = Blob::new(ns, data, AppVersion::V2).unwrap();
682
683 let shares = blob.to_shares().unwrap();
684 assert_eq!(blob, Blob::reconstruct(&shares, AppVersion::V2).unwrap());
685 }
686 }
687
688 #[test]
689 fn reconstruct_with_signer() {
690 for _ in 0..10 {
691 let len = rand::random::<usize>() % (1024 * 1024) + 1;
692 let data = random_bytes(len);
693 let ns = Namespace::const_v0(rand::random());
694 let signer = rand::random::<[u8; 20]>().into();
695
696 let blob = Blob::new_with_signer(ns, data, signer, AppVersion::V3).unwrap();
697 let shares = blob.to_shares().unwrap();
698
699 Blob::reconstruct(&shares, AppVersion::V2).unwrap_err();
700 assert_eq!(blob, Blob::reconstruct(&shares, AppVersion::V3).unwrap());
701 }
702 }
703
704 #[test]
705 fn reconstruct_empty() {
706 assert!(matches!(
707 Blob::reconstruct(&Vec::<Share>::new(), AppVersion::V2),
708 Err(Error::MissingShares)
709 ));
710 }
711
712 #[test]
713 fn reconstruct_not_sequence_start() {
714 let len = rand::random::<usize>() % (1024 * 1024) + 1;
715 let data = random_bytes(len);
716 let ns = Namespace::const_v0(rand::random());
717 let mut shares = Blob::new(ns, data, AppVersion::V2)
718 .unwrap()
719 .to_shares()
720 .unwrap();
721
722 shares[0].as_mut()[NS_SIZE] &= 0b11111110;
724
725 assert!(matches!(
726 Blob::reconstruct(&shares, AppVersion::V2),
727 Err(Error::ExpectedShareWithSequenceStart)
728 ));
729 }
730
731 #[test]
732 fn reconstruct_reserved_namespace() {
733 for ns in (0..255).flat_map(|n| {
734 let mut v0 = [0; NS_ID_SIZE];
735 *v0.last_mut().unwrap() = n;
736 let mut v255 = [0xff; NS_ID_SIZE];
737 *v255.last_mut().unwrap() = n;
738
739 [Namespace::new_v0(&v0), Namespace::new_v255(&v255)]
740 }) {
741 let len = (rand::random::<usize>() % 1023 + 1) * 2;
742 let data = random_bytes(len);
743 let shares = Blob::new(ns.unwrap(), data, AppVersion::V2)
744 .unwrap()
745 .to_shares()
746 .unwrap();
747
748 assert!(matches!(
749 Blob::reconstruct(&shares, AppVersion::V2),
750 Err(Error::UnexpectedReservedNamespace)
751 ));
752 }
753 }
754
755 #[test]
756 fn reconstruct_not_enough_shares() {
757 let len = rand::random::<usize>() % 1024 * 1024 + 2048;
758 let data = random_bytes(len);
759 let ns = Namespace::const_v0(rand::random());
760 let shares = Blob::new(ns, data, AppVersion::V2)
761 .unwrap()
762 .to_shares()
763 .unwrap();
764
765 assert!(matches!(
766 Blob::reconstruct(&shares[..2], AppVersion::V2),
768 Err(Error::MissingShares)
769 ));
770 }
771
772 #[test]
773 fn reconstruct_inconsistent_share_version() {
774 let len = rand::random::<usize>() % (1024 * 1024) + 512;
775 let data = random_bytes(len);
776 let ns = Namespace::const_v0(rand::random());
777 let mut shares = Blob::new(ns, data, AppVersion::V2)
778 .unwrap()
779 .to_shares()
780 .unwrap();
781
782 shares[1].as_mut()[NS_SIZE] = 0b11111110;
784
785 assert!(matches!(
786 Blob::reconstruct(&shares, AppVersion::V2),
787 Err(Error::BlobSharesMetadataMismatch(..))
788 ));
789 }
790
791 #[test]
792 fn reconstruct_inconsistent_namespace() {
793 let len = rand::random::<usize>() % (1024 * 1024) + 512;
794 let data = random_bytes(len);
795 let ns = Namespace::const_v0(rand::random());
796 let ns2 = Namespace::const_v0(rand::random());
797 let mut shares = Blob::new(ns, data, AppVersion::V2)
798 .unwrap()
799 .to_shares()
800 .unwrap();
801
802 shares[1].as_mut()[..NS_SIZE].copy_from_slice(ns2.as_bytes());
804
805 assert!(matches!(
806 Blob::reconstruct(&shares, AppVersion::V2),
807 Err(Error::BlobSharesMetadataMismatch(..))
808 ));
809 }
810
811 #[test]
812 fn reconstruct_unexpected_sequence_start() {
813 let len = rand::random::<usize>() % (1024 * 1024) + 512;
814 let data = random_bytes(len);
815 let ns = Namespace::const_v0(rand::random());
816 let mut shares = Blob::new(ns, data, AppVersion::V2)
817 .unwrap()
818 .to_shares()
819 .unwrap();
820
821 shares[1].as_mut()[NS_SIZE] |= 0b00000001;
823
824 assert!(matches!(
825 Blob::reconstruct(&shares, AppVersion::V2),
826 Err(Error::UnexpectedSequenceStart)
827 ));
828 }
829
830 #[test]
831 fn reconstruct_all() {
832 let blobs: Vec<_> = (0..rand::random::<usize>() % 16 + 3)
833 .map(|_| {
834 let len = rand::random::<usize>() % (1024 * 1024) + 512;
835 let data = random_bytes(len);
836 let ns = Namespace::const_v0(rand::random());
837 Blob::new(ns, data, AppVersion::V2).unwrap()
838 })
839 .collect();
840
841 let shares: Vec<_> = blobs
842 .iter()
843 .flat_map(|blob| blob.to_shares().unwrap())
844 .collect();
845 let reconstructed = Blob::reconstruct_all(&shares, AppVersion::V2).unwrap();
846
847 assert_eq!(blobs, reconstructed);
848 }
849}