Skip to main content

celestia_types/
data_availability_header.rs

1use std::ops::RangeInclusive;
2
3use celestia_proto::celestia::core::v1::da::DataAvailabilityHeader as RawDataAvailabilityHeader;
4use celestia_proto::celestia::core::v1::proof::RowProof as RawRowProof;
5use serde::{Deserialize, Serialize};
6use sha2::Sha256;
7use tendermint::merkle::simple_hash_from_byte_vectors;
8use tendermint_proto::Protobuf;
9#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
10use wasm_bindgen::prelude::*;
11
12use crate::consts::data_availability_header::MIN_EXTENDED_SQUARE_WIDTH;
13use crate::eds::AxisType;
14use crate::hash::Hash;
15use crate::nmt::{Namespace, NamespacedHash, NamespacedHashExt, NamespacedSha2Hasher};
16use crate::{
17    Error, ExtendedDataSquare, MerkleProof, Result, ValidateBasic, ValidationError,
18    bail_validation, bail_verification, validation_error,
19};
20
21/// Header with commitments of the data availability.
22///
23/// It consists of the root hashes of the merkle trees created from each
24/// row and column of the [`ExtendedDataSquare`]. Those are used to prove
25/// the inclusion of the data in a block.
26///
27/// The hash of this header is a hash of all rows and columns and thus a
28/// data commitment of the block.
29///
30/// # Example
31///
32/// ```no_run
33/// # use celestia_types::{ExtendedHeader, Height, Share};
34/// # use celestia_types::nmt::{Namespace, NamespaceProof};
35/// # fn extended_header() -> ExtendedHeader {
36/// #     unimplemented!();
37/// # }
38/// # fn shares_with_proof(_: u64, _: &Namespace) -> (Vec<Share>, NamespaceProof) {
39/// #     unimplemented!();
40/// # }
41/// // fetch the block header and data for your namespace
42/// let namespace = Namespace::new_v0(&[1, 2, 3, 4]).unwrap();
43/// let eh = extended_header();
44/// let (shares, proof) = shares_with_proof(eh.height(), &namespace);
45///
46/// // get the data commitment for a given row
47/// let dah = eh.dah;
48/// let root = dah.row_root(0).unwrap();
49///
50/// // verify a proof of the inclusion of the shares
51/// assert!(proof.verify_complete_namespace(&root, &shares, *namespace).is_ok());
52/// ```
53///
54/// [`ExtendedDataSquare`]: crate::eds::ExtendedDataSquare
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(
57    try_from = "RawDataAvailabilityHeader",
58    into = "RawDataAvailabilityHeader"
59)]
60#[cfg_attr(
61    all(feature = "wasm-bindgen", target_arch = "wasm32"),
62    wasm_bindgen(inspectable)
63)]
64pub struct DataAvailabilityHeader {
65    /// Merkle roots of the [`ExtendedDataSquare`] rows.
66    row_roots: Vec<NamespacedHash>,
67    /// Merkle roots of the [`ExtendedDataSquare`] columns.
68    column_roots: Vec<NamespacedHash>,
69}
70
71impl DataAvailabilityHeader {
72    /// Create new [`DataAvailabilityHeader`].
73    pub fn new(row_roots: Vec<NamespacedHash>, column_roots: Vec<NamespacedHash>) -> Result<Self> {
74        let dah = DataAvailabilityHeader::new_unchecked(row_roots, column_roots);
75        dah.validate_basic()?;
76        Ok(dah)
77    }
78
79    /// Create new non-validated [`DataAvailabilityHeader`].
80    ///
81    /// [`DataAvailabilityHeader::validate_basic`] can be used to check valitidy later on.
82    pub fn new_unchecked(
83        row_roots: Vec<NamespacedHash>,
84        column_roots: Vec<NamespacedHash>,
85    ) -> Self {
86        DataAvailabilityHeader {
87            row_roots,
88            column_roots,
89        }
90    }
91
92    /// Create a DataAvailabilityHeader by computing roots of a given [`ExtendedDataSquare`].
93    pub fn from_eds(eds: &ExtendedDataSquare) -> Self {
94        let square_width = eds.square_width();
95
96        let mut dah = DataAvailabilityHeader {
97            row_roots: Vec::with_capacity(square_width.into()),
98            column_roots: Vec::with_capacity(square_width.into()),
99        };
100
101        for i in 0..square_width {
102            let row_root = eds
103                .row_nmt(i)
104                .expect("EDS validated on construction")
105                .root();
106            dah.row_roots.push(row_root);
107
108            let column_root = eds
109                .column_nmt(i)
110                .expect("EDS validated on construction")
111                .root();
112            dah.column_roots.push(column_root);
113        }
114
115        dah
116    }
117
118    /// Get the root from an axis at the given index.
119    pub fn root(&self, axis: AxisType, index: u16) -> Option<NamespacedHash> {
120        match axis {
121            AxisType::Col => self.column_root(index),
122            AxisType::Row => self.row_root(index),
123        }
124    }
125
126    /// Merkle roots of the [`ExtendedDataSquare`] rows.
127    ///
128    /// [`ExtendedDataSquare`]: crate::eds::ExtendedDataSquare
129    pub fn row_roots(&self) -> &[NamespacedHash] {
130        &self.row_roots
131    }
132
133    /// Merkle roots of the [`ExtendedDataSquare`] columns.
134    ///
135    /// [`ExtendedDataSquare`]: crate::eds::ExtendedDataSquare
136    pub fn column_roots(&self) -> &[NamespacedHash] {
137        &self.column_roots
138    }
139
140    /// Get a root of the row with the given index.
141    pub fn row_root(&self, row: u16) -> Option<NamespacedHash> {
142        let row = usize::from(row);
143        self.row_roots.get(row).cloned()
144    }
145
146    /// Check if row with given index contains provided namespace.
147    pub fn row_contains(&self, row: u16, namespace: Namespace) -> Result<bool> {
148        let row_root = self
149            .row_root(row)
150            .ok_or(Error::IndexOutOfRange(row as usize, self.row_roots.len()))?;
151        Ok(row_root.contains::<NamespacedSha2Hasher>(*namespace))
152    }
153
154    /// Get the a root of the column with the given index.
155    pub fn column_root(&self, column: u16) -> Option<NamespacedHash> {
156        let column = usize::from(column);
157        self.column_roots.get(column).cloned()
158    }
159
160    /// Check if column with given index contains provided namespace.
161    pub fn column_contains(&self, column: u16, namespace: Namespace) -> Result<bool> {
162        let column_root = self.column_root(column).ok_or(Error::IndexOutOfRange(
163            column as usize,
164            self.column_roots.len(),
165        ))?;
166        Ok(column_root.contains::<NamespacedSha2Hasher>(*namespace))
167    }
168
169    /// Compute the combined hash of all rows and columns.
170    ///
171    /// This is the data commitment for the block.
172    ///
173    /// # Example
174    ///
175    /// ```
176    /// # use celestia_types::ExtendedHeader;
177    /// # fn get_extended_header() -> ExtendedHeader {
178    /// #   let s = include_str!("../test_data/chain1/extended_header_block_1.json");
179    /// #   serde_json::from_str(s).unwrap()
180    /// # }
181    /// let eh = get_extended_header();
182    /// let dah = eh.dah;
183    ///
184    /// assert_eq!(dah.hash(), eh.header.data_hash.unwrap());
185    /// ```
186    pub fn hash(&self) -> Hash {
187        let all_roots: Vec<_> = self
188            .row_roots
189            .iter()
190            .chain(self.column_roots.iter())
191            .map(|root| root.to_array())
192            .collect();
193
194        Hash::Sha256(simple_hash_from_byte_vectors::<Sha256>(&all_roots))
195    }
196
197    /// Get the size of the [`ExtendedDataSquare`] for which this header was built.
198    ///
199    /// [`ExtendedDataSquare`]: crate::eds::ExtendedDataSquare
200    pub fn square_width(&self) -> u16 {
201        // `validate_basic` checks that rows num = cols num
202        self.row_roots
203            .len()
204            .try_into()
205            // On validated DAH this never happens
206            .expect("len is bigger than u16::MAX")
207    }
208
209    /// Get the [`RowProof`] for given rows.
210    pub fn row_proof(&self, rows: RangeInclusive<u16>) -> Result<RowProof> {
211        let all_roots: Vec<_> = self
212            .row_roots
213            .iter()
214            .chain(self.column_roots.iter())
215            .map(|root| root.to_array())
216            .collect();
217
218        let start_row = *rows.start();
219        let end_row = *rows.end();
220        let mut proofs = Vec::with_capacity(rows.len());
221        let mut row_roots = Vec::with_capacity(rows.len());
222
223        for idx in rows {
224            proofs.push(MerkleProof::new(idx as usize, &all_roots)?.0);
225            let row = self
226                .row_root(idx)
227                .ok_or(Error::IndexOutOfRange(idx as usize, self.row_roots.len()))?;
228            row_roots.push(row);
229        }
230
231        Ok(RowProof {
232            proofs,
233            row_roots,
234            start_row,
235            end_row,
236        })
237    }
238}
239
240#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
241#[wasm_bindgen]
242impl DataAvailabilityHeader {
243    /// Merkle roots of the [`ExtendedDataSquare`] rows.
244    #[wasm_bindgen(js_name = rowRoots)]
245    pub fn js_row_roots(&self) -> Result<js_sys::Array, serde_wasm_bindgen::Error> {
246        self.row_roots()
247            .iter()
248            .map(|h| serde_wasm_bindgen::to_value(&h))
249            .collect()
250    }
251
252    /// Merkle roots of the [`ExtendedDataSquare`] columns.
253    #[wasm_bindgen(js_name = columnRoots)]
254    pub fn js_column_roots(&self) -> Result<js_sys::Array, serde_wasm_bindgen::Error> {
255        self.column_roots()
256            .iter()
257            .map(|h| serde_wasm_bindgen::to_value(&h))
258            .collect()
259    }
260
261    /// Get a root of the row with the given index.
262    #[wasm_bindgen(js_name = rowRoot)]
263    pub fn js_row_root(&self, row: u16) -> Result<JsValue, serde_wasm_bindgen::Error> {
264        serde_wasm_bindgen::to_value(&self.row_root(row))
265    }
266
267    /// Get the a root of the column with the given index.
268    #[wasm_bindgen(js_name = columnRoot)]
269    pub fn js_column_root(&self, column: u16) -> Result<JsValue, serde_wasm_bindgen::Error> {
270        serde_wasm_bindgen::to_value(&self.column_root(column))
271    }
272
273    /// Compute the combined hash of all rows and columns.
274    ///
275    /// This is the data commitment for the block.
276    #[wasm_bindgen(js_name = hash)]
277    pub fn js_hash(&self) -> Result<JsValue, serde_wasm_bindgen::Error> {
278        serde_wasm_bindgen::to_value(&self.hash())
279    }
280
281    /// Get the size of the [`ExtendedDataSquare`] for which this header was built.
282    #[wasm_bindgen(js_name = squareWidth)]
283    pub fn js_square_width(&self) -> u16 {
284        self.square_width()
285    }
286}
287
288impl Protobuf<RawDataAvailabilityHeader> for DataAvailabilityHeader {}
289
290impl TryFrom<RawDataAvailabilityHeader> for DataAvailabilityHeader {
291    type Error = Error;
292
293    fn try_from(value: RawDataAvailabilityHeader) -> Result<Self, Self::Error> {
294        Ok(DataAvailabilityHeader {
295            row_roots: value
296                .row_roots
297                .iter()
298                .map(|bytes| NamespacedHash::from_raw(bytes))
299                .collect::<Result<Vec<_>>>()?,
300            column_roots: value
301                .column_roots
302                .iter()
303                .map(|bytes| NamespacedHash::from_raw(bytes))
304                .collect::<Result<Vec<_>>>()?,
305        })
306    }
307}
308
309impl From<DataAvailabilityHeader> for RawDataAvailabilityHeader {
310    fn from(value: DataAvailabilityHeader) -> RawDataAvailabilityHeader {
311        RawDataAvailabilityHeader {
312            row_roots: value.row_roots.iter().map(|hash| hash.to_vec()).collect(),
313            column_roots: value
314                .column_roots
315                .iter()
316                .map(|hash| hash.to_vec())
317                .collect(),
318        }
319    }
320}
321
322impl ValidateBasic for DataAvailabilityHeader {
323    fn validate_basic(&self) -> Result<(), ValidationError> {
324        if self.column_roots.len() != self.row_roots.len() {
325            bail_validation!(
326                "column_roots len ({}) != row_roots len ({})",
327                self.column_roots.len(),
328                self.row_roots.len(),
329            )
330        }
331
332        if self.row_roots.len() < MIN_EXTENDED_SQUARE_WIDTH {
333            bail_validation!(
334                "row_roots len ({}) < minimum ({})",
335                self.row_roots.len(),
336                MIN_EXTENDED_SQUARE_WIDTH,
337            )
338        }
339
340        Ok(())
341    }
342}
343
344/// A proof of inclusion of a range of row roots in a [`DataAvailabilityHeader`].
345#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
346#[serde(try_from = "RawRowProof", into = "RawRowProof")]
347pub struct RowProof {
348    row_roots: Vec<NamespacedHash>,
349    proofs: Vec<MerkleProof>,
350    start_row: u16,
351    end_row: u16,
352}
353
354impl RowProof {
355    /// Get the list of row roots this proof proves.
356    pub fn row_roots(&self) -> &[NamespacedHash] {
357        &self.row_roots
358    }
359
360    /// Get the inclusion proofs of each row root in the data availability header.
361    pub fn proofs(&self) -> &[MerkleProof] {
362        &self.proofs
363    }
364
365    /// Verify the proof against the hash of [`DataAvailabilityHeader`], proving
366    /// the inclusion of rows.
367    ///
368    /// # Errors
369    ///
370    /// This function will return an error if:
371    ///  - the proof is malformed. Number of proofs, row roots and the span between starting and ending row need to match.
372    ///  - the verification of any inner merkle proof fails
373    ///
374    /// # Example
375    ///
376    /// ```
377    /// # use celestia_types::ExtendedHeader;
378    /// # fn get_extended_header() -> ExtendedHeader {
379    /// #   let s = include_str!("../test_data/chain1/extended_header_block_1.json");
380    /// #   serde_json::from_str(s).unwrap()
381    /// # }
382    /// let eh = get_extended_header();
383    /// let dah = eh.dah;
384    ///
385    /// let proof = dah.row_proof(0..=1).unwrap();
386    ///
387    /// assert!(proof.verify(dah.hash()).is_ok());
388    /// ```
389    pub fn verify(&self, root: Hash) -> Result<()> {
390        if self.row_roots.len() != self.proofs.len() {
391            bail_verification!("invalid row proof: row_roots.len() != proofs.len()");
392        }
393
394        if self.end_row < self.start_row {
395            bail_verification!(
396                "start_row ({}) > end_row ({})",
397                self.start_row,
398                self.end_row
399            );
400        }
401
402        let length = self.end_row - self.start_row + 1;
403        if length as usize != self.proofs.len() {
404            bail_verification!(
405                "length based on start_row and end_row ({}) != length of proofs ({})",
406                length,
407                self.proofs.len()
408            );
409        }
410
411        let Hash::Sha256(root) = root else {
412            bail_verification!("empty hash");
413        };
414
415        for (row_root, proof) in self.row_roots.iter().zip(self.proofs.iter()) {
416            proof.verify(row_root.to_array(), root)?;
417        }
418
419        Ok(())
420    }
421}
422
423impl Protobuf<RawRowProof> for RowProof {}
424
425impl TryFrom<RawRowProof> for RowProof {
426    type Error = Error;
427
428    fn try_from(value: RawRowProof) -> Result<Self> {
429        Ok(Self {
430            row_roots: value
431                .row_roots
432                .into_iter()
433                .map(|hash| NamespacedHash::from_raw(&hash))
434                .collect::<Result<_>>()?,
435            proofs: value
436                .proofs
437                .into_iter()
438                .map(TryInto::try_into)
439                .collect::<Result<_>>()?,
440            start_row: value
441                .start_row
442                .try_into()
443                .map_err(|_| validation_error!("start_row ({}) exceeds u16", value.start_row))?,
444            end_row: value
445                .end_row
446                .try_into()
447                .map_err(|_| validation_error!("end_row ({}) exceeds u16", value.end_row))?,
448        })
449    }
450}
451
452impl From<RowProof> for RawRowProof {
453    fn from(value: RowProof) -> Self {
454        Self {
455            row_roots: value
456                .row_roots
457                .into_iter()
458                .map(|hash| hash.to_vec())
459                .collect(),
460            proofs: value.proofs.into_iter().map(Into::into).collect(),
461            start_row: value.start_row as u32,
462            end_row: value.end_row as u32,
463            root: vec![],
464        }
465    }
466}
467
468#[cfg(test)]
469mod tests {
470    use crate::nmt::Namespace;
471
472    use super::*;
473
474    #[cfg(target_arch = "wasm32")]
475    use wasm_bindgen_test::wasm_bindgen_test as test;
476
477    fn sample_dah() -> DataAvailabilityHeader {
478        serde_json::from_str(r#"{
479          "row_roots": [
480            "//////////////////////////////////////7//////////////////////////////////////huZWOTTDmD36N1F75A9BshxNlRasCnNpQiWqIhdVHcU",
481            "/////////////////////////////////////////////////////////////////////////////5iieeroHBMfF+sER3JpvROIeEJZjbY+TRE0ntADQLL3"
482          ],
483          "column_roots": [
484            "//////////////////////////////////////7//////////////////////////////////////huZWOTTDmD36N1F75A9BshxNlRasCnNpQiWqIhdVHcU",
485            "/////////////////////////////////////////////////////////////////////////////5iieeroHBMfF+sER3JpvROIeEJZjbY+TRE0ntADQLL3"
486          ]
487        }"#).unwrap()
488    }
489
490    #[test]
491    fn validate_correct() {
492        let dah = sample_dah();
493
494        dah.validate_basic().unwrap();
495    }
496
497    #[test]
498    fn validate_rows_and_cols_len_mismatch() {
499        let mut dah = sample_dah();
500        dah.row_roots.pop();
501
502        dah.validate_basic().unwrap_err();
503    }
504
505    #[test]
506    fn validate_too_little_square() {
507        let mut dah = sample_dah();
508        dah.row_roots = dah
509            .row_roots
510            .into_iter()
511            .cycle()
512            .take(MIN_EXTENDED_SQUARE_WIDTH)
513            .collect();
514        dah.column_roots = dah
515            .column_roots
516            .into_iter()
517            .cycle()
518            .take(MIN_EXTENDED_SQUARE_WIDTH)
519            .collect();
520
521        dah.validate_basic().unwrap();
522
523        dah.row_roots.pop();
524        dah.column_roots.pop();
525
526        dah.validate_basic().unwrap_err();
527    }
528
529    #[test]
530    fn row_proof_serde() {
531        let raw_row_proof = r#"
532          {
533            "end_row": 1,
534            "proofs": [
535              {
536                "aunts": [
537                  "Ch+9PsBdsN5YUt8nvAmjdOAIcVdfmPAEUNmCA8KBe5A=",
538                  "ojjC9H5JG/7OOrt5BzBXs/3w+n1LUI/0YR0d+RSfleU=",
539                  "d6bMQbLTBfZGvqXOW9MPqRM+fTB2/wLJx6CkLc8glCI="
540                ],
541                "index": 0,
542                "leaf_hash": "nOpM3A3d0JYOmNaaI5BFAeKPGwQ90TqmM/kx+sHr79s=",
543                "total": 8
544              },
545              {
546                "aunts": [
547                  "nOpM3A3d0JYOmNaaI5BFAeKPGwQ90TqmM/kx+sHr79s=",
548                  "ojjC9H5JG/7OOrt5BzBXs/3w+n1LUI/0YR0d+RSfleU=",
549                  "d6bMQbLTBfZGvqXOW9MPqRM+fTB2/wLJx6CkLc8glCI="
550                ],
551                "index": 1,
552                "leaf_hash": "Ch+9PsBdsN5YUt8nvAmjdOAIcVdfmPAEUNmCA8KBe5A=",
553                "total": 8
554              }
555            ],
556            "row_roots": [
557              "000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000D8CBB533A24261C4C0A3D37F1CBFB6F4C5EA031472EBA390D482637933874AA0A2B9735E67629993852D",
558              "00000000000000000000000000000000000000D8CBB533A24261C4C0A300000000000000000000000000000000000000D8CBB533A24261C4C0A37E409334CCB1125C793EC040741137634C148F089ACB06BFFF4C1C4CA2CBBA8E"
559            ],
560            "start_row": 0
561          }
562        "#;
563        let raw_dah = r#"
564          {
565            "row_roots": [
566              "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo9N/HL+29MXqAxRy66OQ1IJjeTOHSqCiuXNeZ2KZk4Ut",
567              "AAAAAAAAAAAAAAAAAAAAAAAAANjLtTOiQmHEwKMAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo35AkzTMsRJceT7AQHQRN2NMFI8ImssGv/9MHEyiy7qO",
568              "/////////////////////////////////////////////////////////////////////////////7mTwL+NxdxcYBd89/wRzW2k9vRkQehZiXsuqZXHy89X",
569              "/////////////////////////////////////////////////////////////////////////////2X/FT2ugeYdWmvnEisSgW+9Ih8paNvrji2NYPb8ujaK"
570            ],
571            "column_roots": [
572              "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo/xEv//wkWzNtkcAZiZmSGU1Te6ERwUxTtTfHzoS4bv+",
573              "AAAAAAAAAAAAAAAAAAAAAAAAANjLtTOiQmHEwKMAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo9FOCNvCjA42xYCwHrlo48iPEXLaKt+d+JdErCIrQIi6",
574              "/////////////////////////////////////////////////////////////////////////////y2UErq/83uv433HekCWokxqcY4g+nMQn3tZn2Tr6v74",
575              "/////////////////////////////////////////////////////////////////////////////z6fKmbJTvfLYFlNuDWHn87vJb6V7n44MlCkxv1dyfT2"
576            ]
577          }
578        "#;
579
580        let row_proof: RowProof = serde_json::from_str(raw_row_proof).unwrap();
581        let dah: DataAvailabilityHeader = serde_json::from_str(raw_dah).unwrap();
582
583        row_proof.verify(dah.hash()).unwrap();
584    }
585
586    #[test]
587    fn row_proof_verify_correct() {
588        for square_width in [2, 4, 8, 16] {
589            let dah = random_dah(square_width);
590            let dah_root = dah.hash();
591
592            for start_row in 0..dah.square_width() - 1 {
593                for end_row in start_row..dah.square_width() {
594                    let proof = dah.row_proof(start_row..=end_row).unwrap();
595
596                    proof.verify(dah_root).unwrap()
597                }
598            }
599        }
600    }
601
602    #[test]
603    fn row_proof_verify_malformed() {
604        let dah = random_dah(16);
605        let dah_root = dah.hash();
606
607        let valid_proof = dah.row_proof(0..=1).unwrap();
608
609        // start_row > end_row
610        #[allow(clippy::reversed_empty_ranges)]
611        let proof = dah.row_proof(1..=0).unwrap();
612        proof.verify(dah_root).unwrap_err();
613
614        // length incorrect based on start and end
615        let mut proof = valid_proof.clone();
616        proof.end_row = 2;
617        proof.verify(dah_root).unwrap_err();
618
619        // incorrect amount of proofs
620        let mut proof = valid_proof.clone();
621        proof.proofs.push(proof.proofs[0].clone());
622        proof.verify(dah_root).unwrap_err();
623
624        // incorrect amount of roots
625        let mut proof = valid_proof.clone();
626        proof.row_roots.pop();
627        proof.verify(dah_root).unwrap_err();
628
629        // wrong proof order
630        let mut proof = valid_proof.clone();
631        proof.row_roots = proof.row_roots.into_iter().rev().collect();
632        proof.verify(dah_root).unwrap_err();
633    }
634
635    #[test]
636    fn test_serialize_row_proof_binary() {
637        let dah = random_dah(16);
638        let proof = dah.row_proof(0..=1).unwrap();
639        let serialized = postcard::to_allocvec(&proof).unwrap();
640        let deserialized: RowProof = postcard::from_bytes(&serialized).unwrap();
641        assert_eq!(proof, deserialized);
642    }
643
644    fn random_dah(square_width: u16) -> DataAvailabilityHeader {
645        let namespaces: Vec<_> = (0..square_width)
646            .map(|n| Namespace::new_v0(&[n as u8]).unwrap())
647            .collect();
648        let (row_roots, col_roots): (Vec<_>, Vec<_>) = namespaces
649            .iter()
650            .map(|&ns| {
651                let row = NamespacedHash::new(*ns, *ns, rand::random());
652                let col = NamespacedHash::new(
653                    **namespaces.first().unwrap(),
654                    **namespaces.last().unwrap(),
655                    rand::random(),
656                );
657                (row, col)
658            })
659            .unzip();
660
661        DataAvailabilityHeader::new(row_roots, col_roots).unwrap()
662    }
663}