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;
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 is_parity(&self) -> bool {
126 self.is_parity
127 }
128
129 pub fn namespace(&self) -> Namespace {
131 if !self.is_parity {
132 Namespace::new_unchecked(self.data[..NS_SIZE].try_into().unwrap())
133 } else {
134 Namespace::PARITY_SHARE
135 }
136 }
137
138 pub fn info_byte(&self) -> Option<InfoByte> {
142 if !self.is_parity() {
143 Some(InfoByte::from_raw_unchecked(self.data[NS_SIZE]))
144 } else {
145 None
146 }
147 }
148
149 pub fn sequence_length(&self) -> Option<u32> {
151 if self.info_byte()?.is_sequence_start() {
152 let sequence_length_bytes = &self.data[SHARE_SEQUENCE_LENGTH_OFFSET
153 ..SHARE_SEQUENCE_LENGTH_OFFSET + appconsts::SEQUENCE_LEN_BYTES];
154 Some(u32::from_be_bytes(
155 sequence_length_bytes.try_into().unwrap(),
156 ))
157 } else {
158 None
159 }
160 }
161
162 pub fn signer(&self) -> Option<AccAddress> {
165 let info_byte = self.info_byte()?;
166 if info_byte.is_sequence_start() && info_byte.version() == appconsts::SHARE_VERSION_ONE {
167 let signer_bytes =
168 &self.data[SHARE_SIGNER_OFFSET..SHARE_SIGNER_OFFSET + appconsts::SIGNER_SIZE];
169 Some(AccAddress::try_from(signer_bytes).expect("must have correct size"))
170 } else {
171 None
172 }
173 }
174
175 pub fn payload(&self) -> Option<&[u8]> {
182 let info_byte = self.info_byte()?;
183
184 let start = if info_byte.is_sequence_start() {
185 if info_byte.version() == appconsts::SHARE_VERSION_ONE {
186 SHARE_SIGNER_OFFSET + appconsts::SIGNER_SIZE
188 } else {
189 SHARE_SEQUENCE_LENGTH_OFFSET + appconsts::SEQUENCE_LEN_BYTES
192 }
193 } else {
194 SHARE_SEQUENCE_LENGTH_OFFSET
196 };
197 Some(&self.data[start..])
198 }
199
200 pub fn data(&self) -> &[u8; appconsts::SHARE_SIZE] {
202 &self.data
203 }
204
205 pub fn to_vec(&self) -> Vec<u8> {
209 self.as_ref().to_vec()
210 }
211}
212
213#[cfg(feature = "uniffi")]
214#[uniffi::export]
215impl Share {
216 #[uniffi::constructor(name = "from_raw")]
223 pub fn uniffi_from_raw(data: &[u8]) -> UniffiResult<Self> {
224 Ok(Share::from_raw(data)?)
225 }
226
227 #[uniffi::constructor(name = "parity")]
233 pub fn uniffi_parity(data: &[u8]) -> UniffiResult<Share> {
234 Ok(Share::parity(data)?)
235 }
236
237 #[uniffi::method(name = "is_parity")]
239 pub fn uniffi_is_parity(&self) -> bool {
240 self.is_parity()
241 }
242
243 #[uniffi::method(name = "namespace")]
245 pub fn uniffi_namespace(&self) -> Namespace {
246 self.namespace()
247 }
248
249 #[uniffi::method(name = "info_byte")]
253 pub fn uniffi_info_byte(&self) -> Option<Arc<InfoByte>> {
254 self.info_byte().map(Arc::new)
255 }
256
257 #[uniffi::method(name = "sequence_length")]
259 pub fn uniffi_sequence_length(&self) -> Option<u32> {
260 self.sequence_length()
261 }
262
263 #[uniffi::method(name = "signer")]
266 pub fn uniffi_signer(&self) -> Option<AccAddress> {
267 self.signer()
268 }
269
270 #[uniffi::method(name = "payload")]
277 pub fn uniffi_payload(&self) -> Option<Vec<u8>> {
278 self.payload().map(|p| p.to_vec())
279 }
280
281 #[uniffi::method(name = "data")]
283 pub fn uniffi_data(&self) -> Vec<u8> {
284 self.data.to_vec()
285 }
286}
287
288impl AsRef<[u8]> for Share {
289 fn as_ref(&self) -> &[u8] {
290 &self.data
291 }
292}
293
294impl AsMut<[u8]> for Share {
295 fn as_mut(&mut self) -> &mut [u8] {
296 &mut self.data
297 }
298}
299
300impl Block<NMT_ID_SIZE> for Share {
301 fn cid(&self) -> Result<CidGeneric<NMT_ID_SIZE>, CidError> {
302 let hasher = NamespacedSha2Hasher::with_ignore_max_ns(true);
303 let digest = hasher.hash_leaf(self.as_ref()).iter().collect::<Vec<_>>();
304
305 let mh = Multihash::wrap(NMT_MULTIHASH_CODE, &digest).unwrap();
307
308 Ok(CidGeneric::new_v1(NMT_CODEC, mh))
309 }
310
311 fn data(&self) -> &[u8] {
312 &self.data
313 }
314}
315
316impl TryFrom<RawShare> for Share {
317 type Error = Error;
318
319 fn try_from(value: RawShare) -> Result<Self, Self::Error> {
320 Share::from_raw(&value.data)
321 }
322}
323
324impl From<Share> for RawShare {
325 fn from(value: Share) -> Self {
326 RawShare {
327 data: value.to_vec(),
328 }
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use crate::Blob;
336 use crate::nmt::{NAMESPACED_HASH_SIZE, NamespaceProof, NamespacedHash};
337 use base64::prelude::*;
338
339 #[cfg(target_arch = "wasm32")]
340 use wasm_bindgen_test::wasm_bindgen_test as test;
341
342 #[test]
343 fn share_v0_structure() {
344 let ns = Namespace::new_v0(b"foo").unwrap();
345 let blob = Blob::new(ns, vec![7; 512], None).unwrap();
346
347 let shares = blob.to_shares().unwrap();
348
349 assert_eq!(shares.len(), 2);
350
351 assert_eq!(shares[0].namespace(), ns);
352 assert_eq!(shares[1].namespace(), ns);
353
354 assert_eq!(shares[0].info_byte().unwrap().version(), 0);
355 assert_eq!(shares[1].info_byte().unwrap().version(), 0);
356
357 assert!(shares[0].info_byte().unwrap().is_sequence_start());
358 assert!(!shares[1].info_byte().unwrap().is_sequence_start());
359
360 assert!(shares[0].signer().is_none());
362 assert!(shares[1].signer().is_none());
363
364 const BYTES_IN_SECOND: usize = 512 - appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE;
365 assert_eq!(
366 shares[0].payload().unwrap(),
367 &[7; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE]
368 );
369 assert_eq!(
370 shares[1].payload().unwrap(),
371 &[
372 &[7; BYTES_IN_SECOND][..],
374 &[0; appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE - BYTES_IN_SECOND][..]
376 ]
377 .concat()
378 );
379 }
380
381 #[test]
382 fn share_v1_structure() {
383 let ns = Namespace::new_v0(b"foo").unwrap();
384 let blob = Blob::new(ns, vec![7; 512], Some([5; 20].into())).unwrap();
385
386 let shares = blob.to_shares().unwrap();
387
388 assert_eq!(shares.len(), 2);
389
390 assert_eq!(shares[0].namespace(), ns);
391 assert_eq!(shares[1].namespace(), ns);
392
393 assert_eq!(shares[0].info_byte().unwrap().version(), 1);
394 assert_eq!(shares[1].info_byte().unwrap().version(), 1);
395
396 assert!(shares[0].info_byte().unwrap().is_sequence_start());
397 assert!(!shares[1].info_byte().unwrap().is_sequence_start());
398
399 assert_eq!(shares[0].signer().unwrap(), [5; 20].into());
401 assert!(shares[1].signer().is_none());
402
403 const BYTES_IN_SECOND: usize =
404 512 - appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE + appconsts::SIGNER_SIZE;
405 assert_eq!(
406 shares[0].payload().unwrap(),
407 &[7; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE - appconsts::SIGNER_SIZE]
408 );
409 assert_eq!(
410 shares[1].payload().unwrap(),
411 &[
412 &[7; BYTES_IN_SECOND][..],
414 &[0; appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE - BYTES_IN_SECOND][..]
416 ]
417 .concat()
418 );
419 }
420
421 #[test]
422 fn share_should_have_correct_len() {
423 Share::from_raw(&[0; 0]).unwrap_err();
424 Share::from_raw(&[0; 100]).unwrap_err();
425 Share::from_raw(&[0; appconsts::SHARE_SIZE - 1]).unwrap_err();
426 Share::from_raw(&[0; appconsts::SHARE_SIZE + 1]).unwrap_err();
427 Share::from_raw(&[0; 2 * appconsts::SHARE_SIZE]).unwrap_err();
428
429 Share::from_raw(&vec![0; appconsts::SHARE_SIZE]).unwrap();
430 }
431
432 #[test]
433 fn decode_presence_proof() {
434 let blob_get_proof_response = r#"{
435 "start": 1,
436 "end": 2,
437 "nodes": [
438 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA+poCQOx7UzVkteV9DgcA6g29ZXXOp0hYZb67hoNkFP",
439 "/////////////////////////////////////////////////////////////////////////////8PbbPgQcFSaW2J/BWiJqrCoj6K4g/UUd0Y9dadwqrz+"
440 ]
441 }"#;
442
443 let proof: NamespaceProof =
444 serde_json::from_str(blob_get_proof_response).expect("can not parse proof");
445
446 assert!(!proof.is_of_absence());
447
448 let sibling = &proof.siblings()[0];
449 let min_ns_bytes = &sibling.min_namespace().0[..];
450 let max_ns_bytes = &sibling.max_namespace().0[..];
451 let hash_bytes = &sibling.hash()[..];
452 assert_eq!(
453 min_ns_bytes,
454 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=")
455 );
456 assert_eq!(
457 max_ns_bytes,
458 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=")
459 );
460 assert_eq!(
461 hash_bytes,
462 b64_decode("D6mgJA7HtTNWS15X0OBwDqDb1ldc6nSFhlvruGg2QU8=")
463 );
464
465 let sibling = &proof.siblings()[1];
466 let min_ns_bytes = &sibling.min_namespace().0[..];
467 let max_ns_bytes = &sibling.max_namespace().0[..];
468 let hash_bytes = &sibling.hash()[..];
469 assert_eq!(
470 min_ns_bytes,
471 b64_decode("//////////////////////////////////////8=")
472 );
473 assert_eq!(
474 max_ns_bytes,
475 b64_decode("//////////////////////////////////////8=")
476 );
477 assert_eq!(
478 hash_bytes,
479 b64_decode("w9ts+BBwVJpbYn8FaImqsKiPoriD9RR3Rj11p3CqvP4=")
480 );
481 }
482
483 #[test]
484 fn decode_absence_proof() {
485 let blob_get_proof_response = r#"{
486 "start": 1,
487 "end": 2,
488 "nodes": [
489 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABD+sL4GAQk9mj+ejzHmHjUJEyemkpExb+S5aEDtmuHEq",
490 "/////////////////////////////////////////////////////////////////////////////zgUEBW/wWmCfnwXfalgqMfK9sMy168y3XRzdwY1jpZY"
491 ],
492 "leaf_hash": "AAAAAAAAAAAAAAAAAAAAAAAAAJLVUf6krS8362EAAAAAAAAAAAAAAAAAAAAAAAAAktVR/qStLzfrYeEAWUHOa+lE38pJyHstgGaqi9RXPhZtzUscK7iTUbQS",
493 "is_max_namespace_ignored": true
494 }"#;
495
496 let proof: NamespaceProof =
497 serde_json::from_str(blob_get_proof_response).expect("can not parse proof");
498
499 assert!(proof.is_of_absence());
500
501 let sibling = &proof.siblings()[0];
502 let min_ns_bytes = &sibling.min_namespace().0[..];
503 let max_ns_bytes = &sibling.max_namespace().0[..];
504 let hash_bytes = &sibling.hash()[..];
505 assert_eq!(
506 min_ns_bytes,
507 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=")
508 );
509 assert_eq!(
510 max_ns_bytes,
511 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=")
512 );
513 assert_eq!(
514 hash_bytes,
515 b64_decode("P6wvgYBCT2aP56PMeYeNQkTJ6aSkTFv5LloQO2a4cSo=")
516 );
517
518 let sibling = &proof.siblings()[1];
519 let min_ns_bytes = &sibling.min_namespace().0[..];
520 let max_ns_bytes = &sibling.max_namespace().0[..];
521 let hash_bytes = &sibling.hash()[..];
522 assert_eq!(
523 min_ns_bytes,
524 b64_decode("//////////////////////////////////////8=")
525 );
526 assert_eq!(
527 max_ns_bytes,
528 b64_decode("//////////////////////////////////////8=")
529 );
530 assert_eq!(
531 hash_bytes,
532 b64_decode("OBQQFb/BaYJ+fBd9qWCox8r2wzLXrzLddHN3BjWOllg=")
533 );
534
535 let nmt_rs::NamespaceProof::AbsenceProof {
536 leaf: Some(leaf), ..
537 } = &*proof
538 else {
539 unreachable!();
540 };
541
542 let min_ns_bytes = &leaf.min_namespace().0[..];
543 let max_ns_bytes = &leaf.max_namespace().0[..];
544 let hash_bytes = &leaf.hash()[..];
545 assert_eq!(
546 min_ns_bytes,
547 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAJLVUf6krS8362E=")
548 );
549 assert_eq!(
550 max_ns_bytes,
551 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAJLVUf6krS8362E=")
552 );
553 assert_eq!(
554 hash_bytes,
555 b64_decode("4QBZQc5r6UTfyknIey2AZqqL1Fc+Fm3NSxwruJNRtBI=")
556 );
557 }
558
559 fn b64_decode(s: &str) -> Vec<u8> {
560 BASE64_STANDARD.decode(s).expect("failed to decode base64")
561 }
562
563 #[test]
564 fn test_generate_leaf_multihash() {
565 let namespace = Namespace::new_v0(&[1, 2, 3]).unwrap();
566 let mut data = [0xCDu8; appconsts::SHARE_SIZE];
567 data[..NS_SIZE].copy_from_slice(namespace.as_bytes());
568 let share = Share::from_raw(&data).unwrap();
569
570 let cid = share.cid().unwrap();
571 assert_eq!(cid.codec(), NMT_CODEC);
572 let hash = cid.hash();
573 assert_eq!(hash.code(), NMT_MULTIHASH_CODE);
574 assert_eq!(hash.size(), NAMESPACED_HASH_SIZE as u8);
575 let hash = NamespacedHash::try_from(hash.digest()).unwrap();
576 assert_eq!(hash.min_namespace(), *namespace);
577 assert_eq!(hash.max_namespace(), *namespace);
578 }
579}