1use blockstore::block::{Block, CidError};
2use cid::CidGeneric;
3use multihash::Multihash;
4use nmt_rs::simple_merkle::tree::MerkleHash;
5use nmt_rs::NamespaceMerkleHasher;
6use serde::{Deserialize, Serialize};
7
8use crate::consts::appconsts::{self, AppVersion};
9use crate::nmt::{
10 Namespace, NamespacedSha2Hasher, NMT_CODEC, NMT_ID_SIZE, NMT_MULTIHASH_CODE, NS_SIZE,
11};
12use crate::state::AccAddress;
13use crate::{Error, Result};
14
15mod info_byte;
16mod proof;
17
18pub use celestia_proto::shwap::Share as RawShare;
19pub use info_byte::InfoByte;
20pub use proof::ShareProof;
21
22const SHARE_SEQUENCE_LENGTH_OFFSET: usize = NS_SIZE + appconsts::SHARE_INFO_BYTES;
23const SHARE_SIGNER_OFFSET: usize = SHARE_SEQUENCE_LENGTH_OFFSET + appconsts::SEQUENCE_LEN_BYTES;
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(try_from = "RawShare", into = "RawShare")]
42pub struct Share {
43 data: [u8; appconsts::SHARE_SIZE],
45 is_parity: bool,
46}
47
48impl Share {
49 pub fn from_raw(data: &[u8]) -> Result<Self> {
67 if data.len() != appconsts::SHARE_SIZE {
68 return Err(Error::InvalidShareSize(data.len()));
69 }
70
71 Namespace::from_raw(&data[..NS_SIZE])?;
73 InfoByte::from_raw(data[NS_SIZE])?;
74
75 Ok(Share {
76 data: data.try_into().unwrap(),
77 is_parity: false,
78 })
79 }
80
81 pub fn parity(data: &[u8]) -> Result<Share> {
89 if data.len() != appconsts::SHARE_SIZE {
90 return Err(Error::InvalidShareSize(data.len()));
91 }
92
93 Ok(Share {
94 data: data.try_into().unwrap(),
95 is_parity: true,
96 })
97 }
98
99 pub fn validate(&self, app: AppVersion) -> Result<()> {
101 if self.info_byte().is_some_and(|info| {
102 info.version() == appconsts::SHARE_VERSION_ONE && app < AppVersion::V3
103 }) {
104 Err(Error::UnsupportedShareVersion(appconsts::SHARE_VERSION_ONE))
105 } else {
106 Ok(())
107 }
108 }
109
110 pub fn is_parity(&self) -> bool {
112 self.is_parity
113 }
114
115 pub fn namespace(&self) -> Namespace {
117 if !self.is_parity {
118 Namespace::new_unchecked(self.data[..NS_SIZE].try_into().unwrap())
119 } else {
120 Namespace::PARITY_SHARE
121 }
122 }
123
124 pub fn info_byte(&self) -> Option<InfoByte> {
128 if !self.is_parity() {
129 Some(InfoByte::from_raw_unchecked(self.data[NS_SIZE]))
130 } else {
131 None
132 }
133 }
134
135 pub fn sequence_length(&self) -> Option<u32> {
137 if self.info_byte()?.is_sequence_start() {
138 let sequence_length_bytes = &self.data[SHARE_SEQUENCE_LENGTH_OFFSET
139 ..SHARE_SEQUENCE_LENGTH_OFFSET + appconsts::SEQUENCE_LEN_BYTES];
140 Some(u32::from_be_bytes(
141 sequence_length_bytes.try_into().unwrap(),
142 ))
143 } else {
144 None
145 }
146 }
147
148 pub fn signer(&self) -> Option<AccAddress> {
151 let info_byte = self.info_byte()?;
152 if info_byte.is_sequence_start() && info_byte.version() == appconsts::SHARE_VERSION_ONE {
153 let signer_bytes =
154 &self.data[SHARE_SIGNER_OFFSET..SHARE_SIGNER_OFFSET + appconsts::SIGNER_SIZE];
155 Some(AccAddress::try_from(signer_bytes).expect("must have correct size"))
156 } else {
157 None
158 }
159 }
160
161 pub fn payload(&self) -> Option<&[u8]> {
168 let info_byte = self.info_byte()?;
169
170 let start = if info_byte.is_sequence_start() {
171 if info_byte.version() == appconsts::SHARE_VERSION_ONE {
172 SHARE_SIGNER_OFFSET + appconsts::SIGNER_SIZE
174 } else {
175 SHARE_SEQUENCE_LENGTH_OFFSET + appconsts::SEQUENCE_LEN_BYTES
178 }
179 } else {
180 SHARE_SEQUENCE_LENGTH_OFFSET
182 };
183 Some(&self.data[start..])
184 }
185
186 pub fn data(&self) -> &[u8; appconsts::SHARE_SIZE] {
188 &self.data
189 }
190
191 pub fn to_vec(&self) -> Vec<u8> {
195 self.as_ref().to_vec()
196 }
197}
198
199impl AsRef<[u8]> for Share {
200 fn as_ref(&self) -> &[u8] {
201 &self.data
202 }
203}
204
205impl AsMut<[u8]> for Share {
206 fn as_mut(&mut self) -> &mut [u8] {
207 &mut self.data
208 }
209}
210
211impl Block<NMT_ID_SIZE> for Share {
212 fn cid(&self) -> Result<CidGeneric<NMT_ID_SIZE>, CidError> {
213 let hasher = NamespacedSha2Hasher::with_ignore_max_ns(true);
214 let digest = hasher.hash_leaf(self.as_ref()).iter().collect::<Vec<_>>();
215
216 let mh = Multihash::wrap(NMT_MULTIHASH_CODE, &digest).unwrap();
218
219 Ok(CidGeneric::new_v1(NMT_CODEC, mh))
220 }
221
222 fn data(&self) -> &[u8] {
223 &self.data
224 }
225}
226
227impl TryFrom<RawShare> for Share {
228 type Error = Error;
229
230 fn try_from(value: RawShare) -> Result<Self, Self::Error> {
231 Share::from_raw(&value.data)
232 }
233}
234
235impl From<Share> for RawShare {
236 fn from(value: Share) -> Self {
237 RawShare {
238 data: value.to_vec(),
239 }
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use crate::consts::appconsts::AppVersion;
247 use crate::nmt::{NamespaceProof, NamespacedHash, NAMESPACED_HASH_SIZE};
248 use crate::Blob;
249 use base64::prelude::*;
250
251 #[cfg(target_arch = "wasm32")]
252 use wasm_bindgen_test::wasm_bindgen_test as test;
253
254 #[test]
255 fn share_v0_structure() {
256 let ns = Namespace::new_v0(b"foo").unwrap();
257 let blob = Blob::new(ns, vec![7; 512], AppVersion::V2).unwrap();
258
259 let shares = blob.to_shares().unwrap();
260
261 assert_eq!(shares.len(), 2);
262
263 assert_eq!(shares[0].namespace(), ns);
264 assert_eq!(shares[1].namespace(), ns);
265
266 assert_eq!(shares[0].info_byte().unwrap().version(), 0);
267 assert_eq!(shares[1].info_byte().unwrap().version(), 0);
268
269 assert!(shares[0].info_byte().unwrap().is_sequence_start());
270 assert!(!shares[1].info_byte().unwrap().is_sequence_start());
271
272 assert!(shares[0].signer().is_none());
274 assert!(shares[1].signer().is_none());
275
276 const BYTES_IN_SECOND: usize = 512 - appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE;
277 assert_eq!(
278 shares[0].payload().unwrap(),
279 &[7; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE]
280 );
281 assert_eq!(
282 shares[1].payload().unwrap(),
283 &[
284 &[7; BYTES_IN_SECOND][..],
286 &[0; appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE - BYTES_IN_SECOND][..]
288 ]
289 .concat()
290 );
291 }
292
293 #[test]
294 fn share_v1_structure() {
295 let ns = Namespace::new_v0(b"foo").unwrap();
296 let blob = Blob::new_with_signer(ns, vec![7; 512], [5; 20].into(), AppVersion::V3).unwrap();
297
298 let shares = blob.to_shares().unwrap();
299
300 assert_eq!(shares.len(), 2);
301
302 assert_eq!(shares[0].namespace(), ns);
303 assert_eq!(shares[1].namespace(), ns);
304
305 assert_eq!(shares[0].info_byte().unwrap().version(), 1);
306 assert_eq!(shares[1].info_byte().unwrap().version(), 1);
307
308 assert!(shares[0].info_byte().unwrap().is_sequence_start());
309 assert!(!shares[1].info_byte().unwrap().is_sequence_start());
310
311 assert_eq!(shares[0].signer().unwrap(), [5; 20].into());
313 assert!(shares[1].signer().is_none());
314
315 const BYTES_IN_SECOND: usize =
316 512 - appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE + appconsts::SIGNER_SIZE;
317 assert_eq!(
318 shares[0].payload().unwrap(),
319 &[7; appconsts::FIRST_SPARSE_SHARE_CONTENT_SIZE - appconsts::SIGNER_SIZE]
320 );
321 assert_eq!(
322 shares[1].payload().unwrap(),
323 &[
324 &[7; BYTES_IN_SECOND][..],
326 &[0; appconsts::CONTINUATION_SPARSE_SHARE_CONTENT_SIZE - BYTES_IN_SECOND][..]
328 ]
329 .concat()
330 );
331 }
332
333 #[test]
334 fn share_should_have_correct_len() {
335 Share::from_raw(&[0; 0]).unwrap_err();
336 Share::from_raw(&[0; 100]).unwrap_err();
337 Share::from_raw(&[0; appconsts::SHARE_SIZE - 1]).unwrap_err();
338 Share::from_raw(&[0; appconsts::SHARE_SIZE + 1]).unwrap_err();
339 Share::from_raw(&[0; 2 * appconsts::SHARE_SIZE]).unwrap_err();
340
341 Share::from_raw(&vec![0; appconsts::SHARE_SIZE]).unwrap();
342 }
343
344 #[test]
345 fn share_validate() {
346 let app_versions = [
347 AppVersion::V1,
348 AppVersion::V2,
349 AppVersion::V3,
350 AppVersion::latest(),
351 ];
352
353 let share = Share::from_raw(&[0; appconsts::SHARE_SIZE]).unwrap();
355
356 for app in app_versions {
357 share.validate(app).unwrap();
358 }
359
360 let mut data = [0; appconsts::SHARE_SIZE];
362 data[NS_SIZE] = InfoByte::new(appconsts::SHARE_VERSION_ONE, false)
363 .unwrap()
364 .as_u8();
365 let share = Share::from_raw(&data).unwrap();
366
367 for app in app_versions {
368 if app < AppVersion::V3 {
369 share.validate(app).unwrap_err();
370 } else {
371 share.validate(app).unwrap();
372 }
373 }
374 }
375
376 #[test]
377 fn decode_presence_proof() {
378 let blob_get_proof_response = r#"{
379 "start": 1,
380 "end": 2,
381 "nodes": [
382 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA+poCQOx7UzVkteV9DgcA6g29ZXXOp0hYZb67hoNkFP",
383 "/////////////////////////////////////////////////////////////////////////////8PbbPgQcFSaW2J/BWiJqrCoj6K4g/UUd0Y9dadwqrz+"
384 ]
385 }"#;
386
387 let proof: NamespaceProof =
388 serde_json::from_str(blob_get_proof_response).expect("can not parse proof");
389
390 assert!(!proof.is_of_absence());
391
392 let sibling = &proof.siblings()[0];
393 let min_ns_bytes = &sibling.min_namespace().0[..];
394 let max_ns_bytes = &sibling.max_namespace().0[..];
395 let hash_bytes = &sibling.hash()[..];
396 assert_eq!(
397 min_ns_bytes,
398 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=")
399 );
400 assert_eq!(
401 max_ns_bytes,
402 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=")
403 );
404 assert_eq!(
405 hash_bytes,
406 b64_decode("D6mgJA7HtTNWS15X0OBwDqDb1ldc6nSFhlvruGg2QU8=")
407 );
408
409 let sibling = &proof.siblings()[1];
410 let min_ns_bytes = &sibling.min_namespace().0[..];
411 let max_ns_bytes = &sibling.max_namespace().0[..];
412 let hash_bytes = &sibling.hash()[..];
413 assert_eq!(
414 min_ns_bytes,
415 b64_decode("//////////////////////////////////////8=")
416 );
417 assert_eq!(
418 max_ns_bytes,
419 b64_decode("//////////////////////////////////////8=")
420 );
421 assert_eq!(
422 hash_bytes,
423 b64_decode("w9ts+BBwVJpbYn8FaImqsKiPoriD9RR3Rj11p3CqvP4=")
424 );
425 }
426
427 #[test]
428 fn decode_absence_proof() {
429 let blob_get_proof_response = r#"{
430 "start": 1,
431 "end": 2,
432 "nodes": [
433 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABD+sL4GAQk9mj+ejzHmHjUJEyemkpExb+S5aEDtmuHEq",
434 "/////////////////////////////////////////////////////////////////////////////zgUEBW/wWmCfnwXfalgqMfK9sMy168y3XRzdwY1jpZY"
435 ],
436 "leaf_hash": "AAAAAAAAAAAAAAAAAAAAAAAAAJLVUf6krS8362EAAAAAAAAAAAAAAAAAAAAAAAAAktVR/qStLzfrYeEAWUHOa+lE38pJyHstgGaqi9RXPhZtzUscK7iTUbQS",
437 "is_max_namespace_ignored": true
438 }"#;
439
440 let proof: NamespaceProof =
441 serde_json::from_str(blob_get_proof_response).expect("can not parse proof");
442
443 assert!(proof.is_of_absence());
444
445 let sibling = &proof.siblings()[0];
446 let min_ns_bytes = &sibling.min_namespace().0[..];
447 let max_ns_bytes = &sibling.max_namespace().0[..];
448 let hash_bytes = &sibling.hash()[..];
449 assert_eq!(
450 min_ns_bytes,
451 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=")
452 );
453 assert_eq!(
454 max_ns_bytes,
455 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=")
456 );
457 assert_eq!(
458 hash_bytes,
459 b64_decode("P6wvgYBCT2aP56PMeYeNQkTJ6aSkTFv5LloQO2a4cSo=")
460 );
461
462 let sibling = &proof.siblings()[1];
463 let min_ns_bytes = &sibling.min_namespace().0[..];
464 let max_ns_bytes = &sibling.max_namespace().0[..];
465 let hash_bytes = &sibling.hash()[..];
466 assert_eq!(
467 min_ns_bytes,
468 b64_decode("//////////////////////////////////////8=")
469 );
470 assert_eq!(
471 max_ns_bytes,
472 b64_decode("//////////////////////////////////////8=")
473 );
474 assert_eq!(
475 hash_bytes,
476 b64_decode("OBQQFb/BaYJ+fBd9qWCox8r2wzLXrzLddHN3BjWOllg=")
477 );
478
479 let nmt_rs::NamespaceProof::AbsenceProof {
480 leaf: Some(leaf), ..
481 } = &*proof
482 else {
483 unreachable!();
484 };
485
486 let min_ns_bytes = &leaf.min_namespace().0[..];
487 let max_ns_bytes = &leaf.max_namespace().0[..];
488 let hash_bytes = &leaf.hash()[..];
489 assert_eq!(
490 min_ns_bytes,
491 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAJLVUf6krS8362E=")
492 );
493 assert_eq!(
494 max_ns_bytes,
495 b64_decode("AAAAAAAAAAAAAAAAAAAAAAAAAJLVUf6krS8362E=")
496 );
497 assert_eq!(
498 hash_bytes,
499 b64_decode("4QBZQc5r6UTfyknIey2AZqqL1Fc+Fm3NSxwruJNRtBI=")
500 );
501 }
502
503 fn b64_decode(s: &str) -> Vec<u8> {
504 BASE64_STANDARD.decode(s).expect("failed to decode base64")
505 }
506
507 #[test]
508 fn test_generate_leaf_multihash() {
509 let namespace = Namespace::new_v0(&[1, 2, 3]).unwrap();
510 let mut data = [0xCDu8; appconsts::SHARE_SIZE];
511 data[..NS_SIZE].copy_from_slice(namespace.as_bytes());
512 let share = Share::from_raw(&data).unwrap();
513
514 let cid = share.cid().unwrap();
515 assert_eq!(cid.codec(), NMT_CODEC);
516 let hash = cid.hash();
517 assert_eq!(hash.code(), NMT_MULTIHASH_CODE);
518 assert_eq!(hash.size(), NAMESPACED_HASH_SIZE as u8);
519 let hash = NamespacedHash::try_from(hash.digest()).unwrap();
520 assert_eq!(hash.min_namespace(), *namespace);
521 assert_eq!(hash.max_namespace(), *namespace);
522 }
523}