Skip to main content

celestia_types/
share.rs

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/// A single fixed-size chunk of data which is used to form an [`ExtendedDataSquare`].
33///
34/// All data in Celestia is split into [`Share`]s before being put into a
35/// block's data square. See [`Blob::to_shares`].
36///
37/// All shares have the fixed size of 512 bytes and the following structure:
38///
39/// ```text
40/// | Namespace | InfoByte | (optional) sequence length | data |
41/// ```
42///
43/// `sequence length` is the length of the original data in bytes and is present only in the first of the shares the data was split into.
44///
45/// [`ExtendedDataSquare`]: crate::eds::ExtendedDataSquare
46/// [`Blob::to_shares`]: crate::Blob::to_shares
47#[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    /// A raw data of the share.
56    data: [u8; appconsts::SHARE_SIZE],
57    is_parity: bool,
58}
59
60/// A list of shares that were published at particular height.
61#[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    /// height the shares were published at
68    pub height: u64,
69    /// shares published
70    pub shares: Vec<Share>,
71}
72
73impl Share {
74    /// Create a new [`Share`] from raw bytes.
75    ///
76    /// # Errors
77    ///
78    /// This function will return an error if the slice length isn't
79    /// [`SHARE_SIZE`] or if a namespace encoded in the share is invalid.
80    ///
81    /// # Example
82    ///
83    /// ```
84    /// use celestia_types::Share;
85    ///
86    /// let raw = [0; 512];
87    /// let share = Share::from_raw(&raw).unwrap();
88    /// ```
89    ///
90    /// [`SHARE_SIZE`]: crate::consts::appconsts::SHARE_SIZE
91    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        // validate namespace and info byte so that we can return it later without checks
97        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    /// Create a new [`Share`] within [`Namespace::PARITY_SHARE`] from raw bytes.
107    ///
108    /// # Errors
109    ///
110    /// This function will return an error if the slice length isn't [`SHARE_SIZE`].
111    ///
112    /// [`SHARE_SIZE`]: crate::consts::appconsts::SHARE_SIZE
113    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    /// Returns true if share contains parity data.
125    pub fn is_parity(&self) -> bool {
126        self.is_parity
127    }
128
129    /// Get the [`Namespace`] the [`Share`] belongs to.
130    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    /// Return Share's `InfoByte`
139    ///
140    /// Returns `None` if share is within [`Namespace::PARITY_SHARE`].
141    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    /// For first share in a sequence, return sequence length, `None` for continuation shares
150    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    /// Get the `signer` part of share if it's first in the sequence of sparse shares and `share_version` supprots
163    /// it. Otherwise returns `None`
164    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    /// Get the payload of the share.
176    ///
177    /// Payload is the data that shares contain after all its metadata,
178    /// e.g. blob data in sparse shares.
179    ///
180    /// Returns `None` if share is within [`Namespace::PARITY_SHARE`].
181    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                // in sparse share v1, last metadata in first share is signer
187                SHARE_SIGNER_OFFSET + appconsts::SIGNER_SIZE
188            } else {
189                // otherwise last metadata in first share is sequence len
190                // (we ignore reserved bytes of compact shares, they are treated as payload)
191                SHARE_SEQUENCE_LENGTH_OFFSET + appconsts::SEQUENCE_LEN_BYTES
192            }
193        } else {
194            // or info byte in continuation shares
195            SHARE_SEQUENCE_LENGTH_OFFSET
196        };
197        Some(&self.data[start..])
198    }
199
200    /// Get the underlying share data.
201    pub fn data(&self) -> &[u8; appconsts::SHARE_SIZE] {
202        &self.data
203    }
204
205    /// Converts this [`Share`] into the raw bytes vector.
206    ///
207    /// This will include also the [`InfoByte`] and the `sequence length`.
208    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    /// Create a new [`Share`] from raw bytes.
217    ///
218    /// # Errors
219    ///
220    /// This function will return an error if data provided isn't share sized or
221    /// if the namespace encoded in the share is invalid.
222    #[uniffi::constructor(name = "from_raw")]
223    pub fn uniffi_from_raw(data: &[u8]) -> UniffiResult<Self> {
224        Ok(Share::from_raw(data)?)
225    }
226
227    /// Create a new [`Share`] within [`Namespace::PARITY_SHARE`] from raw bytes.
228    ///
229    /// # Errors
230    ///
231    /// This function will return an error if provided data isn't share sized
232    #[uniffi::constructor(name = "parity")]
233    pub fn uniffi_parity(data: &[u8]) -> UniffiResult<Share> {
234        Ok(Share::parity(data)?)
235    }
236
237    /// Returns true if share contains parity data.
238    #[uniffi::method(name = "is_parity")]
239    pub fn uniffi_is_parity(&self) -> bool {
240        self.is_parity()
241    }
242
243    /// Get the [`Namespace`] the [`Share`] belongs to.
244    #[uniffi::method(name = "namespace")]
245    pub fn uniffi_namespace(&self) -> Namespace {
246        self.namespace()
247    }
248
249    /// Return Share's `InfoByte`
250    ///
251    /// Returns `None` if share is parity share
252    #[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    /// For first share in a sequence, return sequence length, `None` for continuation shares
258    #[uniffi::method(name = "sequence_length")]
259    pub fn uniffi_sequence_length(&self) -> Option<u32> {
260        self.sequence_length()
261    }
262
263    /// Get the `signer` part of share if it's first in the sequence of sparse shares and `share_version` supprots
264    /// it. Otherwise returns `None`
265    #[uniffi::method(name = "signer")]
266    pub fn uniffi_signer(&self) -> Option<AccAddress> {
267        self.signer()
268    }
269
270    /// Get the payload of the share.
271    ///
272    /// Payload is the data that shares contain after all its metadata,
273    /// e.g. blob data in sparse shares.
274    ///
275    /// Returns `None` if share is parity share
276    #[uniffi::method(name = "payload")]
277    pub fn uniffi_payload(&self) -> Option<Vec<u8>> {
278        self.payload().map(|p| p.to_vec())
279    }
280
281    /// Get the underlying share data.
282    #[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        // size is correct, so unwrap is safe
306        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        // share v0 doesn't have signer
361        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                // rest of the blob
373                &[7; BYTES_IN_SECOND][..],
374                // padding
375                &[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        // share v1 has signer only if it's the first share of the sequence
400        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                // rest of the blob
413                &[7; BYTES_IN_SECOND][..],
414                // padding
415                &[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}