1use std::io::Cursor;
2use std::num::NonZeroU64;
3
4use base64::prelude::*;
5use bytes::{Buf, BufMut, BytesMut};
6use celestia_proto::serializers::cow_str::CowStr;
7use nmt_rs::NamespaceMerkleHasher;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use tendermint::crypto::sha256::HASH_SIZE;
10use tendermint::{crypto, merkle};
11#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
12use wasm_bindgen::prelude::*;
13
14use crate::consts::appconsts;
15use crate::nmt::{Namespace, NamespacedHashExt, NamespacedSha2Hasher, Nmt, RawNamespacedHash};
16use crate::state::{AccAddress, AddressTrait};
17use crate::{AppVersion, Error, Result};
18use crate::{InfoByte, Share};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
57#[cfg_attr(
58 all(feature = "wasm-bindgen", target_arch = "wasm32"),
59 wasm_bindgen(inspectable)
60)]
61pub struct Commitment {
62 hash: merkle::Hash,
64}
65
66impl Commitment {
67 pub fn new(hash: merkle::Hash) -> Self {
69 Commitment { hash }
70 }
71
72 pub fn from_blob(
74 namespace: Namespace,
75 blob_data: &[u8],
76 share_version: u8,
77 signer: Option<&AccAddress>,
78 app_version: AppVersion,
79 ) -> Result<Commitment> {
80 validate_blob(share_version, signer.is_some(), Some(app_version))?;
81 let shares = split_blob_to_shares(namespace, share_version, blob_data, signer)?;
82 Self::from_shares(namespace, &shares, app_version)
83 }
84
85 pub fn from_shares(
87 namespace: Namespace,
88 mut shares: &[Share],
89 app_version: AppVersion,
90 ) -> Result<Commitment> {
91 let subtree_root_threshold = appconsts::subtree_root_threshold(app_version);
96 let subtree_width = subtree_width(shares.len() as u64, subtree_root_threshold);
97 let tree_sizes = merkle_mountain_range_sizes(shares.len() as u64, subtree_width);
98
99 let mut leaf_sets: Vec<&[_]> = Vec::with_capacity(tree_sizes.len());
100
101 for size in tree_sizes {
102 let (leafs, rest) = shares.split_at(size as usize);
103 leaf_sets.push(leafs);
104 shares = rest;
105 }
106
107 let mut subtree_roots: Vec<RawNamespacedHash> = Vec::with_capacity(leaf_sets.len());
109 for leaf_set in leaf_sets {
110 let mut tree = Nmt::with_hasher(NamespacedSha2Hasher::with_ignore_max_ns(true));
112 for leaf_share in leaf_set {
113 tree.push_leaf(leaf_share.as_ref(), namespace.into())
114 .map_err(Error::Nmt)?;
115 }
116 subtree_roots.push(tree.root().to_array());
118 }
119
120 let hash = merkle::simple_hash_from_byte_vectors::<crypto::default::Sha256>(&subtree_roots);
121
122 Ok(Commitment { hash })
123 }
124
125 pub fn hash(&self) -> &merkle::Hash {
127 &self.hash
128 }
129}
130
131#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
132#[wasm_bindgen]
133impl Commitment {
134 #[wasm_bindgen(js_name = hash)]
136 pub fn js_hash(&self) -> Vec<u8> {
137 self.hash.to_vec()
138 }
139}
140
141impl From<Commitment> for merkle::Hash {
142 fn from(commitment: Commitment) -> Self {
143 commitment.hash
144 }
145}
146
147impl Serialize for Commitment {
148 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
149 where
150 S: Serializer,
151 {
152 let s = BASE64_STANDARD.encode(self.hash);
153 serializer.serialize_str(&s)
154 }
155}
156
157impl<'de> Deserialize<'de> for Commitment {
158 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159 where
160 D: Deserializer<'de>,
161 {
162 let mut buf = [0u8; HASH_SIZE * 2];
164
165 let s = CowStr::deserialize(deserializer)?;
166
167 let len = BASE64_STANDARD
168 .decode_slice(s, &mut buf)
169 .map_err(|e| serde::de::Error::custom(e.to_string()))?;
170
171 let hash: merkle::Hash = buf[..len]
172 .try_into()
173 .map_err(|_| serde::de::Error::custom("commitment is not a size of a sha256"))?;
174
175 Ok(Commitment { hash })
176 }
177}
178
179pub(crate) fn validate_blob(
182 share_version: u8,
183 has_signer: bool,
184 app_version: Option<AppVersion>,
185) -> Result<()> {
186 if ![appconsts::SHARE_VERSION_ZERO, appconsts::SHARE_VERSION_ONE].contains(&share_version) {
187 return Err(Error::UnsupportedShareVersion(share_version));
188 }
189 if share_version == appconsts::SHARE_VERSION_ZERO && has_signer {
190 return Err(Error::SignerNotSupported);
191 }
192 if share_version == appconsts::SHARE_VERSION_ONE && !has_signer {
193 return Err(Error::MissingSigner);
194 }
195 if app_version
196 .is_some_and(|app| share_version == appconsts::SHARE_VERSION_ONE && app < AppVersion::V3)
197 {
198 return Err(Error::UnsupportedShareVersion(share_version));
199 }
200 Ok(())
201}
202
203pub(crate) fn split_blob_to_shares(
205 namespace: Namespace,
206 share_version: u8,
207 blob_data: &[u8],
208 signer: Option<&AccAddress>,
209) -> Result<Vec<Share>> {
210 let mut shares = Vec::new();
211 let mut cursor = Cursor::new(blob_data);
212
213 while cursor.has_remaining() {
214 let share = build_sparse_share(namespace, share_version, signer, &mut cursor)?;
215 shares.push(share);
216 }
217 Ok(shares)
218}
219
220fn build_sparse_share(
222 namespace: Namespace,
223 share_version: u8,
224 signer: Option<&AccAddress>,
225 data: &mut Cursor<impl AsRef<[u8]>>,
226) -> Result<Share> {
227 let is_first_share = data.position() == 0;
228 let data_len = cursor_inner_length(data);
229 let mut bytes = BytesMut::with_capacity(appconsts::SHARE_SIZE);
230
231 bytes.put_slice(namespace.as_bytes());
233 let info_byte = InfoByte::new(share_version, is_first_share)?;
235 bytes.put_u8(info_byte.as_u8());
236
237 if is_first_share {
239 let data_len = data_len
240 .try_into()
241 .map_err(|_| Error::ShareSequenceLenExceeded(data_len))?;
242 bytes.put_u32(data_len);
243 if share_version == appconsts::SHARE_VERSION_ONE {
245 let signer = signer.as_ref().ok_or(Error::MissingSigner)?;
246 bytes.put_slice(signer.as_bytes());
247 }
248 }
249
250 let current_size = bytes.len();
252 let available_space = appconsts::SHARE_SIZE - current_size;
253 let read_amount = available_space.min(data.remaining());
254
255 bytes.resize(appconsts::SHARE_SIZE, 0);
257 data.copy_to_slice(&mut bytes[current_size..current_size + read_amount]);
259
260 Share::from_raw(&bytes)
261}
262
263fn cursor_inner_length(cursor: &Cursor<impl AsRef<[u8]>>) -> usize {
264 cursor.get_ref().as_ref().len()
265}
266
267fn merkle_mountain_range_sizes(mut total_size: u64, max_tree_size: u64) -> Vec<u64> {
274 let mut tree_sizes = Vec::new();
275
276 while total_size != 0 {
277 if total_size >= max_tree_size {
278 tree_sizes.push(max_tree_size);
279 total_size -= max_tree_size;
280 } else {
281 let tree_size = round_down_to_power_of_2(
282 total_size.try_into().unwrap(),
284 )
285 .expect("Failed to find next power of 2");
286 tree_sizes.push(tree_size);
287 total_size -= tree_size;
288 }
289 }
290
291 tree_sizes
292}
293
294fn blob_min_square_size(share_count: u64) -> u64 {
297 round_up_to_power_of_2((share_count as f64).sqrt().ceil() as u64)
298 .expect("Failed to find minimum blob square size")
299}
300
301fn subtree_width(share_count: u64, subtree_root_threshold: u64) -> u64 {
307 let mut s = share_count / subtree_root_threshold;
310
311 if share_count % subtree_root_threshold != 0 {
313 s += 1;
314 }
315
316 s = round_up_to_power_of_2(s).expect("Failed to find next power of 2");
319
320 s.min(blob_min_square_size(share_count))
324}
325
326fn round_up_to_power_of_2(x: u64) -> Option<u64> {
328 let mut po2 = 1;
329
330 loop {
331 if po2 >= x {
332 return Some(po2);
333 }
334 if let Some(next_po2) = po2.checked_shl(1) {
335 po2 = next_po2;
336 } else {
337 return None;
338 }
339 }
340}
341
342fn round_down_to_power_of_2(x: NonZeroU64) -> Option<u64> {
344 let x: u64 = x.into();
345
346 match round_up_to_power_of_2(x) {
347 Some(po2) if po2 == x => Some(x),
348 Some(po2) => Some(po2 / 2),
349 _ => None,
350 }
351}
352
353#[cfg(feature = "uniffi")]
354mod uniffi_types {
355 use super::{Commitment as RustCommitment, HASH_SIZE};
356 use uniffi::Record;
357
358 use crate::error::UniffiConversionError;
359
360 #[derive(Record)]
361 pub struct Commitment {
362 pub sha_hash: Vec<u8>,
363 }
364
365 impl From<RustCommitment> for Commitment {
366 fn from(value: RustCommitment) -> Self {
367 Commitment {
368 sha_hash: value.hash.to_vec(),
369 }
370 }
371 }
372
373 impl TryFrom<Commitment> for RustCommitment {
374 type Error = UniffiConversionError;
375
376 fn try_from(value: Commitment) -> Result<Self, Self::Error> {
377 let hash: [u8; HASH_SIZE] = value
378 .sha_hash
379 .try_into()
380 .map_err(|_| UniffiConversionError::InvalidCommitmentLength)?;
381 Ok(RustCommitment { hash })
382 }
383 }
384
385 uniffi::custom_type!(RustCommitment, Commitment);
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[cfg(target_arch = "wasm32")]
393 use wasm_bindgen_test::wasm_bindgen_test as test;
394
395 #[test]
396 fn test_single_sparse_share_v0() {
397 let namespace = Namespace::new(0, &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).unwrap();
398 let data = vec![1, 2, 3, 4, 5, 6, 7];
399 let mut cursor = Cursor::new(&data);
400
401 let share = build_sparse_share(namespace, 0, None, &mut cursor).unwrap();
402
403 assert!(!cursor.has_remaining());
405
406 let (share_ns, share_data) = share.as_ref().split_at(appconsts::NAMESPACE_SIZE);
408 assert_eq!(share_ns, namespace.as_bytes());
409
410 let expected_share_start: &[u8] = &[
412 1, 0, 0, 0, 7, 1, 2, 3, 4, 5, 6, 7, ];
416 let (share_data, share_padding) = share_data.split_at(expected_share_start.len());
417 assert_eq!(share_data, expected_share_start);
418
419 assert_eq!(
421 share_padding,
422 &vec![0; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE - data.len()],
423 );
424 }
425
426 #[test]
427 fn test_single_sparse_share_v1() {
428 let namespace = Namespace::new(0, &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).unwrap();
429 let data = vec![1, 2, 3, 4, 5, 6, 7];
430 let signer = AccAddress::from([9; appconsts::SIGNER_SIZE]);
431 let mut cursor = Cursor::new(&data);
432
433 let share = build_sparse_share(namespace, 1, Some(&signer), &mut cursor).unwrap();
434
435 assert!(!cursor.has_remaining());
437
438 let (share_ns, share_data) = share.as_ref().split_at(appconsts::NAMESPACE_SIZE);
440 assert_eq!(share_ns, namespace.as_bytes());
441
442 let expected_share_start: &[u8] = &[
444 0b00000011, 0, 0, 0, 7, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 2, 3, 4, 5, 6, 7, ];
449 let (share_data, share_padding) = share_data.split_at(expected_share_start.len());
450 assert_eq!(share_data, expected_share_start);
451
452 assert_eq!(
454 share_padding,
455 &vec![
456 0;
457 appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE - appconsts::SIGNER_SIZE - data.len()
458 ],
459 );
460 }
461
462 #[test]
463 fn test_sparse_share_v0_with_continuation() {
464 let namespace = Namespace::new(0, &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).unwrap();
465 let continuation_len = 7;
466 let data = vec![7; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE + continuation_len];
467 let mut cursor = Cursor::new(&data);
468
469 let first_share = build_sparse_share(namespace, 0, None, &mut cursor).unwrap();
470
471 assert_eq!(
473 cursor.position(),
474 appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE as u64
475 );
476
477 let (share_ns, share_data) = first_share.as_ref().split_at(appconsts::NAMESPACE_SIZE);
479 assert_eq!(share_ns, namespace.as_bytes());
480
481 let (share_info_byte, share_data) = share_data.split_at(appconsts::SHARE_INFO_BYTES);
483 assert_eq!(share_info_byte, &[1]);
484
485 let (share_seq_len, share_data) = share_data.split_at(appconsts::SEQUENCE_LEN_BYTES);
487 assert_eq!(share_seq_len, &(data.len() as u32).to_be_bytes());
488
489 assert_eq!(
491 share_data,
492 &vec![7; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE]
493 );
494
495 let continuation_share = build_sparse_share(namespace, 0, None, &mut cursor).unwrap();
497
498 assert!(!cursor.has_remaining());
500
501 let (share_ns, share_data) = continuation_share
503 .as_ref()
504 .split_at(appconsts::NAMESPACE_SIZE);
505 assert_eq!(share_ns, namespace.as_bytes());
506
507 let expected_continuation_share_start: &[u8] = &[
509 0, 7, 7, 7, 7, 7, 7, 7, ];
512 let (share_data, share_padding) =
513 share_data.split_at(expected_continuation_share_start.len());
514 assert_eq!(share_data, expected_continuation_share_start);
515
516 assert_eq!(
518 share_padding,
519 &vec![0; appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE - continuation_len],
520 );
521 }
522
523 #[test]
524 fn test_sparse_share_v0_empty_data() {
525 let namespace = Namespace::new(0, &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).unwrap();
526 let data = vec![];
527 let mut cursor = Cursor::new(&data);
528 let expected_share_start: &[u8] = &[
529 1, 0, 0, 0, 0, ];
532
533 let share = build_sparse_share(namespace, 0, None, &mut cursor).unwrap();
534
535 assert!(!cursor.has_remaining());
537
538 let (share_ns, share_data) = share.as_ref().split_at(appconsts::NAMESPACE_SIZE);
540 assert_eq!(share_ns, namespace.as_bytes());
541
542 let (share_start, share_data) = share_data.split_at(expected_share_start.len());
544 assert_eq!(share_start, expected_share_start);
545
546 assert_eq!(
548 share_data,
549 &vec![0; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE],
550 );
551 }
552
553 #[test]
554 fn merkle_mountain_ranges() {
555 struct TestCase {
556 total_size: u64,
557 square_size: u64,
558 expected: Vec<u64>,
559 }
560
561 let test_cases = [
562 TestCase {
563 total_size: 11,
564 square_size: 4,
565 expected: vec![4, 4, 2, 1],
566 },
567 TestCase {
568 total_size: 2,
569 square_size: 64,
570 expected: vec![2],
571 },
572 TestCase {
573 total_size: 64,
574 square_size: 8,
575 expected: vec![8, 8, 8, 8, 8, 8, 8, 8],
576 },
577 TestCase {
589 total_size: 19,
590 square_size: 8,
591 expected: vec![8, 8, 2, 1],
592 },
593 ];
594 for case in test_cases {
595 assert_eq!(
596 merkle_mountain_range_sizes(case.total_size, case.square_size),
597 case.expected,
598 );
599 }
600 }
601
602 #[test]
603 fn blob_validation() {
604 let app_signer_allowed = Some(AppVersion::V3);
605 let app_signer_forbidden = Some(AppVersion::V2);
606 let app_unknown = None;
607
608 let share_signer_required = appconsts::SHARE_VERSION_ONE;
609 let share_signer_forbidden = appconsts::SHARE_VERSION_ZERO;
610 let share_version_unsupported = appconsts::MAX_SHARE_VERSION;
611
612 let with_signer = true;
613 let no_signer = false;
614
615 validate_blob(share_signer_forbidden, no_signer, app_signer_allowed).unwrap();
617 validate_blob(share_signer_forbidden, no_signer, app_signer_forbidden).unwrap();
618 validate_blob(share_signer_forbidden, no_signer, app_unknown).unwrap();
619
620 validate_blob(share_signer_required, with_signer, app_signer_allowed).unwrap();
622 validate_blob(share_signer_required, with_signer, app_unknown).unwrap();
623
624 validate_blob(share_version_unsupported, no_signer, app_signer_allowed).unwrap_err();
626
627 validate_blob(share_signer_required, no_signer, app_signer_forbidden).unwrap_err();
629 validate_blob(share_signer_required, no_signer, app_signer_allowed).unwrap_err();
630 validate_blob(share_signer_required, no_signer, app_unknown).unwrap_err();
631
632 validate_blob(share_signer_required, with_signer, app_signer_forbidden).unwrap_err();
634 validate_blob(share_signer_forbidden, with_signer, app_signer_forbidden).unwrap_err();
635 validate_blob(share_signer_forbidden, with_signer, app_signer_allowed).unwrap_err();
636 validate_blob(share_signer_forbidden, with_signer, app_unknown).unwrap_err();
637 }
638}