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#[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 row_roots: Vec<NamespacedHash>,
70 column_roots: Vec<NamespacedHash>,
72}
73
74impl DataAvailabilityHeader {
75 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 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 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 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 pub fn row_roots(&self) -> &[NamespacedHash] {
137 &self.row_roots
138 }
139
140 pub fn column_roots(&self) -> &[NamespacedHash] {
144 &self.column_roots
145 }
146
147 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 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 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 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 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 pub fn square_width(&self) -> u16 {
208 self.row_roots
210 .len()
211 .try_into()
212 .expect("len is bigger than u16::MAX")
214 }
215
216 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 #[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 #[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 #[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 #[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 #[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 #[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#[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 pub fn row_roots(&self) -> &[NamespacedHash] {
374 &self.row_roots
375 }
376
377 pub fn proofs(&self) -> &[MerkleProof] {
379 &self.proofs
380 }
381
382 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 #[allow(clippy::reversed_empty_ranges)]
652 let proof = dah.row_proof(1..=0).unwrap();
653 proof.verify(dah_root).unwrap_err();
654
655 let mut proof = valid_proof.clone();
657 proof.end_row = 2;
658 proof.verify(dah_root).unwrap_err();
659
660 let mut proof = valid_proof.clone();
662 proof.proofs.push(proof.proofs[0].clone());
663 proof.verify(dah_root).unwrap_err();
664
665 let mut proof = valid_proof.clone();
667 proof.row_roots.pop();
668 proof.verify(dah_root).unwrap_err();
669
670 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}