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(test)]
354mod tests {
355 use super::*;
356
357 #[cfg(target_arch = "wasm32")]
358 use wasm_bindgen_test::wasm_bindgen_test as test;
359
360 #[test]
361 fn test_single_sparse_share_v0() {
362 let namespace = Namespace::new(0, &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).unwrap();
363 let data = vec![1, 2, 3, 4, 5, 6, 7];
364 let mut cursor = Cursor::new(&data);
365
366 let share = build_sparse_share(namespace, 0, None, &mut cursor).unwrap();
367
368 assert!(!cursor.has_remaining());
370
371 let (share_ns, share_data) = share.as_ref().split_at(appconsts::NAMESPACE_SIZE);
373 assert_eq!(share_ns, namespace.as_bytes());
374
375 let expected_share_start: &[u8] = &[
377 1, 0, 0, 0, 7, 1, 2, 3, 4, 5, 6, 7, ];
381 let (share_data, share_padding) = share_data.split_at(expected_share_start.len());
382 assert_eq!(share_data, expected_share_start);
383
384 assert_eq!(
386 share_padding,
387 &vec![0; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE - data.len()],
388 );
389 }
390
391 #[test]
392 fn test_single_sparse_share_v1() {
393 let namespace = Namespace::new(0, &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).unwrap();
394 let data = vec![1, 2, 3, 4, 5, 6, 7];
395 let signer = AccAddress::from([9; appconsts::SIGNER_SIZE]);
396 let mut cursor = Cursor::new(&data);
397
398 let share = build_sparse_share(namespace, 1, Some(&signer), &mut cursor).unwrap();
399
400 assert!(!cursor.has_remaining());
402
403 let (share_ns, share_data) = share.as_ref().split_at(appconsts::NAMESPACE_SIZE);
405 assert_eq!(share_ns, namespace.as_bytes());
406
407 let expected_share_start: &[u8] = &[
409 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, ];
414 let (share_data, share_padding) = share_data.split_at(expected_share_start.len());
415 assert_eq!(share_data, expected_share_start);
416
417 assert_eq!(
419 share_padding,
420 &vec![
421 0;
422 appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE - appconsts::SIGNER_SIZE - data.len()
423 ],
424 );
425 }
426
427 #[test]
428 fn test_sparse_share_v0_with_continuation() {
429 let namespace = Namespace::new(0, &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).unwrap();
430 let continuation_len = 7;
431 let data = vec![7; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE + continuation_len];
432 let mut cursor = Cursor::new(&data);
433
434 let first_share = build_sparse_share(namespace, 0, None, &mut cursor).unwrap();
435
436 assert_eq!(
438 cursor.position(),
439 appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE as u64
440 );
441
442 let (share_ns, share_data) = first_share.as_ref().split_at(appconsts::NAMESPACE_SIZE);
444 assert_eq!(share_ns, namespace.as_bytes());
445
446 let (share_info_byte, share_data) = share_data.split_at(appconsts::SHARE_INFO_BYTES);
448 assert_eq!(share_info_byte, &[1]);
449
450 let (share_seq_len, share_data) = share_data.split_at(appconsts::SEQUENCE_LEN_BYTES);
452 assert_eq!(share_seq_len, &(data.len() as u32).to_be_bytes());
453
454 assert_eq!(
456 share_data,
457 &vec![7; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE]
458 );
459
460 let continuation_share = build_sparse_share(namespace, 0, None, &mut cursor).unwrap();
462
463 assert!(!cursor.has_remaining());
465
466 let (share_ns, share_data) = continuation_share
468 .as_ref()
469 .split_at(appconsts::NAMESPACE_SIZE);
470 assert_eq!(share_ns, namespace.as_bytes());
471
472 let expected_continuation_share_start: &[u8] = &[
474 0, 7, 7, 7, 7, 7, 7, 7, ];
477 let (share_data, share_padding) =
478 share_data.split_at(expected_continuation_share_start.len());
479 assert_eq!(share_data, expected_continuation_share_start);
480
481 assert_eq!(
483 share_padding,
484 &vec![0; appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE - continuation_len],
485 );
486 }
487
488 #[test]
489 fn test_sparse_share_v0_empty_data() {
490 let namespace = Namespace::new(0, &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).unwrap();
491 let data = vec![];
492 let mut cursor = Cursor::new(&data);
493 let expected_share_start: &[u8] = &[
494 1, 0, 0, 0, 0, ];
497
498 let share = build_sparse_share(namespace, 0, None, &mut cursor).unwrap();
499
500 assert!(!cursor.has_remaining());
502
503 let (share_ns, share_data) = share.as_ref().split_at(appconsts::NAMESPACE_SIZE);
505 assert_eq!(share_ns, namespace.as_bytes());
506
507 let (share_start, share_data) = share_data.split_at(expected_share_start.len());
509 assert_eq!(share_start, expected_share_start);
510
511 assert_eq!(
513 share_data,
514 &vec![0; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE],
515 );
516 }
517
518 #[test]
519 fn merkle_mountain_ranges() {
520 struct TestCase {
521 total_size: u64,
522 square_size: u64,
523 expected: Vec<u64>,
524 }
525
526 let test_cases = [
527 TestCase {
528 total_size: 11,
529 square_size: 4,
530 expected: vec![4, 4, 2, 1],
531 },
532 TestCase {
533 total_size: 2,
534 square_size: 64,
535 expected: vec![2],
536 },
537 TestCase {
538 total_size: 64,
539 square_size: 8,
540 expected: vec![8, 8, 8, 8, 8, 8, 8, 8],
541 },
542 TestCase {
554 total_size: 19,
555 square_size: 8,
556 expected: vec![8, 8, 2, 1],
557 },
558 ];
559 for case in test_cases {
560 assert_eq!(
561 merkle_mountain_range_sizes(case.total_size, case.square_size),
562 case.expected,
563 );
564 }
565 }
566
567 #[test]
568 fn blob_validation() {
569 let app_signer_allowed = Some(AppVersion::V3);
570 let app_signer_forbidden = Some(AppVersion::V2);
571 let app_unknown = None;
572
573 let share_signer_required = appconsts::SHARE_VERSION_ONE;
574 let share_signer_forbidden = appconsts::SHARE_VERSION_ZERO;
575 let share_version_unsupported = appconsts::MAX_SHARE_VERSION;
576
577 let with_signer = true;
578 let no_signer = false;
579
580 validate_blob(share_signer_forbidden, no_signer, app_signer_allowed).unwrap();
582 validate_blob(share_signer_forbidden, no_signer, app_signer_forbidden).unwrap();
583 validate_blob(share_signer_forbidden, no_signer, app_unknown).unwrap();
584
585 validate_blob(share_signer_required, with_signer, app_signer_allowed).unwrap();
587 validate_blob(share_signer_required, with_signer, app_unknown).unwrap();
588
589 validate_blob(share_version_unsupported, no_signer, app_signer_allowed).unwrap_err();
591
592 validate_blob(share_signer_required, no_signer, app_signer_forbidden).unwrap_err();
594 validate_blob(share_signer_required, no_signer, app_signer_allowed).unwrap_err();
595 validate_blob(share_signer_required, no_signer, app_unknown).unwrap_err();
596
597 validate_blob(share_signer_required, with_signer, app_signer_forbidden).unwrap_err();
599 validate_blob(share_signer_forbidden, with_signer, app_signer_forbidden).unwrap_err();
600 validate_blob(share_signer_forbidden, with_signer, app_signer_allowed).unwrap_err();
601 validate_blob(share_signer_forbidden, with_signer, app_unknown).unwrap_err();
602 }
603}