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 max_extended_square_width, MIN_EXTENDED_SQUARE_WIDTH,
15};
16use crate::eds::AxisType;
17use crate::hash::Hash;
18use crate::nmt::{NamespacedHash, NamespacedHashExt};
19use crate::{
20 bail_validation, bail_verification, validation_error, Error, ExtendedDataSquare, MerkleProof,
21 Result, ValidateBasicWithAppVersion, ValidationError,
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 column_root(&self, column: u16) -> Option<NamespacedHash> {
155 let column = usize::from(column);
156 self.column_roots.get(column).cloned()
157 }
158
159 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 pub fn square_width(&self) -> u16 {
191 self.row_roots
193 .len()
194 .try_into()
195 .expect("len is bigger than u16::MAX")
197 }
198
199 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 #[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 #[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 #[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 #[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 #[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 #[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#[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 pub fn row_roots(&self) -> &[NamespacedHash] {
357 &self.row_roots
358 }
359
360 pub fn verify(&self, root: Hash) -> Result<()> {
385 if self.row_roots.len() != self.proofs.len() {
386 bail_verification!("invalid row proof: row_roots.len() != proofs.len()");
387 }
388
389 if self.end_row < self.start_row {
390 bail_verification!(
391 "start_row ({}) > end_row ({})",
392 self.start_row,
393 self.end_row
394 );
395 }
396
397 let length = self.end_row - self.start_row + 1;
398 if length as usize != self.proofs.len() {
399 bail_verification!(
400 "length based on start_row and end_row ({}) != length of proofs ({})",
401 length,
402 self.proofs.len()
403 );
404 }
405
406 let Hash::Sha256(root) = root else {
407 bail_verification!("empty hash");
408 };
409
410 for (row_root, proof) in self.row_roots.iter().zip(self.proofs.iter()) {
411 proof.verify(row_root.to_array(), root)?;
412 }
413
414 Ok(())
415 }
416}
417
418impl Protobuf<RawRowProof> for RowProof {}
419
420impl TryFrom<RawRowProof> for RowProof {
421 type Error = Error;
422
423 fn try_from(value: RawRowProof) -> Result<Self> {
424 Ok(Self {
425 row_roots: value
426 .row_roots
427 .into_iter()
428 .map(|hash| NamespacedHash::from_raw(&hash))
429 .collect::<Result<_>>()?,
430 proofs: value
431 .proofs
432 .into_iter()
433 .map(TryInto::try_into)
434 .collect::<Result<_>>()?,
435 start_row: value
436 .start_row
437 .try_into()
438 .map_err(|_| validation_error!("start_row ({}) exceeds u16", value.start_row))?,
439 end_row: value
440 .end_row
441 .try_into()
442 .map_err(|_| validation_error!("end_row ({}) exceeds u16", value.end_row))?,
443 })
444 }
445}
446
447impl From<RowProof> for RawRowProof {
448 fn from(value: RowProof) -> Self {
449 Self {
450 row_roots: value
451 .row_roots
452 .into_iter()
453 .map(|hash| hash.to_vec())
454 .collect(),
455 proofs: value.proofs.into_iter().map(Into::into).collect(),
456 start_row: value.start_row as u32,
457 end_row: value.end_row as u32,
458 root: vec![],
459 }
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use crate::nmt::Namespace;
466
467 use super::*;
468
469 #[cfg(target_arch = "wasm32")]
470 use wasm_bindgen_test::wasm_bindgen_test as test;
471
472 fn sample_dah() -> DataAvailabilityHeader {
473 serde_json::from_str(r#"{
474 "row_roots": [
475 "//////////////////////////////////////7//////////////////////////////////////huZWOTTDmD36N1F75A9BshxNlRasCnNpQiWqIhdVHcU",
476 "/////////////////////////////////////////////////////////////////////////////5iieeroHBMfF+sER3JpvROIeEJZjbY+TRE0ntADQLL3"
477 ],
478 "column_roots": [
479 "//////////////////////////////////////7//////////////////////////////////////huZWOTTDmD36N1F75A9BshxNlRasCnNpQiWqIhdVHcU",
480 "/////////////////////////////////////////////////////////////////////////////5iieeroHBMfF+sER3JpvROIeEJZjbY+TRE0ntADQLL3"
481 ]
482 }"#).unwrap()
483 }
484
485 #[test]
486 fn validate_correct() {
487 let dah = sample_dah();
488
489 dah.validate_basic(AppVersion::V2).unwrap();
490 }
491
492 #[test]
493 fn validate_rows_and_cols_len_mismatch() {
494 let mut dah = sample_dah();
495 dah.row_roots.pop();
496
497 dah.validate_basic(AppVersion::V2).unwrap_err();
498 }
499
500 #[test]
501 fn validate_too_little_square() {
502 let mut dah = sample_dah();
503 dah.row_roots = dah
504 .row_roots
505 .into_iter()
506 .cycle()
507 .take(MIN_EXTENDED_SQUARE_WIDTH)
508 .collect();
509 dah.column_roots = dah
510 .column_roots
511 .into_iter()
512 .cycle()
513 .take(MIN_EXTENDED_SQUARE_WIDTH)
514 .collect();
515
516 dah.validate_basic(AppVersion::V2).unwrap();
517
518 dah.row_roots.pop();
519 dah.column_roots.pop();
520
521 dah.validate_basic(AppVersion::V2).unwrap_err();
522 }
523
524 #[test]
525 fn validate_too_big_square() {
526 let mut dah = sample_dah();
527 dah.row_roots = dah
528 .row_roots
529 .into_iter()
530 .cycle()
531 .take(max_extended_square_width(AppVersion::V2))
532 .collect();
533 dah.column_roots = dah
534 .column_roots
535 .into_iter()
536 .cycle()
537 .take(max_extended_square_width(AppVersion::V2))
538 .collect();
539
540 dah.validate_basic(AppVersion::V2).unwrap();
541
542 dah.row_roots.push(dah.row_roots[0].clone());
543 dah.column_roots.push(dah.column_roots[0].clone());
544
545 dah.validate_basic(AppVersion::V2).unwrap_err();
546 }
547
548 #[test]
549 fn row_proof_serde() {
550 let raw_row_proof = r#"
551 {
552 "end_row": 1,
553 "proofs": [
554 {
555 "aunts": [
556 "Ch+9PsBdsN5YUt8nvAmjdOAIcVdfmPAEUNmCA8KBe5A=",
557 "ojjC9H5JG/7OOrt5BzBXs/3w+n1LUI/0YR0d+RSfleU=",
558 "d6bMQbLTBfZGvqXOW9MPqRM+fTB2/wLJx6CkLc8glCI="
559 ],
560 "index": 0,
561 "leaf_hash": "nOpM3A3d0JYOmNaaI5BFAeKPGwQ90TqmM/kx+sHr79s=",
562 "total": 8
563 },
564 {
565 "aunts": [
566 "nOpM3A3d0JYOmNaaI5BFAeKPGwQ90TqmM/kx+sHr79s=",
567 "ojjC9H5JG/7OOrt5BzBXs/3w+n1LUI/0YR0d+RSfleU=",
568 "d6bMQbLTBfZGvqXOW9MPqRM+fTB2/wLJx6CkLc8glCI="
569 ],
570 "index": 1,
571 "leaf_hash": "Ch+9PsBdsN5YUt8nvAmjdOAIcVdfmPAEUNmCA8KBe5A=",
572 "total": 8
573 }
574 ],
575 "row_roots": [
576 "000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000D8CBB533A24261C4C0A3D37F1CBFB6F4C5EA031472EBA390D482637933874AA0A2B9735E67629993852D",
577 "00000000000000000000000000000000000000D8CBB533A24261C4C0A300000000000000000000000000000000000000D8CBB533A24261C4C0A37E409334CCB1125C793EC040741137634C148F089ACB06BFFF4C1C4CA2CBBA8E"
578 ],
579 "start_row": 0
580 }
581 "#;
582 let raw_dah = r#"
583 {
584 "row_roots": [
585 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo9N/HL+29MXqAxRy66OQ1IJjeTOHSqCiuXNeZ2KZk4Ut",
586 "AAAAAAAAAAAAAAAAAAAAAAAAANjLtTOiQmHEwKMAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo35AkzTMsRJceT7AQHQRN2NMFI8ImssGv/9MHEyiy7qO",
587 "/////////////////////////////////////////////////////////////////////////////7mTwL+NxdxcYBd89/wRzW2k9vRkQehZiXsuqZXHy89X",
588 "/////////////////////////////////////////////////////////////////////////////2X/FT2ugeYdWmvnEisSgW+9Ih8paNvrji2NYPb8ujaK"
589 ],
590 "column_roots": [
591 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo/xEv//wkWzNtkcAZiZmSGU1Te6ERwUxTtTfHzoS4bv+",
592 "AAAAAAAAAAAAAAAAAAAAAAAAANjLtTOiQmHEwKMAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo9FOCNvCjA42xYCwHrlo48iPEXLaKt+d+JdErCIrQIi6",
593 "/////////////////////////////////////////////////////////////////////////////y2UErq/83uv433HekCWokxqcY4g+nMQn3tZn2Tr6v74",
594 "/////////////////////////////////////////////////////////////////////////////z6fKmbJTvfLYFlNuDWHn87vJb6V7n44MlCkxv1dyfT2"
595 ]
596 }
597 "#;
598
599 let row_proof: RowProof = serde_json::from_str(raw_row_proof).unwrap();
600 let dah: DataAvailabilityHeader = serde_json::from_str(raw_dah).unwrap();
601
602 row_proof.verify(dah.hash()).unwrap();
603 }
604
605 #[test]
606 fn row_proof_verify_correct() {
607 for square_width in [2, 4, 8, 16] {
608 let dah = random_dah(square_width);
609 let dah_root = dah.hash();
610
611 for start_row in 0..dah.square_width() - 1 {
612 for end_row in start_row..dah.square_width() {
613 let proof = dah.row_proof(start_row..=end_row).unwrap();
614
615 proof.verify(dah_root).unwrap()
616 }
617 }
618 }
619 }
620
621 #[test]
622 fn row_proof_verify_malformed() {
623 let dah = random_dah(16);
624 let dah_root = dah.hash();
625
626 let valid_proof = dah.row_proof(0..=1).unwrap();
627
628 #[allow(clippy::reversed_empty_ranges)]
630 let proof = dah.row_proof(1..=0).unwrap();
631 proof.verify(dah_root).unwrap_err();
632
633 let mut proof = valid_proof.clone();
635 proof.end_row = 2;
636 proof.verify(dah_root).unwrap_err();
637
638 let mut proof = valid_proof.clone();
640 proof.proofs.push(proof.proofs[0].clone());
641 proof.verify(dah_root).unwrap_err();
642
643 let mut proof = valid_proof.clone();
645 proof.row_roots.pop();
646 proof.verify(dah_root).unwrap_err();
647
648 let mut proof = valid_proof.clone();
650 proof.row_roots = proof.row_roots.into_iter().rev().collect();
651 proof.verify(dah_root).unwrap_err();
652 }
653
654 fn random_dah(square_width: u16) -> DataAvailabilityHeader {
655 let namespaces: Vec<_> = (0..square_width)
656 .map(|n| Namespace::new_v0(&[n as u8]).unwrap())
657 .collect();
658 let (row_roots, col_roots): (Vec<_>, Vec<_>) = namespaces
659 .iter()
660 .map(|&ns| {
661 let row = NamespacedHash::new(*ns, *ns, rand::random());
662 let col = NamespacedHash::new(
663 **namespaces.first().unwrap(),
664 **namespaces.last().unwrap(),
665 rand::random(),
666 );
667 (row, col)
668 })
669 .unzip();
670
671 DataAvailabilityHeader::new(row_roots, col_roots, AppVersion::V2).unwrap()
672 }
673}