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::data_availability_header::MIN_EXTENDED_SQUARE_WIDTH;
13use crate::eds::AxisType;
14use crate::hash::Hash;
15use crate::nmt::{Namespace, NamespacedHash, NamespacedHashExt, NamespacedSha2Hasher};
16use crate::{
17 Error, ExtendedDataSquare, MerkleProof, Result, ValidateBasic, ValidationError,
18 bail_validation, bail_verification, validation_error,
19};
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(
57 try_from = "RawDataAvailabilityHeader",
58 into = "RawDataAvailabilityHeader"
59)]
60#[cfg_attr(
61 all(feature = "wasm-bindgen", target_arch = "wasm32"),
62 wasm_bindgen(inspectable)
63)]
64pub struct DataAvailabilityHeader {
65 row_roots: Vec<NamespacedHash>,
67 column_roots: Vec<NamespacedHash>,
69}
70
71impl DataAvailabilityHeader {
72 pub fn new(row_roots: Vec<NamespacedHash>, column_roots: Vec<NamespacedHash>) -> Result<Self> {
74 let dah = DataAvailabilityHeader::new_unchecked(row_roots, column_roots);
75 dah.validate_basic()?;
76 Ok(dah)
77 }
78
79 pub fn new_unchecked(
83 row_roots: Vec<NamespacedHash>,
84 column_roots: Vec<NamespacedHash>,
85 ) -> Self {
86 DataAvailabilityHeader {
87 row_roots,
88 column_roots,
89 }
90 }
91
92 pub fn from_eds(eds: &ExtendedDataSquare) -> Self {
94 let square_width = eds.square_width();
95
96 let mut dah = DataAvailabilityHeader {
97 row_roots: Vec::with_capacity(square_width.into()),
98 column_roots: Vec::with_capacity(square_width.into()),
99 };
100
101 for i in 0..square_width {
102 let row_root = eds
103 .row_nmt(i)
104 .expect("EDS validated on construction")
105 .root();
106 dah.row_roots.push(row_root);
107
108 let column_root = eds
109 .column_nmt(i)
110 .expect("EDS validated on construction")
111 .root();
112 dah.column_roots.push(column_root);
113 }
114
115 dah
116 }
117
118 pub fn root(&self, axis: AxisType, index: u16) -> Option<NamespacedHash> {
120 match axis {
121 AxisType::Col => self.column_root(index),
122 AxisType::Row => self.row_root(index),
123 }
124 }
125
126 pub fn row_roots(&self) -> &[NamespacedHash] {
130 &self.row_roots
131 }
132
133 pub fn column_roots(&self) -> &[NamespacedHash] {
137 &self.column_roots
138 }
139
140 pub fn row_root(&self, row: u16) -> Option<NamespacedHash> {
142 let row = usize::from(row);
143 self.row_roots.get(row).cloned()
144 }
145
146 pub fn row_contains(&self, row: u16, namespace: Namespace) -> Result<bool> {
148 let row_root = self
149 .row_root(row)
150 .ok_or(Error::IndexOutOfRange(row as usize, self.row_roots.len()))?;
151 Ok(row_root.contains::<NamespacedSha2Hasher>(*namespace))
152 }
153
154 pub fn column_root(&self, column: u16) -> Option<NamespacedHash> {
156 let column = usize::from(column);
157 self.column_roots.get(column).cloned()
158 }
159
160 pub fn column_contains(&self, column: u16, namespace: Namespace) -> Result<bool> {
162 let column_root = self.column_root(column).ok_or(Error::IndexOutOfRange(
163 column as usize,
164 self.column_roots.len(),
165 ))?;
166 Ok(column_root.contains::<NamespacedSha2Hasher>(*namespace))
167 }
168
169 pub fn hash(&self) -> Hash {
187 let all_roots: Vec<_> = self
188 .row_roots
189 .iter()
190 .chain(self.column_roots.iter())
191 .map(|root| root.to_array())
192 .collect();
193
194 Hash::Sha256(simple_hash_from_byte_vectors::<Sha256>(&all_roots))
195 }
196
197 pub fn square_width(&self) -> u16 {
201 self.row_roots
203 .len()
204 .try_into()
205 .expect("len is bigger than u16::MAX")
207 }
208
209 pub fn row_proof(&self, rows: RangeInclusive<u16>) -> Result<RowProof> {
211 let all_roots: Vec<_> = self
212 .row_roots
213 .iter()
214 .chain(self.column_roots.iter())
215 .map(|root| root.to_array())
216 .collect();
217
218 let start_row = *rows.start();
219 let end_row = *rows.end();
220 let mut proofs = Vec::with_capacity(rows.len());
221 let mut row_roots = Vec::with_capacity(rows.len());
222
223 for idx in rows {
224 proofs.push(MerkleProof::new(idx as usize, &all_roots)?.0);
225 let row = self
226 .row_root(idx)
227 .ok_or(Error::IndexOutOfRange(idx as usize, self.row_roots.len()))?;
228 row_roots.push(row);
229 }
230
231 Ok(RowProof {
232 proofs,
233 row_roots,
234 start_row,
235 end_row,
236 })
237 }
238}
239
240#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
241#[wasm_bindgen]
242impl DataAvailabilityHeader {
243 #[wasm_bindgen(js_name = rowRoots)]
245 pub fn js_row_roots(&self) -> Result<js_sys::Array, serde_wasm_bindgen::Error> {
246 self.row_roots()
247 .iter()
248 .map(|h| serde_wasm_bindgen::to_value(&h))
249 .collect()
250 }
251
252 #[wasm_bindgen(js_name = columnRoots)]
254 pub fn js_column_roots(&self) -> Result<js_sys::Array, serde_wasm_bindgen::Error> {
255 self.column_roots()
256 .iter()
257 .map(|h| serde_wasm_bindgen::to_value(&h))
258 .collect()
259 }
260
261 #[wasm_bindgen(js_name = rowRoot)]
263 pub fn js_row_root(&self, row: u16) -> Result<JsValue, serde_wasm_bindgen::Error> {
264 serde_wasm_bindgen::to_value(&self.row_root(row))
265 }
266
267 #[wasm_bindgen(js_name = columnRoot)]
269 pub fn js_column_root(&self, column: u16) -> Result<JsValue, serde_wasm_bindgen::Error> {
270 serde_wasm_bindgen::to_value(&self.column_root(column))
271 }
272
273 #[wasm_bindgen(js_name = hash)]
277 pub fn js_hash(&self) -> Result<JsValue, serde_wasm_bindgen::Error> {
278 serde_wasm_bindgen::to_value(&self.hash())
279 }
280
281 #[wasm_bindgen(js_name = squareWidth)]
283 pub fn js_square_width(&self) -> u16 {
284 self.square_width()
285 }
286}
287
288impl Protobuf<RawDataAvailabilityHeader> for DataAvailabilityHeader {}
289
290impl TryFrom<RawDataAvailabilityHeader> for DataAvailabilityHeader {
291 type Error = Error;
292
293 fn try_from(value: RawDataAvailabilityHeader) -> Result<Self, Self::Error> {
294 Ok(DataAvailabilityHeader {
295 row_roots: value
296 .row_roots
297 .iter()
298 .map(|bytes| NamespacedHash::from_raw(bytes))
299 .collect::<Result<Vec<_>>>()?,
300 column_roots: value
301 .column_roots
302 .iter()
303 .map(|bytes| NamespacedHash::from_raw(bytes))
304 .collect::<Result<Vec<_>>>()?,
305 })
306 }
307}
308
309impl From<DataAvailabilityHeader> for RawDataAvailabilityHeader {
310 fn from(value: DataAvailabilityHeader) -> RawDataAvailabilityHeader {
311 RawDataAvailabilityHeader {
312 row_roots: value.row_roots.iter().map(|hash| hash.to_vec()).collect(),
313 column_roots: value
314 .column_roots
315 .iter()
316 .map(|hash| hash.to_vec())
317 .collect(),
318 }
319 }
320}
321
322impl ValidateBasic for DataAvailabilityHeader {
323 fn validate_basic(&self) -> Result<(), ValidationError> {
324 if self.column_roots.len() != self.row_roots.len() {
325 bail_validation!(
326 "column_roots len ({}) != row_roots len ({})",
327 self.column_roots.len(),
328 self.row_roots.len(),
329 )
330 }
331
332 if self.row_roots.len() < MIN_EXTENDED_SQUARE_WIDTH {
333 bail_validation!(
334 "row_roots len ({}) < minimum ({})",
335 self.row_roots.len(),
336 MIN_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().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().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().unwrap();
522
523 dah.row_roots.pop();
524 dah.column_roots.pop();
525
526 dah.validate_basic().unwrap_err();
527 }
528
529 #[test]
530 fn row_proof_serde() {
531 let raw_row_proof = r#"
532 {
533 "end_row": 1,
534 "proofs": [
535 {
536 "aunts": [
537 "Ch+9PsBdsN5YUt8nvAmjdOAIcVdfmPAEUNmCA8KBe5A=",
538 "ojjC9H5JG/7OOrt5BzBXs/3w+n1LUI/0YR0d+RSfleU=",
539 "d6bMQbLTBfZGvqXOW9MPqRM+fTB2/wLJx6CkLc8glCI="
540 ],
541 "index": 0,
542 "leaf_hash": "nOpM3A3d0JYOmNaaI5BFAeKPGwQ90TqmM/kx+sHr79s=",
543 "total": 8
544 },
545 {
546 "aunts": [
547 "nOpM3A3d0JYOmNaaI5BFAeKPGwQ90TqmM/kx+sHr79s=",
548 "ojjC9H5JG/7OOrt5BzBXs/3w+n1LUI/0YR0d+RSfleU=",
549 "d6bMQbLTBfZGvqXOW9MPqRM+fTB2/wLJx6CkLc8glCI="
550 ],
551 "index": 1,
552 "leaf_hash": "Ch+9PsBdsN5YUt8nvAmjdOAIcVdfmPAEUNmCA8KBe5A=",
553 "total": 8
554 }
555 ],
556 "row_roots": [
557 "000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000D8CBB533A24261C4C0A3D37F1CBFB6F4C5EA031472EBA390D482637933874AA0A2B9735E67629993852D",
558 "00000000000000000000000000000000000000D8CBB533A24261C4C0A300000000000000000000000000000000000000D8CBB533A24261C4C0A37E409334CCB1125C793EC040741137634C148F089ACB06BFFF4C1C4CA2CBBA8E"
559 ],
560 "start_row": 0
561 }
562 "#;
563 let raw_dah = r#"
564 {
565 "row_roots": [
566 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo9N/HL+29MXqAxRy66OQ1IJjeTOHSqCiuXNeZ2KZk4Ut",
567 "AAAAAAAAAAAAAAAAAAAAAAAAANjLtTOiQmHEwKMAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo35AkzTMsRJceT7AQHQRN2NMFI8ImssGv/9MHEyiy7qO",
568 "/////////////////////////////////////////////////////////////////////////////7mTwL+NxdxcYBd89/wRzW2k9vRkQehZiXsuqZXHy89X",
569 "/////////////////////////////////////////////////////////////////////////////2X/FT2ugeYdWmvnEisSgW+9Ih8paNvrji2NYPb8ujaK"
570 ],
571 "column_roots": [
572 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo/xEv//wkWzNtkcAZiZmSGU1Te6ERwUxTtTfHzoS4bv+",
573 "AAAAAAAAAAAAAAAAAAAAAAAAANjLtTOiQmHEwKMAAAAAAAAAAAAAAAAAAAAAAAAA2Mu1M6JCYcTAo9FOCNvCjA42xYCwHrlo48iPEXLaKt+d+JdErCIrQIi6",
574 "/////////////////////////////////////////////////////////////////////////////y2UErq/83uv433HekCWokxqcY4g+nMQn3tZn2Tr6v74",
575 "/////////////////////////////////////////////////////////////////////////////z6fKmbJTvfLYFlNuDWHn87vJb6V7n44MlCkxv1dyfT2"
576 ]
577 }
578 "#;
579
580 let row_proof: RowProof = serde_json::from_str(raw_row_proof).unwrap();
581 let dah: DataAvailabilityHeader = serde_json::from_str(raw_dah).unwrap();
582
583 row_proof.verify(dah.hash()).unwrap();
584 }
585
586 #[test]
587 fn row_proof_verify_correct() {
588 for square_width in [2, 4, 8, 16] {
589 let dah = random_dah(square_width);
590 let dah_root = dah.hash();
591
592 for start_row in 0..dah.square_width() - 1 {
593 for end_row in start_row..dah.square_width() {
594 let proof = dah.row_proof(start_row..=end_row).unwrap();
595
596 proof.verify(dah_root).unwrap()
597 }
598 }
599 }
600 }
601
602 #[test]
603 fn row_proof_verify_malformed() {
604 let dah = random_dah(16);
605 let dah_root = dah.hash();
606
607 let valid_proof = dah.row_proof(0..=1).unwrap();
608
609 #[allow(clippy::reversed_empty_ranges)]
611 let proof = dah.row_proof(1..=0).unwrap();
612 proof.verify(dah_root).unwrap_err();
613
614 let mut proof = valid_proof.clone();
616 proof.end_row = 2;
617 proof.verify(dah_root).unwrap_err();
618
619 let mut proof = valid_proof.clone();
621 proof.proofs.push(proof.proofs[0].clone());
622 proof.verify(dah_root).unwrap_err();
623
624 let mut proof = valid_proof.clone();
626 proof.row_roots.pop();
627 proof.verify(dah_root).unwrap_err();
628
629 let mut proof = valid_proof.clone();
631 proof.row_roots = proof.row_roots.into_iter().rev().collect();
632 proof.verify(dah_root).unwrap_err();
633 }
634
635 #[test]
636 fn test_serialize_row_proof_binary() {
637 let dah = random_dah(16);
638 let proof = dah.row_proof(0..=1).unwrap();
639 let serialized = postcard::to_allocvec(&proof).unwrap();
640 let deserialized: RowProof = postcard::from_bytes(&serialized).unwrap();
641 assert_eq!(proof, deserialized);
642 }
643
644 fn random_dah(square_width: u16) -> DataAvailabilityHeader {
645 let namespaces: Vec<_> = (0..square_width)
646 .map(|n| Namespace::new_v0(&[n as u8]).unwrap())
647 .collect();
648 let (row_roots, col_roots): (Vec<_>, Vec<_>) = namespaces
649 .iter()
650 .map(|&ns| {
651 let row = NamespacedHash::new(*ns, *ns, rand::random());
652 let col = NamespacedHash::new(
653 **namespaces.first().unwrap(),
654 **namespaces.last().unwrap(),
655 rand::random(),
656 );
657 (row, col)
658 })
659 .unzip();
660
661 DataAvailabilityHeader::new(row_roots, col_roots).unwrap()
662 }
663}