celestia_types/
share.rs

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/// A single fixed-size chunk of data which is used to form an [`ExtendedDataSquare`].
26///
27/// All data in Celestia is split into [`Share`]s before being put into a
28/// block's data square. See [`Blob::to_shares`].
29///
30/// All shares have the fixed size of 512 bytes and the following structure:
31///
32/// ```text
33/// | Namespace | InfoByte | (optional) sequence length | data |
34/// ```
35///
36/// `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.
37///
38/// [`ExtendedDataSquare`]: crate::eds::ExtendedDataSquare
39/// [`Blob::to_shares`]: crate::Blob::to_shares
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(try_from = "RawShare", into = "RawShare")]
42pub struct Share {
43    /// A raw data of the share.
44    data: [u8; appconsts::SHARE_SIZE],
45    is_parity: bool,
46}
47
48impl Share {
49    /// Create a new [`Share`] from raw bytes.
50    ///
51    /// # Errors
52    ///
53    /// This function will return an error if the slice length isn't
54    /// [`SHARE_SIZE`] or if a namespace encoded in the share is invalid.
55    ///
56    /// # Example
57    ///
58    /// ```
59    /// use celestia_types::Share;
60    ///
61    /// let raw = [0; 512];
62    /// let share = Share::from_raw(&raw).unwrap();
63    /// ```
64    ///
65    /// [`SHARE_SIZE`]: crate::consts::appconsts::SHARE_SIZE
66    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        // validate namespace and info byte so that we can return it later without checks
72        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    /// Create a new [`Share`] within [`Namespace::PARITY_SHARE`] from raw bytes.
82    ///
83    /// # Errors
84    ///
85    /// This function will return an error if the slice length isn't [`SHARE_SIZE`].
86    ///
87    /// [`SHARE_SIZE`]: crate::consts::appconsts::SHARE_SIZE
88    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    /// Check if share is valid in given [`AppVersion`]
100    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    /// Returns true if share contains parity data.
111    pub fn is_parity(&self) -> bool {
112        self.is_parity
113    }
114
115    /// Get the [`Namespace`] the [`Share`] belongs to.
116    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    /// Return Share's `InfoByte`
125    ///
126    /// Returns `None` if share is within [`Namespace::PARITY_SHARE`].
127    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    /// For first share in a sequence, return sequence length, `None` for continuation shares
136    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    /// Get the `signer` part of share if it's first in the sequence of sparse shares and `share_version` supprots
149    /// it. Otherwise returns `None`
150    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    /// Get the payload of the share.
162    ///
163    /// Payload is the data that shares contain after all its metadata,
164    /// e.g. blob data in sparse shares.
165    ///
166    /// Returns `None` if share is within [`Namespace::PARITY_SHARE`].
167    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                // in sparse share v1, last metadata in first share is signer
173                SHARE_SIGNER_OFFSET + appconsts::SIGNER_SIZE
174            } else {
175                // otherwise last metadata in first share is sequence len
176                // (we ignore reserved bytes of compact shares, they are treated as payload)
177                SHARE_SEQUENCE_LENGTH_OFFSET + appconsts::SEQUENCE_LEN_BYTES
178            }
179        } else {
180            // or info byte in continuation shares
181            SHARE_SEQUENCE_LENGTH_OFFSET
182        };
183        Some(&self.data[start..])
184    }
185
186    /// Get the underlying share data.
187    pub fn data(&self) -> &[u8; appconsts::SHARE_SIZE] {
188        &self.data
189    }
190
191    /// Converts this [`Share`] into the raw bytes vector.
192    ///
193    /// This will include also the [`InfoByte`] and the `sequence length`.
194    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        // size is correct, so unwrap is safe
217        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        // share v0 doesn't have signer
273        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                // rest of the blob
285                &[7; BYTES_IN_SECOND][..],
286                // padding
287                &[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        // share v1 has signer only if it's the first share of the sequence
312        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                // rest of the blob
325                &[7; BYTES_IN_SECOND][..],
326                // padding
327                &[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        // v0
354        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        // v1
361        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}