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::{Namespace, NamespacedHash, NamespacedHashExt, NamespacedSha2Hasher};
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(_: u64, _: &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    /// Check if row with given index contains provided namespace.
154    pub fn row_contains(&self, row: u16, namespace: Namespace) -> Result<bool> {
155        let row_root = self
156            .row_root(row)
157            .ok_or(Error::IndexOutOfRange(row as usize, self.row_roots.len()))?;
158        Ok(row_root.contains::<NamespacedSha2Hasher>(*namespace))
159    }
160
161    /// Get the a root of the column with the given index.
162    pub fn column_root(&self, column: u16) -> Option<NamespacedHash> {
163        let column = usize::from(column);
164        self.column_roots.get(column).cloned()
165    }
166
167    /// Check if column with given index contains provided namespace.
168    pub fn column_contains(&self, column: u16, namespace: Namespace) -> Result<bool> {
169        let column_root = self.column_root(column).ok_or(Error::IndexOutOfRange(
170            column as usize,
171            self.column_roots.len(),
172        ))?;
173        Ok(column_root.contains::<NamespacedSha2Hasher>(*namespace))
174    }
175
176    /// Compute the combined hash of all rows and columns.
177    ///
178    /// This is the data commitment for the block.
179    ///
180    /// # Example
181    ///
182    /// ```
183    /// # use celestia_types::ExtendedHeader;
184    /// # fn get_extended_header() -> ExtendedHeader {
185    /// #   let s = include_str!("../test_data/chain1/extended_header_block_1.json");
186    /// #   serde_json::from_str(s).unwrap()
187    /// # }
188    /// let eh = get_extended_header();
189    /// let dah = eh.dah;
190    ///
191    /// assert_eq!(dah.hash(), eh.header.data_hash.unwrap());
192    /// ```
193    pub fn hash(&self) -> Hash {
194        let all_roots: Vec<_> = self
195            .row_roots
196            .iter()
197            .chain(self.column_roots.iter())
198            .map(|root| root.to_array())
199            .collect();
200
201        Hash::Sha256(simple_hash_from_byte_vectors::<Sha256>(&all_roots))
202    }
203
204    /// Get the size of the [`ExtendedDataSquare`] for which this header was built.
205    ///
206    /// [`ExtendedDataSquare`]: crate::eds::ExtendedDataSquare
207    pub fn square_width(&self) -> u16 {
208        // `validate_basic` checks that rows num = cols num
209        self.row_roots
210            .len()
211            .try_into()
212            // On validated DAH this never happens
213            .expect("len is bigger than u16::MAX")
214    }
215
216    /// Get the [`RowProof`] for given rows.
217    pub fn row_proof(&self, rows: RangeInclusive<u16>) -> Result<RowProof> {
218        let all_roots: Vec<_> = self
219            .row_roots
220            .iter()
221            .chain(self.column_roots.iter())
222            .map(|root| root.to_array())
223            .collect();
224
225        let start_row = *rows.start();
226        let end_row = *rows.end();
227        let mut proofs = Vec::with_capacity(rows.len());
228        let mut row_roots = Vec::with_capacity(rows.len());
229
230        for idx in rows {
231            proofs.push(MerkleProof::new(idx as usize, &all_roots)?.0);
232            let row = self
233                .row_root(idx)
234                .ok_or(Error::IndexOutOfRange(idx as usize, self.row_roots.len()))?;
235            row_roots.push(row);
236        }
237
238        Ok(RowProof {
239            proofs,
240            row_roots,
241            start_row,
242            end_row,
243        })
244    }
245}
246
247#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
248#[wasm_bindgen]
249impl DataAvailabilityHeader {
250    /// Merkle roots of the [`ExtendedDataSquare`] rows.
251    #[wasm_bindgen(js_name = rowRoots)]
252    pub fn js_row_roots(&self) -> Result<js_sys::Array, serde_wasm_bindgen::Error> {
253        self.row_roots()
254            .iter()
255            .map(|h| serde_wasm_bindgen::to_value(&h))
256            .collect()
257    }
258
259    /// Merkle roots of the [`ExtendedDataSquare`] columns.
260    #[wasm_bindgen(js_name = columnRoots)]
261    pub fn js_column_roots(&self) -> Result<js_sys::Array, serde_wasm_bindgen::Error> {
262        self.column_roots()
263            .iter()
264            .map(|h| serde_wasm_bindgen::to_value(&h))
265            .collect()
266    }
267
268    /// Get a root of the row with the given index.
269    #[wasm_bindgen(js_name = rowRoot)]
270    pub fn js_row_root(&self, row: u16) -> Result<JsValue, serde_wasm_bindgen::Error> {
271        serde_wasm_bindgen::to_value(&self.row_root(row))
272    }
273
274    /// Get the a root of the column with the given index.
275    #[wasm_bindgen(js_name = columnRoot)]
276    pub fn js_column_root(&self, column: u16) -> Result<JsValue, serde_wasm_bindgen::Error> {
277        serde_wasm_bindgen::to_value(&self.column_root(column))
278    }
279
280    /// Compute the combined hash of all rows and columns.
281    ///
282    /// This is the data commitment for the block.
283    #[wasm_bindgen(js_name = hash)]
284    pub fn js_hash(&self) -> Result<JsValue, serde_wasm_bindgen::Error> {
285        serde_wasm_bindgen::to_value(&self.hash())
286    }
287
288    /// Get the size of the [`ExtendedDataSquare`] for which this header was built.
289    #[wasm_bindgen(js_name = squareWidth)]
290    pub fn js_square_width(&self) -> u16 {
291        self.square_width()
292    }
293}
294
295impl Protobuf<RawDataAvailabilityHeader> for DataAvailabilityHeader {}
296
297impl TryFrom<RawDataAvailabilityHeader> for DataAvailabilityHeader {
298    type Error = Error;
299
300    fn try_from(value: RawDataAvailabilityHeader) -> Result<Self, Self::Error> {
301        Ok(DataAvailabilityHeader {
302            row_roots: value
303                .row_roots
304                .iter()
305                .map(|bytes| NamespacedHash::from_raw(bytes))
306                .collect::<Result<Vec<_>>>()?,
307            column_roots: value
308                .column_roots
309                .iter()
310                .map(|bytes| NamespacedHash::from_raw(bytes))
311                .collect::<Result<Vec<_>>>()?,
312        })
313    }
314}
315
316impl From<DataAvailabilityHeader> for RawDataAvailabilityHeader {
317    fn from(value: DataAvailabilityHeader) -> RawDataAvailabilityHeader {
318        RawDataAvailabilityHeader {
319            row_roots: value.row_roots.iter().map(|hash| hash.to_vec()).collect(),
320            column_roots: value
321                .column_roots
322                .iter()
323                .map(|hash| hash.to_vec())
324                .collect(),
325        }
326    }
327}
328
329impl ValidateBasicWithAppVersion for DataAvailabilityHeader {
330    fn validate_basic(&self, app_version: AppVersion) -> Result<(), ValidationError> {
331        let max_extended_square_width = max_extended_square_width(app_version);
332
333        if self.column_roots.len() != self.row_roots.len() {
334            bail_validation!(
335                "column_roots len ({}) != row_roots len ({})",
336                self.column_roots.len(),
337                self.row_roots.len(),
338            )
339        }
340
341        if self.row_roots.len() < MIN_EXTENDED_SQUARE_WIDTH {
342            bail_validation!(
343                "row_roots len ({}) < minimum ({})",
344                self.row_roots.len(),
345                MIN_EXTENDED_SQUARE_WIDTH,
346            )
347        }
348
349        if self.row_roots.len() > max_extended_square_width {
350            bail_validation!(
351                "row_roots len ({}) > maximum ({})",
352                self.row_roots.len(),
353                max_extended_square_width,
354            )
355        }
356
357        Ok(())
358    }
359}
360
361/// A proof of inclusion of a range of row roots in a [`DataAvailabilityHeader`].
362#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
363#[serde(try_from = "RawRowProof", into = "RawRowProof")]
364pub struct RowProof {
365    row_roots: Vec<NamespacedHash>,
366    proofs: Vec<MerkleProof>,
367    start_row: u16,
368    end_row: u16,
369}
370
371impl RowProof {
372    /// Get the list of row roots this proof proves.
373    pub fn row_roots(&self) -> &[NamespacedHash] {
374        &self.row_roots
375    }
376
377    /// Get the inclusion proofs of each row root in the data availability header.
378    pub fn proofs(&self) -> &[MerkleProof] {
379        &self.proofs
380    }
381
382    /// Verify the proof against the hash of [`DataAvailabilityHeader`], proving
383    /// the inclusion of rows.
384    ///
385    /// # Errors
386    ///
387    /// This function will return an error if:
388    ///  - the proof is malformed. Number of proofs, row roots and the span between starting and ending row need to match.
389    ///  - the verification of any inner merkle proof fails
390    ///
391    /// # Example
392    ///
393    /// ```
394    /// # use celestia_types::ExtendedHeader;
395    /// # fn get_extended_header() -> ExtendedHeader {
396    /// #   let s = include_str!("../test_data/chain1/extended_header_block_1.json");
397    /// #   serde_json::from_str(s).unwrap()
398    /// # }
399    /// let eh = get_extended_header();
400    /// let dah = eh.dah;
401    ///
402    /// let proof = dah.row_proof(0..=1).unwrap();
403    ///
404    /// assert!(proof.verify(dah.hash()).is_ok());
405    /// ```
406    pub fn verify(&self, root: Hash) -> Result<()> {
407        if self.row_roots.len() != self.proofs.len() {
408            bail_verification!("invalid row proof: row_roots.len() != proofs.len()");
409        }
410
411        if self.end_row < self.start_row {
412            bail_verification!(
413                "start_row ({}) > end_row ({})",
414                self.start_row,
415                self.end_row
416            );
417        }
418
419        let length = self.end_row - self.start_row + 1;
420        if length as usize != self.proofs.len() {
421            bail_verification!(
422                "length based on start_row and end_row ({}) != length of proofs ({})",
423                length,
424                self.proofs.len()
425            );
426        }
427
428        let Hash::Sha256(root) = root else {
429            bail_verification!("empty hash");
430        };
431
432        for (row_root, proof) in self.row_roots.iter().zip(self.proofs.iter()) {
433            proof.verify(row_root.to_array(), root)?;
434        }
435
436        Ok(())
437    }
438}
439
440impl Protobuf<RawRowProof> for RowProof {}
441
442impl TryFrom<RawRowProof> for RowProof {
443    type Error = Error;
444
445    fn try_from(value: RawRowProof) -> Result<Self> {
446        Ok(Self {
447            row_roots: value
448                .row_roots
449                .into_iter()
450                .map(|hash| NamespacedHash::from_raw(&hash))
451                .collect::<Result<_>>()?,
452            proofs: value
453                .proofs
454                .into_iter()
455                .map(TryInto::try_into)
456                .collect::<Result<_>>()?,
457            start_row: value
458                .start_row
459                .try_into()
460                .map_err(|_| validation_error!("start_row ({}) exceeds u16", value.start_row))?,
461            end_row: value
462                .end_row
463                .try_into()
464                .map_err(|_| validation_error!("end_row ({}) exceeds u16", value.end_row))?,
465        })
466    }
467}
468
469impl From<RowProof> for RawRowProof {
470    fn from(value: RowProof) -> Self {
471        Self {
472            row_roots: value
473                .row_roots
474                .into_iter()
475                .map(|hash| hash.to_vec())
476                .collect(),
477            proofs: value.proofs.into_iter().map(Into::into).collect(),
478            start_row: value.start_row as u32,
479            end_row: value.end_row as u32,
480            root: vec![],
481        }
482    }
483}
484
485#[cfg(test)]
486mod tests {
487    use crate::nmt::Namespace;
488
489    use super::*;
490
491    #[cfg(target_arch = "wasm32")]
492    use wasm_bindgen_test::wasm_bindgen_test as test;
493
494    fn sample_dah() -> DataAvailabilityHeader {
495        serde_json::from_str(r#"{
496          "row_roots": [
497            "//////////////////////////////////////7//////////////////////////////////////huZWOTTDmD36N1F75A9BshxNlRasCnNpQiWqIhdVHcU",
498            "/////////////////////////////////////////////////////////////////////////////5iieeroHBMfF+sER3JpvROIeEJZjbY+TRE0ntADQLL3"
499          ],
500          "column_roots": [
501            "//////////////////////////////////////7//////////////////////////////////////huZWOTTDmD36N1F75A9BshxNlRasCnNpQiWqIhdVHcU",
502            "/////////////////////////////////////////////////////////////////////////////5iieeroHBMfF+sER3JpvROIeEJZjbY+TRE0ntADQLL3"
503          ]
504        }"#).unwrap()
505    }
506
507    #[test]
508    fn validate_correct() {
509        let dah = sample_dah();
510
511        dah.validate_basic(AppVersion::V2).unwrap();
512    }
513
514    #[test]
515    fn validate_rows_and_cols_len_mismatch() {
516        let mut dah = sample_dah();
517        dah.row_roots.pop();
518
519        dah.validate_basic(AppVersion::V2).unwrap_err();
520    }
521
522    #[test]
523    fn validate_too_little_square() {
524        let mut dah = sample_dah();
525        dah.row_roots = dah
526            .row_roots
527            .into_iter()
528            .cycle()
529            .take(MIN_EXTENDED_SQUARE_WIDTH)
530            .collect();
531        dah.column_roots = dah
532            .column_roots
533            .into_iter()
534            .cycle()
535            .take(MIN_EXTENDED_SQUARE_WIDTH)
536            .collect();
537
538        dah.validate_basic(AppVersion::V2).unwrap();
539
540        dah.row_roots.pop();
541        dah.column_roots.pop();
542
543        dah.validate_basic(AppVersion::V2).unwrap_err();
544    }
545
546    #[test]
547    fn validate_too_big_square() {
548        let mut dah = sample_dah();
549        dah.row_roots = dah
550            .row_roots
551            .into_iter()
552            .cycle()
553            .take(max_extended_square_width(AppVersion::V2))
554            .collect();
555        dah.column_roots = dah
556            .column_roots
557            .into_iter()
558            .cycle()
559            .take(max_extended_square_width(AppVersion::V2))
560            .collect();
561
562        dah.validate_basic(AppVersion::V2).unwrap();
563
564        dah.row_roots.push(dah.row_roots[0].clone());
565        dah.column_roots.push(dah.column_roots[0].clone());
566
567        dah.validate_basic(AppVersion::V2).unwrap_err();
568    }
569
570    #[test]
571    fn row_proof_serde() {
572        let raw_row_proof = r#"
573          {
574            "end_row": 1,
575            "proofs": [
576              {
577                "aunts": [
578                  "Ch+9PsBdsN5YUt8nvAmjdOAIcVdfmPAEUNmCA8KBe5A=",
579                  "ojjC9H5JG/7OOrt5BzBXs/3w+n1LUI/0YR0d+RSfleU=",
580                  "d6bMQbLTBfZGvqXOW9MPqRM+fTB2/wLJx6CkLc8glCI="
581                ],
582                "index": 0,
583                "leaf_hash": "nOpM3A3d0JYOmNaaI5BFAeKPGwQ90TqmM/kx+sHr79s=",
584                "total": 8
585              },
586              {
587                "aunts": [
588                  "nOpM3A3d0JYOmNaaI5BFAeKPGwQ90TqmM/kx+sHr79s=",
589                  "ojjC9H5JG/7OOrt5BzBXs/3w+n1LUI/0YR0d+RSfleU=",
590                  "d6bMQbLTBfZGvqXOW9MPqRM+fTB2/wLJx6CkLc8glCI="
591                ],
592                "index": 1,
593                "leaf_hash": "Ch+9PsBdsN5YUt8nvAmjdOAIcVdfmPAEUNmCA8KBe5A=",
594                "total": 8
595              }
596            ],
597            "row_roots": [
598              "000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000D8CBB533A24261C4C0A3D37F1CBFB6F4C5EA031472EBA390D482637933874AA0A2B9735E67629993852D",
599              "00000000000000000000000000000000000000D8CBB533A24261C4C0A300000000000000000000000000000000000000D8CBB533A24261C4C0A37E409334CCB1125C793EC040741137634C148F089ACB06BFFF4C1C4CA2CBBA8E"
600            ],
601            "start_row": 0
602          }
603        "#;
604        let raw_dah = r#"
605          {
606            "row_roots": [
607              "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo9N/HL+29MXqAxRy66OQ1IJjeTOHSqCiuXNeZ2KZk4Ut",
608              "AAAAAAAAAAAAAAAAAAAAAAAAANjLtTOiQmHEwKMAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo35AkzTMsRJceT7AQHQRN2NMFI8ImssGv/9MHEyiy7qO",
609              "/////////////////////////////////////////////////////////////////////////////7mTwL+NxdxcYBd89/wRzW2k9vRkQehZiXsuqZXHy89X",
610              "/////////////////////////////////////////////////////////////////////////////2X/FT2ugeYdWmvnEisSgW+9Ih8paNvrji2NYPb8ujaK"
611            ],
612            "column_roots": [
613              "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo/xEv//wkWzNtkcAZiZmSGU1Te6ERwUxTtTfHzoS4bv+",
614              "AAAAAAAAAAAAAAAAAAAAAAAAANjLtTOiQmHEwKMAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo9FOCNvCjA42xYCwHrlo48iPEXLaKt+d+JdErCIrQIi6",
615              "/////////////////////////////////////////////////////////////////////////////y2UErq/83uv433HekCWokxqcY4g+nMQn3tZn2Tr6v74",
616              "/////////////////////////////////////////////////////////////////////////////z6fKmbJTvfLYFlNuDWHn87vJb6V7n44MlCkxv1dyfT2"
617            ]
618          }
619        "#;
620
621        let row_proof: RowProof = serde_json::from_str(raw_row_proof).unwrap();
622        let dah: DataAvailabilityHeader = serde_json::from_str(raw_dah).unwrap();
623
624        row_proof.verify(dah.hash()).unwrap();
625    }
626
627    #[test]
628    fn row_proof_verify_correct() {
629        for square_width in [2, 4, 8, 16] {
630            let dah = random_dah(square_width);
631            let dah_root = dah.hash();
632
633            for start_row in 0..dah.square_width() - 1 {
634                for end_row in start_row..dah.square_width() {
635                    let proof = dah.row_proof(start_row..=end_row).unwrap();
636
637                    proof.verify(dah_root).unwrap()
638                }
639            }
640        }
641    }
642
643    #[test]
644    fn row_proof_verify_malformed() {
645        let dah = random_dah(16);
646        let dah_root = dah.hash();
647
648        let valid_proof = dah.row_proof(0..=1).unwrap();
649
650        // start_row > end_row
651        #[allow(clippy::reversed_empty_ranges)]
652        let proof = dah.row_proof(1..=0).unwrap();
653        proof.verify(dah_root).unwrap_err();
654
655        // length incorrect based on start and end
656        let mut proof = valid_proof.clone();
657        proof.end_row = 2;
658        proof.verify(dah_root).unwrap_err();
659
660        // incorrect amount of proofs
661        let mut proof = valid_proof.clone();
662        proof.proofs.push(proof.proofs[0].clone());
663        proof.verify(dah_root).unwrap_err();
664
665        // incorrect amount of roots
666        let mut proof = valid_proof.clone();
667        proof.row_roots.pop();
668        proof.verify(dah_root).unwrap_err();
669
670        // wrong proof order
671        let mut proof = valid_proof.clone();
672        proof.row_roots = proof.row_roots.into_iter().rev().collect();
673        proof.verify(dah_root).unwrap_err();
674    }
675
676    #[test]
677    fn test_serialize_row_proof_binary() {
678        let dah = random_dah(16);
679        let proof = dah.row_proof(0..=1).unwrap();
680        let serialized = postcard::to_allocvec(&proof).unwrap();
681        let deserialized: RowProof = postcard::from_bytes(&serialized).unwrap();
682        assert_eq!(proof, deserialized);
683    }
684
685    fn random_dah(square_width: u16) -> DataAvailabilityHeader {
686        let namespaces: Vec<_> = (0..square_width)
687            .map(|n| Namespace::new_v0(&[n as u8]).unwrap())
688            .collect();
689        let (row_roots, col_roots): (Vec<_>, Vec<_>) = namespaces
690            .iter()
691            .map(|&ns| {
692                let row = NamespacedHash::new(*ns, *ns, rand::random());
693                let col = NamespacedHash::new(
694                    **namespaces.first().unwrap(),
695                    **namespaces.last().unwrap(),
696                    rand::random(),
697                );
698                (row, col)
699            })
700            .unzip();
701
702        DataAvailabilityHeader::new(row_roots, col_roots, AppVersion::V2).unwrap()
703    }
704}