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::appconsts::AppVersion;
13use crate::consts::data_availability_header::{
14    MIN_EXTENDED_SQUARE_WIDTH, max_extended_square_width,
15};
16use crate::eds::AxisType;
17use crate::hash::Hash;
18use crate::nmt::{NamespacedHash, NamespacedHashExt};
19use crate::{
20    Error, ExtendedDataSquare, MerkleProof, Result, ValidateBasicWithAppVersion, ValidationError,
21    bail_validation, bail_verification, validation_error,
22};
23
24/// Header with commitments of the data availability.
25///
26/// It consists of the root hashes of the merkle trees created from each
27/// row and column of the [`ExtendedDataSquare`]. Those are used to prove
28/// the inclusion of the data in a block.
29///
30/// The hash of this header is a hash of all rows and columns and thus a
31/// data commitment of the block.
32///
33/// # Example
34///
35/// ```no_run
36/// # use celestia_types::{ExtendedHeader, Height, Share};
37/// # use celestia_types::nmt::{Namespace, NamespaceProof};
38/// # fn extended_header() -> ExtendedHeader {
39/// #     unimplemented!();
40/// # }
41/// # fn shares_with_proof(_: Height, _: &Namespace) -> (Vec<Share>, NamespaceProof) {
42/// #     unimplemented!();
43/// # }
44/// // fetch the block header and data for your namespace
45/// let namespace = Namespace::new_v0(&[1, 2, 3, 4]).unwrap();
46/// let eh = extended_header();
47/// let (shares, proof) = shares_with_proof(eh.height(), &namespace);
48///
49/// // get the data commitment for a given row
50/// let dah = eh.dah;
51/// let root = dah.row_root(0).unwrap();
52///
53/// // verify a proof of the inclusion of the shares
54/// assert!(proof.verify_complete_namespace(&root, &shares, *namespace).is_ok());
55/// ```
56///
57/// [`ExtendedDataSquare`]: crate::eds::ExtendedDataSquare
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(
60    try_from = "RawDataAvailabilityHeader",
61    into = "RawDataAvailabilityHeader"
62)]
63#[cfg_attr(
64    all(feature = "wasm-bindgen", target_arch = "wasm32"),
65    wasm_bindgen(inspectable)
66)]
67pub struct DataAvailabilityHeader {
68    /// Merkle roots of the [`ExtendedDataSquare`] rows.
69    row_roots: Vec<NamespacedHash>,
70    /// Merkle roots of the [`ExtendedDataSquare`] columns.
71    column_roots: Vec<NamespacedHash>,
72}
73
74impl DataAvailabilityHeader {
75    /// Create new [`DataAvailabilityHeader`].
76    pub fn new(
77        row_roots: Vec<NamespacedHash>,
78        column_roots: Vec<NamespacedHash>,
79        app_version: AppVersion,
80    ) -> Result<Self> {
81        let dah = DataAvailabilityHeader::new_unchecked(row_roots, column_roots);
82        dah.validate_basic(app_version)?;
83        Ok(dah)
84    }
85
86    /// Create new non-validated [`DataAvailabilityHeader`].
87    ///
88    /// [`DataAvailabilityHeader::validate_basic`] can be used to check valitidy later on.
89    pub fn new_unchecked(
90        row_roots: Vec<NamespacedHash>,
91        column_roots: Vec<NamespacedHash>,
92    ) -> Self {
93        DataAvailabilityHeader {
94            row_roots,
95            column_roots,
96        }
97    }
98
99    /// Create a DataAvailabilityHeader by computing roots of a given [`ExtendedDataSquare`].
100    pub fn from_eds(eds: &ExtendedDataSquare) -> Self {
101        let square_width = eds.square_width();
102
103        let mut dah = DataAvailabilityHeader {
104            row_roots: Vec::with_capacity(square_width.into()),
105            column_roots: Vec::with_capacity(square_width.into()),
106        };
107
108        for i in 0..square_width {
109            let row_root = eds
110                .row_nmt(i)
111                .expect("EDS validated on construction")
112                .root();
113            dah.row_roots.push(row_root);
114
115            let column_root = eds
116                .column_nmt(i)
117                .expect("EDS validated on construction")
118                .root();
119            dah.column_roots.push(column_root);
120        }
121
122        dah
123    }
124
125    /// Get the root from an axis at the given index.
126    pub fn root(&self, axis: AxisType, index: u16) -> Option<NamespacedHash> {
127        match axis {
128            AxisType::Col => self.column_root(index),
129            AxisType::Row => self.row_root(index),
130        }
131    }
132
133    /// Merkle roots of the [`ExtendedDataSquare`] rows.
134    ///
135    /// [`ExtendedDataSquare`]: crate::eds::ExtendedDataSquare
136    pub fn row_roots(&self) -> &[NamespacedHash] {
137        &self.row_roots
138    }
139
140    /// Merkle roots of the [`ExtendedDataSquare`] columns.
141    ///
142    /// [`ExtendedDataSquare`]: crate::eds::ExtendedDataSquare
143    pub fn column_roots(&self) -> &[NamespacedHash] {
144        &self.column_roots
145    }
146
147    /// Get a root of the row with the given index.
148    pub fn row_root(&self, row: u16) -> Option<NamespacedHash> {
149        let row = usize::from(row);
150        self.row_roots.get(row).cloned()
151    }
152
153    /// Get the a root of the column with the given index.
154    pub fn column_root(&self, column: u16) -> Option<NamespacedHash> {
155        let column = usize::from(column);
156        self.column_roots.get(column).cloned()
157    }
158
159    /// Compute the combined hash of all rows and columns.
160    ///
161    /// This is the data commitment for the block.
162    ///
163    /// # Example
164    ///
165    /// ```
166    /// # use celestia_types::ExtendedHeader;
167    /// # fn get_extended_header() -> ExtendedHeader {
168    /// #   let s = include_str!("../test_data/chain1/extended_header_block_1.json");
169    /// #   serde_json::from_str(s).unwrap()
170    /// # }
171    /// let eh = get_extended_header();
172    /// let dah = eh.dah;
173    ///
174    /// assert_eq!(dah.hash(), eh.header.data_hash.unwrap());
175    /// ```
176    pub fn hash(&self) -> Hash {
177        let all_roots: Vec<_> = self
178            .row_roots
179            .iter()
180            .chain(self.column_roots.iter())
181            .map(|root| root.to_array())
182            .collect();
183
184        Hash::Sha256(simple_hash_from_byte_vectors::<Sha256>(&all_roots))
185    }
186
187    /// Get the size of the [`ExtendedDataSquare`] for which this header was built.
188    ///
189    /// [`ExtendedDataSquare`]: crate::eds::ExtendedDataSquare
190    pub fn square_width(&self) -> u16 {
191        // `validate_basic` checks that rows num = cols num
192        self.row_roots
193            .len()
194            .try_into()
195            // On validated DAH this never happens
196            .expect("len is bigger than u16::MAX")
197    }
198
199    /// Get the [`RowProof`] for given rows.
200    pub fn row_proof(&self, rows: RangeInclusive<u16>) -> Result<RowProof> {
201        let all_roots: Vec<_> = self
202            .row_roots
203            .iter()
204            .chain(self.column_roots.iter())
205            .map(|root| root.to_array())
206            .collect();
207
208        let start_row = *rows.start();
209        let end_row = *rows.end();
210        let mut proofs = Vec::with_capacity(rows.len());
211        let mut row_roots = Vec::with_capacity(rows.len());
212
213        for idx in rows {
214            proofs.push(MerkleProof::new(idx as usize, &all_roots)?.0);
215            let row = self
216                .row_root(idx)
217                .ok_or(Error::IndexOutOfRange(idx as usize, self.row_roots.len()))?;
218            row_roots.push(row);
219        }
220
221        Ok(RowProof {
222            proofs,
223            row_roots,
224            start_row,
225            end_row,
226        })
227    }
228}
229
230#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
231#[wasm_bindgen]
232impl DataAvailabilityHeader {
233    /// Merkle roots of the [`ExtendedDataSquare`] rows.
234    #[wasm_bindgen(js_name = rowRoots)]
235    pub fn js_row_roots(&self) -> Result<js_sys::Array, serde_wasm_bindgen::Error> {
236        self.row_roots()
237            .iter()
238            .map(|h| serde_wasm_bindgen::to_value(&h))
239            .collect()
240    }
241
242    /// Merkle roots of the [`ExtendedDataSquare`] columns.
243    #[wasm_bindgen(js_name = columnRoots)]
244    pub fn js_column_roots(&self) -> Result<js_sys::Array, serde_wasm_bindgen::Error> {
245        self.column_roots()
246            .iter()
247            .map(|h| serde_wasm_bindgen::to_value(&h))
248            .collect()
249    }
250
251    /// Get a root of the row with the given index.
252    #[wasm_bindgen(js_name = rowRoot)]
253    pub fn js_row_root(&self, row: u16) -> Result<JsValue, serde_wasm_bindgen::Error> {
254        serde_wasm_bindgen::to_value(&self.row_root(row))
255    }
256
257    /// Get the a root of the column with the given index.
258    #[wasm_bindgen(js_name = columnRoot)]
259    pub fn js_column_root(&self, column: u16) -> Result<JsValue, serde_wasm_bindgen::Error> {
260        serde_wasm_bindgen::to_value(&self.column_root(column))
261    }
262
263    /// Compute the combined hash of all rows and columns.
264    ///
265    /// This is the data commitment for the block.
266    #[wasm_bindgen(js_name = hash)]
267    pub fn js_hash(&self) -> Result<JsValue, serde_wasm_bindgen::Error> {
268        serde_wasm_bindgen::to_value(&self.hash())
269    }
270
271    /// Get the size of the [`ExtendedDataSquare`] for which this header was built.
272    #[wasm_bindgen(js_name = squareWidth)]
273    pub fn js_square_width(&self) -> u16 {
274        self.square_width()
275    }
276}
277
278impl Protobuf<RawDataAvailabilityHeader> for DataAvailabilityHeader {}
279
280impl TryFrom<RawDataAvailabilityHeader> for DataAvailabilityHeader {
281    type Error = Error;
282
283    fn try_from(value: RawDataAvailabilityHeader) -> Result<Self, Self::Error> {
284        Ok(DataAvailabilityHeader {
285            row_roots: value
286                .row_roots
287                .iter()
288                .map(|bytes| NamespacedHash::from_raw(bytes))
289                .collect::<Result<Vec<_>>>()?,
290            column_roots: value
291                .column_roots
292                .iter()
293                .map(|bytes| NamespacedHash::from_raw(bytes))
294                .collect::<Result<Vec<_>>>()?,
295        })
296    }
297}
298
299impl From<DataAvailabilityHeader> for RawDataAvailabilityHeader {
300    fn from(value: DataAvailabilityHeader) -> RawDataAvailabilityHeader {
301        RawDataAvailabilityHeader {
302            row_roots: value.row_roots.iter().map(|hash| hash.to_vec()).collect(),
303            column_roots: value
304                .column_roots
305                .iter()
306                .map(|hash| hash.to_vec())
307                .collect(),
308        }
309    }
310}
311
312impl ValidateBasicWithAppVersion for DataAvailabilityHeader {
313    fn validate_basic(&self, app_version: AppVersion) -> Result<(), ValidationError> {
314        let max_extended_square_width = max_extended_square_width(app_version);
315
316        if self.column_roots.len() != self.row_roots.len() {
317            bail_validation!(
318                "column_roots len ({}) != row_roots len ({})",
319                self.column_roots.len(),
320                self.row_roots.len(),
321            )
322        }
323
324        if self.row_roots.len() < MIN_EXTENDED_SQUARE_WIDTH {
325            bail_validation!(
326                "row_roots len ({}) < minimum ({})",
327                self.row_roots.len(),
328                MIN_EXTENDED_SQUARE_WIDTH,
329            )
330        }
331
332        if self.row_roots.len() > max_extended_square_width {
333            bail_validation!(
334                "row_roots len ({}) > maximum ({})",
335                self.row_roots.len(),
336                max_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(AppVersion::V2).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(AppVersion::V2).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(AppVersion::V2).unwrap();
522
523        dah.row_roots.pop();
524        dah.column_roots.pop();
525
526        dah.validate_basic(AppVersion::V2).unwrap_err();
527    }
528
529    #[test]
530    fn validate_too_big_square() {
531        let mut dah = sample_dah();
532        dah.row_roots = dah
533            .row_roots
534            .into_iter()
535            .cycle()
536            .take(max_extended_square_width(AppVersion::V2))
537            .collect();
538        dah.column_roots = dah
539            .column_roots
540            .into_iter()
541            .cycle()
542            .take(max_extended_square_width(AppVersion::V2))
543            .collect();
544
545        dah.validate_basic(AppVersion::V2).unwrap();
546
547        dah.row_roots.push(dah.row_roots[0].clone());
548        dah.column_roots.push(dah.column_roots[0].clone());
549
550        dah.validate_basic(AppVersion::V2).unwrap_err();
551    }
552
553    #[test]
554    fn row_proof_serde() {
555        let raw_row_proof = r#"
556          {
557            "end_row": 1,
558            "proofs": [
559              {
560                "aunts": [
561                  "Ch+9PsBdsN5YUt8nvAmjdOAIcVdfmPAEUNmCA8KBe5A=",
562                  "ojjC9H5JG/7OOrt5BzBXs/3w+n1LUI/0YR0d+RSfleU=",
563                  "d6bMQbLTBfZGvqXOW9MPqRM+fTB2/wLJx6CkLc8glCI="
564                ],
565                "index": 0,
566                "leaf_hash": "nOpM3A3d0JYOmNaaI5BFAeKPGwQ90TqmM/kx+sHr79s=",
567                "total": 8
568              },
569              {
570                "aunts": [
571                  "nOpM3A3d0JYOmNaaI5BFAeKPGwQ90TqmM/kx+sHr79s=",
572                  "ojjC9H5JG/7OOrt5BzBXs/3w+n1LUI/0YR0d+RSfleU=",
573                  "d6bMQbLTBfZGvqXOW9MPqRM+fTB2/wLJx6CkLc8glCI="
574                ],
575                "index": 1,
576                "leaf_hash": "Ch+9PsBdsN5YUt8nvAmjdOAIcVdfmPAEUNmCA8KBe5A=",
577                "total": 8
578              }
579            ],
580            "row_roots": [
581              "000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000D8CBB533A24261C4C0A3D37F1CBFB6F4C5EA031472EBA390D482637933874AA0A2B9735E67629993852D",
582              "00000000000000000000000000000000000000D8CBB533A24261C4C0A300000000000000000000000000000000000000D8CBB533A24261C4C0A37E409334CCB1125C793EC040741137634C148F089ACB06BFFF4C1C4CA2CBBA8E"
583            ],
584            "start_row": 0
585          }
586        "#;
587        let raw_dah = r#"
588          {
589            "row_roots": [
590              "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo9N/HL+29MXqAxRy66OQ1IJjeTOHSqCiuXNeZ2KZk4Ut",
591              "AAAAAAAAAAAAAAAAAAAAAAAAANjLtTOiQmHEwKMAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo35AkzTMsRJceT7AQHQRN2NMFI8ImssGv/9MHEyiy7qO",
592              "/////////////////////////////////////////////////////////////////////////////7mTwL+NxdxcYBd89/wRzW2k9vRkQehZiXsuqZXHy89X",
593              "/////////////////////////////////////////////////////////////////////////////2X/FT2ugeYdWmvnEisSgW+9Ih8paNvrji2NYPb8ujaK"
594            ],
595            "column_roots": [
596              "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo/xEv//wkWzNtkcAZiZmSGU1Te6ERwUxTtTfHzoS4bv+",
597              "AAAAAAAAAAAAAAAAAAAAAAAAANjLtTOiQmHEwKMAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo9FOCNvCjA42xYCwHrlo48iPEXLaKt+d+JdErCIrQIi6",
598              "/////////////////////////////////////////////////////////////////////////////y2UErq/83uv433HekCWokxqcY4g+nMQn3tZn2Tr6v74",
599              "/////////////////////////////////////////////////////////////////////////////z6fKmbJTvfLYFlNuDWHn87vJb6V7n44MlCkxv1dyfT2"
600            ]
601          }
602        "#;
603
604        let row_proof: RowProof = serde_json::from_str(raw_row_proof).unwrap();
605        let dah: DataAvailabilityHeader = serde_json::from_str(raw_dah).unwrap();
606
607        row_proof.verify(dah.hash()).unwrap();
608    }
609
610    #[test]
611    fn row_proof_verify_correct() {
612        for square_width in [2, 4, 8, 16] {
613            let dah = random_dah(square_width);
614            let dah_root = dah.hash();
615
616            for start_row in 0..dah.square_width() - 1 {
617                for end_row in start_row..dah.square_width() {
618                    let proof = dah.row_proof(start_row..=end_row).unwrap();
619
620                    proof.verify(dah_root).unwrap()
621                }
622            }
623        }
624    }
625
626    #[test]
627    fn row_proof_verify_malformed() {
628        let dah = random_dah(16);
629        let dah_root = dah.hash();
630
631        let valid_proof = dah.row_proof(0..=1).unwrap();
632
633        // start_row > end_row
634        #[allow(clippy::reversed_empty_ranges)]
635        let proof = dah.row_proof(1..=0).unwrap();
636        proof.verify(dah_root).unwrap_err();
637
638        // length incorrect based on start and end
639        let mut proof = valid_proof.clone();
640        proof.end_row = 2;
641        proof.verify(dah_root).unwrap_err();
642
643        // incorrect amount of proofs
644        let mut proof = valid_proof.clone();
645        proof.proofs.push(proof.proofs[0].clone());
646        proof.verify(dah_root).unwrap_err();
647
648        // incorrect amount of roots
649        let mut proof = valid_proof.clone();
650        proof.row_roots.pop();
651        proof.verify(dah_root).unwrap_err();
652
653        // wrong proof order
654        let mut proof = valid_proof.clone();
655        proof.row_roots = proof.row_roots.into_iter().rev().collect();
656        proof.verify(dah_root).unwrap_err();
657    }
658
659    #[test]
660    fn test_serialize_row_proof_binary() {
661        let dah = random_dah(16);
662        let proof = dah.row_proof(0..=1).unwrap();
663        let serialized = postcard::to_allocvec(&proof).unwrap();
664        let deserialized: RowProof = postcard::from_bytes(&serialized).unwrap();
665        assert_eq!(proof, deserialized);
666    }
667
668    fn random_dah(square_width: u16) -> DataAvailabilityHeader {
669        let namespaces: Vec<_> = (0..square_width)
670            .map(|n| Namespace::new_v0(&[n as u8]).unwrap())
671            .collect();
672        let (row_roots, col_roots): (Vec<_>, Vec<_>) = namespaces
673            .iter()
674            .map(|&ns| {
675                let row = NamespacedHash::new(*ns, *ns, rand::random());
676                let col = NamespacedHash::new(
677                    **namespaces.first().unwrap(),
678                    **namespaces.last().unwrap(),
679                    rand::random(),
680                );
681                (row, col)
682            })
683            .unzip();
684
685        DataAvailabilityHeader::new(row_roots, col_roots, AppVersion::V2).unwrap()
686    }
687}