1#[cfg(feature = "uniffi")]
2use std::sync::Arc;
3
4use blockstore::block::{Block, CidError};
5use cid::CidGeneric;
6use multihash::Multihash;
7use nmt_rs::NamespaceMerkleHasher;
8use nmt_rs::simple_merkle::tree::MerkleHash;
9use serde::{Deserialize, Serialize};
10#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
11use wasm_bindgen::prelude::*;
12
13use crate::consts::appconsts::{self, AppVersion};
14#[cfg(feature = "uniffi")]
15use crate::error::UniffiResult;
16use crate::nmt::{
17 NMT_CODEC, NMT_ID_SIZE, NMT_MULTIHASH_CODE, NS_SIZE, Namespace, NamespacedSha2Hasher,
18};
19use crate::state::AccAddress;
20use crate::{Error, Result};
21
22mod info_byte;
23mod proof;
24
25pub use celestia_proto::shwap::Share as RawShare;
26pub use info_byte::InfoByte;
27pub use proof::ShareProof;
28
29const SHARE_SEQUENCE_LENGTH_OFFSET: usize = NS_SIZE + appconsts::SHARE_INFO_BYTES;
30const SHARE_SIGNER_OFFSET: usize = SHARE_SEQUENCE_LENGTH_OFFSET + appconsts::SEQUENCE_LEN_BYTES;
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(try_from = "RawShare", into = "RawShare")]
49#[cfg_attr(
50 all(feature = "wasm-bindgen", target_arch = "wasm32"),
51 wasm_bindgen(inspectable)
52)]
53#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
54pub struct Share {
55 data: [u8; appconsts::SHARE_SIZE],
57 is_parity: bool,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62#[cfg_attr(
63 all(feature = "wasm-bindgen", target_arch = "wasm32"),
64 wasm_bindgen(getter_with_clone, inspectable)
65)]
66pub struct SharesAtHeight {
67 pub height: u64,
69 pub shares: Vec<Share>,
71}
72
73impl Share {
74 pub fn from_raw(data: &[u8]) -> Result<Self> {
92 if data.len() != appconsts::SHARE_SIZE {
93 return Err(Error::InvalidShareSize(data.len()));
94 }
95
96 Namespace::from_raw(&data[..NS_SIZE])?;
98 InfoByte::from_raw(data[NS_SIZE])?;
99
100 Ok(Share {
101 data: data.try_into().unwrap(),
102 is_parity: false,
103 })
104 }
105
106 pub fn parity(data: &[u8]) -> Result<Share> {
114 if data.len() != appconsts::SHARE_SIZE {
115 return Err(Error::InvalidShareSize(data.len()));
116 }
117
118 Ok(Share {
119 data: data.try_into().unwrap(),
120 is_parity: true,
121 })
122 }
123
124 pub fn validate(&self, app: AppVersion) -> Result<()> {
126 if self.info_byte().is_some_and(|info| {
127 info.version() == appconsts::SHARE_VERSION_ONE && app < AppVersion::V3
128 }) {
129 Err(Error::UnsupportedShareVersion(appconsts::SHARE_VERSION_ONE))
130 } else {
131 Ok(())
132 }
133 }
134
135 pub fn is_parity(&self) -> bool {
137 self.is_parity
138 }
139
140 pub fn namespace(&self) -> Namespace {
142 if !self.is_parity {
143 Namespace::new_unchecked(self.data[..NS_SIZE].try_into().unwrap())
144 } else {
145 Namespace::PARITY_SHARE
146 }
147 }
148
149 pub fn info_byte(&self) -> Option<InfoByte> {
153 if !self.is_parity() {
154 Some(InfoByte::from_raw_unchecked(self.data[NS_SIZE]))
155 } else {
156 None
157 }
158 }
159
160 pub fn sequence_length(&self) -> Option<u32> {
162 if self.info_byte()?.is_sequence_start() {
163 let sequence_length_bytes = &self.data[SHARE_SEQUENCE_LENGTH_OFFSET
164 ..SHARE_SEQUENCE_LENGTH_OFFSET + appconsts::SEQUENCE_LEN_BYTES];
165 Some(u32::from_be_bytes(
166 sequence_length_bytes.try_into().unwrap(),
167 ))
168 } else {
169 None
170 }
171 }
172
173 pub fn signer(&self) -> Option<AccAddress> {
176 let info_byte = self.info_byte()?;
177 if info_byte.is_sequence_start() && info_byte.version() == appconsts::SHARE_VERSION_ONE {
178 let signer_bytes =
179 &self.data[SHARE_SIGNER_OFFSET..SHARE_SIGNER_OFFSET + appconsts::SIGNER_SIZE];
180 Some(AccAddress::try_from(signer_bytes).expect("must have correct size"))
181 } else {
182 None
183 }
184 }
185
186 pub fn payload(&self) -> Option<&[u8]> {
193 let info_byte = self.info_byte()?;
194
195 let start = if info_byte.is_sequence_start() {
196 if info_byte.version() == appconsts::SHARE_VERSION_ONE {
197 SHARE_SIGNER_OFFSET + appconsts::SIGNER_SIZE
199 } else {
200 SHARE_SEQUENCE_LENGTH_OFFSET + appconsts::SEQUENCE_LEN_BYTES
203 }
204 } else {
205 SHARE_SEQUENCE_LENGTH_OFFSET
207 };
208 Some(&self.data[start..])
209 }
210
211 pub fn data(&self) -> &[u8; appconsts::SHARE_SIZE] {
213 &self.data
214 }
215
216 pub fn to_vec(&self) -> Vec<u8> {
220 self.as_ref().to_vec()
221 }
222}
223
224#[cfg(feature = "uniffi")]
225#[uniffi::export]
226impl Share {
227 #[uniffi::constructor(name = "from_raw")]
234 pub fn uniffi_from_raw(data: &[u8]) -> UniffiResult<Self> {
235 Ok(Share::from_raw(data)?)
236 }
237
238 #[uniffi::constructor(name = "parity")]
244 pub fn uniffi_parity(data: &[u8]) -> UniffiResult<Share> {
245 Ok(Share::parity(data)?)
246 }
247
248 #[uniffi::method(name = "validate")]
250 pub fn uniffi_validate(&self, app: AppVersion) -> UniffiResult<()> {
251 Ok(self.validate(app)?)
252 }
253
254 #[uniffi::method(name = "is_parity")]
256 pub fn uniffi_is_parity(&self) -> bool {
257 self.is_parity()
258 }
259
260 #[uniffi::method(name = "namespace")]
262 pub fn uniffi_namespace(&self) -> Namespace {
263 self.namespace()
264 }
265
266 #[uniffi::method(name = "info_byte")]
270 pub fn uniffi_info_byte(&self) -> Option<Arc<InfoByte>> {
271 self.info_byte().map(Arc::new)
272 }
273
274 #[uniffi::method(name = "sequence_length")]
276 pub fn uniffi_sequence_length(&self) -> Option<u32> {
277 self.sequence_length()
278 }
279
280 #[uniffi::method(name = "signer")]
283 pub fn uniffi_signer(&self) -> Option<AccAddress> {
284 self.signer()
285 }
286
287 #[uniffi::method(name = "payload")]
294 pub fn uniffi_payload(&self) -> Option<Vec<u8>> {
295 self.payload().map(|p| p.to_vec())
296 }
297
298 #[uniffi::method(name = "data")]
300 pub fn uniffi_data(&self) -> Vec<u8> {
301 self.data.to_vec()
302 }
303}
304
305impl AsRef<[u8]> for Share {
306 fn as_ref(&self) -> &[u8] {
307 &self.data
308 }
309}
310
311impl AsMut<[u8]> for Share {
312 fn as_mut(&mut self) -> &mut [u8] {
313 &mut self.data
314 }
315}
316
317impl Block<NMT_ID_SIZE> for Share {
318 fn cid(&self) -> Result<CidGeneric<NMT_ID_SIZE>, CidError> {
319 let hasher = NamespacedSha2Hasher::with_ignore_max_ns(true);
320 let digest = hasher.hash_leaf(self.as_ref()).iter().collect::<Vec<_>>();
321
322 let mh = Multihash::wrap(NMT_MULTIHASH_CODE, &digest).unwrap();
324
325 Ok(CidGeneric::new_v1(NMT_CODEC, mh))
326 }
327
328 fn data(&self) -> &[u8] {
329 &self.data
330 }
331}
332
333impl TryFrom<RawShare> for Share {
334 type Error = Error;
335
336 fn try_from(value: RawShare) -> Result<Self, Self::Error> {
337 Share::from_raw(&value.data)
338 }
339}
340
341impl From<Share> for RawShare {
342 fn from(value: Share) -> Self {
343 RawShare {
344 data: value.to_vec(),
345 }
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use crate::Blob;
353 use crate::consts::appconsts::AppVersion;
354 use crate::nmt::{NAMESPACED_HASH_SIZE, NamespaceProof, NamespacedHash};
355 use base64::prelude::*;
356
357 #[cfg(target_arch = "wasm32")]
358 use wasm_bindgen_test::wasm_bindgen_test as test;
359
360 #[test]
361 fn share_v0_structure() {
362 let ns = Namespace::new_v0(b"foo").unwrap();
363 let blob = Blob::new(ns, vec![7; 512], None, AppVersion::V2).unwrap();
364
365 let shares = blob.to_shares().unwrap();
366
367 assert_eq!(shares.len(), 2);
368
369 assert_eq!(shares[0].namespace(), ns);
370 assert_eq!(shares[1].namespace(), ns);
371
372 assert_eq!(shares[0].info_byte().unwrap().version(), 0);
373 assert_eq!(shares[1].info_byte().unwrap().version(), 0);
374
375 assert!(shares[0].info_byte().unwrap().is_sequence_start());
376 assert!(!shares[1].info_byte().unwrap().is_sequence_start());
377
378 assert!(shares[0].signer().is_none());
380 assert!(shares[1].signer().is_none());
381
382 const BYTES_IN_SECOND: usize = 512 - appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE;
383 assert_eq!(
384 shares[0].payload().unwrap(),
385 &[7; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE]
386 );
387 assert_eq!(
388 shares[1].payload().unwrap(),
389 &[
390 &[7; BYTES_IN_SECOND][..],
392 &[0; appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE - BYTES_IN_SECOND][..]
394 ]
395 .concat()
396 );
397 }
398
399 #[test]
400 fn share_v1_structure() {
401 let ns = Namespace::new_v0(b"foo").unwrap();
402 let blob = Blob::new(ns, vec![7; 512], Some([5; 20].into()), AppVersion::V3).unwrap();
403
404 let shares = blob.to_shares().unwrap();
405
406 assert_eq!(shares.len(), 2);
407
408 assert_eq!(shares[0].namespace(), ns);
409 assert_eq!(shares[1].namespace(), ns);
410
411 assert_eq!(shares[0].info_byte().unwrap().version(), 1);
412 assert_eq!(shares[1].info_byte().unwrap().version(), 1);
413
414 assert!(shares[0].info_byte().unwrap().is_sequence_start());
415 assert!(!shares[1].info_byte().unwrap().is_sequence_start());
416
417 assert_eq!(shares[0].signer().unwrap(), [5; 20].into());
419 assert!(shares[1].signer().is_none());
420
421 const BYTES_IN_SECOND: usize =
422 512 - appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE + appconsts::SIGNER_SIZE;
423 assert_eq!(
424 shares[0].payload().unwrap(),
425 &[7; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE - appconsts::SIGNER_SIZE]
426 );
427 assert_eq!(
428 shares[1].payload().unwrap(),
429 &[
430 &[7; BYTES_IN_SECOND][..],
432 &[0; appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE - BYTES_IN_SECOND][..]
434 ]
435 .concat()
436 );
437 }
438
439 #[test]
440 fn share_should_have_correct_len() {
441 Share::from_raw(&[0; 0]).unwrap_err();
442 Share::from_raw(&[0; 100]).unwrap_err();
443 Share::from_raw(&[0; appconsts::SHARE_SIZE - 1]).unwrap_err();
444 Share::from_raw(&[0; appconsts::SHARE_SIZE + 1]).unwrap_err();
445 Share::from_raw(&[0; 2 * appconsts::SHARE_SIZE]).unwrap_err();
446
447 Share::from_raw(&vec![0; appconsts::SHARE_SIZE]).unwrap();
448 }
449
450 #[test]
451 fn share_validate() {
452 let app_versions = [
453 AppVersion::V1,
454 AppVersion::V2,
455 AppVersion::V3,
456 AppVersion::latest(),
457 ];
458
459 let share = Share::from_raw(&[0; appconsts::SHARE_SIZE]).unwrap();
461
462 for app in app_versions {
463 share.validate(app).unwrap();
464 }
465
466 let mut data = [0; appconsts::SHARE_SIZE];
468 data[NS_SIZE] = InfoByte::new(appconsts::SHARE_VERSION_ONE, false)
469 .unwrap()
470 .as_u8();
471 let share = Share::from_raw(&data).unwrap();
472
473 for app in app_versions {
474 if app < AppVersion::V3 {
475 share.validate(app).unwrap_err();
476 } else {
477 share.validate(app).unwrap();
478 }
479 }
480 }
481
482 #[test]
483 fn decode_presence_proof() {
484 let blob_get_proof_response = r#"{
485 "start": 1,
486 "end": 2,
487 "nodes": [
488 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA+poCQOx7UzVkteV9DgcA6g29ZXXOp0hYZb67hoNkFP",
489 "/////////////////////////////////////////////////////////////////////////////8PbbPgQcFSaW2J/BWiJqrCoj6K4g/UUd0Y9dadwqrz+"
490 ]
491 }"#;
492
493 let proof: NamespaceProof =
494 serde_json::from_str(blob_get_proof_response).expect("can not parse proof");
495
496 assert!(!proof.is_of_absence());
497
498 let sibling = &proof.siblings()[0];
499 let min_ns_bytes = &sibling.min_namespace().0[..];
500 let max_ns_bytes = &sibling.max_namespace().0[..];
501 let hash_bytes = &sibling.hash()[..];
502 assert_eq!(
503 min_ns_bytes,
504 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=")
505 );
506 assert_eq!(
507 max_ns_bytes,
508 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=")
509 );
510 assert_eq!(
511 hash_bytes,
512 b64_decode("D6mgJA7HtTNWS15X0OBwDqDb1ldc6nSFhlvruGg2QU8=")
513 );
514
515 let sibling = &proof.siblings()[1];
516 let min_ns_bytes = &sibling.min_namespace().0[..];
517 let max_ns_bytes = &sibling.max_namespace().0[..];
518 let hash_bytes = &sibling.hash()[..];
519 assert_eq!(
520 min_ns_bytes,
521 b64_decode("//////////////////////////////////////8=")
522 );
523 assert_eq!(
524 max_ns_bytes,
525 b64_decode("//////////////////////////////////////8=")
526 );
527 assert_eq!(
528 hash_bytes,
529 b64_decode("w9ts+BBwVJpbYn8FaImqsKiPoriD9RR3Rj11p3CqvP4=")
530 );
531 }
532
533 #[test]
534 fn decode_absence_proof() {
535 let blob_get_proof_response = r#"{
536 "start": 1,
537 "end": 2,
538 "nodes": [
539 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABD+sL4GAQk9mj+ejzHmHjUJEyemkpExb+S5aEDtmuHEq",
540 "/////////////////////////////////////////////////////////////////////////////zgUEBW/wWmCfnwXfalgqMfK9sMy168y3XRzdwY1jpZY"
541 ],
542 "leaf_hash": "AAAAAAAAAAAAAAAAAAAAAAAAAJLVUf6krS8362EAAAAAAAAAAAAAAAAAAAAAAAAAktVR/qStLzfrYeEAWUHOa+lE38pJyHstgGaqi9RXPhZtzUscK7iTUbQS",
543 "is_max_namespace_ignored": true
544 }"#;
545
546 let proof: NamespaceProof =
547 serde_json::from_str(blob_get_proof_response).expect("can not parse proof");
548
549 assert!(proof.is_of_absence());
550
551 let sibling = &proof.siblings()[0];
552 let min_ns_bytes = &sibling.min_namespace().0[..];
553 let max_ns_bytes = &sibling.max_namespace().0[..];
554 let hash_bytes = &sibling.hash()[..];
555 assert_eq!(
556 min_ns_bytes,
557 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=")
558 );
559 assert_eq!(
560 max_ns_bytes,
561 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=")
562 );
563 assert_eq!(
564 hash_bytes,
565 b64_decode("P6wvgYBCT2aP56PMeYeNQkTJ6aSkTFv5LloQO2a4cSo=")
566 );
567
568 let sibling = &proof.siblings()[1];
569 let min_ns_bytes = &sibling.min_namespace().0[..];
570 let max_ns_bytes = &sibling.max_namespace().0[..];
571 let hash_bytes = &sibling.hash()[..];
572 assert_eq!(
573 min_ns_bytes,
574 b64_decode("//////////////////////////////////////8=")
575 );
576 assert_eq!(
577 max_ns_bytes,
578 b64_decode("//////////////////////////////////////8=")
579 );
580 assert_eq!(
581 hash_bytes,
582 b64_decode("OBQQFb/BaYJ+fBd9qWCox8r2wzLXrzLddHN3BjWOllg=")
583 );
584
585 let nmt_rs::NamespaceProof::AbsenceProof {
586 leaf: Some(leaf), ..
587 } = &*proof
588 else {
589 unreachable!();
590 };
591
592 let min_ns_bytes = &leaf.min_namespace().0[..];
593 let max_ns_bytes = &leaf.max_namespace().0[..];
594 let hash_bytes = &leaf.hash()[..];
595 assert_eq!(
596 min_ns_bytes,
597 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAJLVUf6krS8362E=")
598 );
599 assert_eq!(
600 max_ns_bytes,
601 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAJLVUf6krS8362E=")
602 );
603 assert_eq!(
604 hash_bytes,
605 b64_decode("4QBZQc5r6UTfyknIey2AZqqL1Fc+Fm3NSxwruJNRtBI=")
606 );
607 }
608
609 fn b64_decode(s: &str) -> Vec<u8> {
610 BASE64_STANDARD.decode(s).expect("failed to decode base64")
611 }
612
613 #[test]
614 fn test_generate_leaf_multihash() {
615 let namespace = Namespace::new_v0(&[1, 2, 3]).unwrap();
616 let mut data = [0xCDu8; appconsts::SHARE_SIZE];
617 data[..NS_SIZE].copy_from_slice(namespace.as_bytes());
618 let share = Share::from_raw(&data).unwrap();
619
620 let cid = share.cid().unwrap();
621 assert_eq!(cid.codec(), NMT_CODEC);
622 let hash = cid.hash();
623 assert_eq!(hash.code(), NMT_MULTIHASH_CODE);
624 assert_eq!(hash.size(), NAMESPACED_HASH_SIZE as u8);
625 let hash = NamespacedHash::try_from(hash.digest()).unwrap();
626 assert_eq!(hash.min_namespace(), *namespace);
627 assert_eq!(hash.max_namespace(), *namespace);
628 }
629}