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#[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 proofs(&self) -> &[MerkleProof] {
362 &self.proofs
363 }
364
365 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 #[allow(clippy::reversed_empty_ranges)]
635 let proof = dah.row_proof(1..=0).unwrap();
636 proof.verify(dah_root).unwrap_err();
637
638 let mut proof = valid_proof.clone();
640 proof.end_row = 2;
641 proof.verify(dah_root).unwrap_err();
642
643 let mut proof = valid_proof.clone();
645 proof.proofs.push(proof.proofs[0].clone());
646 proof.verify(dah_root).unwrap_err();
647
648 let mut proof = valid_proof.clone();
650 proof.row_roots.pop();
651 proof.verify(dah_root).unwrap_err();
652
653 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}