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::{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/// 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    /// Check if share is valid in given [`AppVersion`]
125    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    /// Returns true if share contains parity data.
136    pub fn is_parity(&self) -> bool {
137        self.is_parity
138    }
139
140    /// Get the [`Namespace`] the [`Share`] belongs to.
141    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    /// Return Share's `InfoByte`
150    ///
151    /// Returns `None` if share is within [`Namespace::PARITY_SHARE`].
152    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    /// For first share in a sequence, return sequence length, `None` for continuation shares
161    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    /// Get the `signer` part of share if it's first in the sequence of sparse shares and `share_version` supprots
174    /// it. Otherwise returns `None`
175    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    /// Get the payload of the share.
187    ///
188    /// Payload is the data that shares contain after all its metadata,
189    /// e.g. blob data in sparse shares.
190    ///
191    /// Returns `None` if share is within [`Namespace::PARITY_SHARE`].
192    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                // in sparse share v1, last metadata in first share is signer
198                SHARE_SIGNER_OFFSET + appconsts::SIGNER_SIZE
199            } else {
200                // otherwise last metadata in first share is sequence len
201                // (we ignore reserved bytes of compact shares, they are treated as payload)
202                SHARE_SEQUENCE_LENGTH_OFFSET + appconsts::SEQUENCE_LEN_BYTES
203            }
204        } else {
205            // or info byte in continuation shares
206            SHARE_SEQUENCE_LENGTH_OFFSET
207        };
208        Some(&self.data[start..])
209    }
210
211    /// Get the underlying share data.
212    pub fn data(&self) -> &[u8; appconsts::SHARE_SIZE] {
213        &self.data
214    }
215
216    /// Converts this [`Share`] into the raw bytes vector.
217    ///
218    /// This will include also the [`InfoByte`] and the `sequence length`.
219    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    /// Create a new [`Share`] from raw bytes.
228    ///
229    /// # Errors
230    ///
231    /// This function will return an error if data provided isn't share sized or
232    /// if the namespace encoded in the share is invalid.
233    #[uniffi::constructor(name = "from_raw")]
234    pub fn uniffi_from_raw(data: &[u8]) -> UniffiResult<Self> {
235        Ok(Share::from_raw(data)?)
236    }
237
238    /// Create a new [`Share`] within [`Namespace::PARITY_SHARE`] from raw bytes.
239    ///
240    /// # Errors
241    ///
242    /// This function will return an error if provided data isn't share sized
243    #[uniffi::constructor(name = "parity")]
244    pub fn uniffi_parity(data: &[u8]) -> UniffiResult<Share> {
245        Ok(Share::parity(data)?)
246    }
247
248    /// Check if share is valid in given [`AppVersion`]
249    #[uniffi::method(name = "validate")]
250    pub fn uniffi_validate(&self, app: AppVersion) -> UniffiResult<()> {
251        Ok(self.validate(app)?)
252    }
253
254    /// Returns true if share contains parity data.
255    #[uniffi::method(name = "is_parity")]
256    pub fn uniffi_is_parity(&self) -> bool {
257        self.is_parity()
258    }
259
260    /// Get the [`Namespace`] the [`Share`] belongs to.
261    #[uniffi::method(name = "namespace")]
262    pub fn uniffi_namespace(&self) -> Namespace {
263        self.namespace()
264    }
265
266    /// Return Share's `InfoByte`
267    ///
268    /// Returns `None` if share is parity share
269    #[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    /// For first share in a sequence, return sequence length, `None` for continuation shares
275    #[uniffi::method(name = "sequence_length")]
276    pub fn uniffi_sequence_length(&self) -> Option<u32> {
277        self.sequence_length()
278    }
279
280    /// Get the `signer` part of share if it's first in the sequence of sparse shares and `share_version` supprots
281    /// it. Otherwise returns `None`
282    #[uniffi::method(name = "signer")]
283    pub fn uniffi_signer(&self) -> Option<AccAddress> {
284        self.signer()
285    }
286
287    /// Get the payload of the share.
288    ///
289    /// Payload is the data that shares contain after all its metadata,
290    /// e.g. blob data in sparse shares.
291    ///
292    /// Returns `None` if share is parity share
293    #[uniffi::method(name = "payload")]
294    pub fn uniffi_payload(&self) -> Option<Vec<u8>> {
295        self.payload().map(|p| p.to_vec())
296    }
297
298    /// Get the underlying share data.
299    #[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        // size is correct, so unwrap is safe
323        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        // share v0 doesn't have signer
379        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                // rest of the blob
391                &[7; BYTES_IN_SECOND][..],
392                // padding
393                &[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        // share v1 has signer only if it's the first share of the sequence
418        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                // rest of the blob
431                &[7; BYTES_IN_SECOND][..],
432                // padding
433                &[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        // v0
460        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        // v1
467        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}