1use std::fmt::{Display, Formatter};
2#[cfg(any(
3 not(any(target_arch = "wasm32", target_arch = "riscv32")),
4 feature = "wasm-bindgen"
5))]
6use std::time::Duration;
7
8use celestia_proto::header::pb::ExtendedHeader as RawExtendedHeader;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
11use serde_wasm_bindgen::to_value;
12use tendermint::block::header::Header;
13use tendermint::block::{Commit, Height};
14use tendermint::chain::id::Id;
15use tendermint::{validator, Time};
16use tendermint_proto::Protobuf;
17#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
18use wasm_bindgen::prelude::*;
19
20use crate::consts::appconsts::AppVersion;
21use crate::hash::Hash;
22use crate::trust_level::DEFAULT_TRUST_LEVEL;
23use crate::validator_set::ValidatorSetExt;
24use crate::{
25 bail_validation, bail_verification, DataAvailabilityHeader, Error, Result, ValidateBasic,
26 ValidateBasicWithAppVersion,
27};
28
29pub type Validator = validator::Info;
31pub type ValidatorSet = validator::Set;
33
34#[cfg(any(
35 not(any(target_arch = "wasm32", target_arch = "riscv32")),
36 feature = "wasm-bindgen"
37))]
38const VERIFY_CLOCK_DRIFT: Duration = Duration::from_secs(10);
39
40#[derive(Debug, Clone, PartialEq, Eq)]
68#[cfg_attr(
69 all(feature = "wasm-bindgen", target_arch = "wasm32"),
70 wasm_bindgen(inspectable)
71)]
72pub struct ExtendedHeader {
73 #[cfg_attr(
75 all(feature = "wasm-bindgen", target_arch = "wasm32"),
76 wasm_bindgen(skip)
77 )]
78 pub header: Header,
79 #[cfg_attr(
81 all(feature = "wasm-bindgen", target_arch = "wasm32"),
82 wasm_bindgen(skip)
83 )]
84 pub commit: Commit,
85 #[cfg_attr(
87 all(feature = "wasm-bindgen", target_arch = "wasm32"),
88 wasm_bindgen(skip)
89 )]
90 pub validator_set: ValidatorSet,
91 #[cfg_attr(
93 all(feature = "wasm-bindgen", target_arch = "wasm32"),
94 wasm_bindgen(getter_with_clone)
95 )]
96 pub dah: DataAvailabilityHeader,
97}
98
99impl Display for ExtendedHeader {
100 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
101 write!(f, "hash: {}; height: {}", self.hash(), self.height())
102 }
103}
104
105impl ExtendedHeader {
106 pub fn decode_and_validate(bytes: &[u8]) -> Result<Self> {
108 let header = ExtendedHeader::decode(bytes)?;
109 header.validate()?;
110 Ok(header)
111 }
112
113 pub fn app_version(&self) -> Result<AppVersion> {
120 let app_version = self.header.version.app;
121 AppVersion::from_u64(app_version).ok_or(Error::UnsupportedAppVersion(app_version))
122 }
123
124 pub fn chain_id(&self) -> &Id {
126 &self.header.chain_id
127 }
128
129 pub fn height(&self) -> Height {
131 self.header.height
132 }
133
134 pub fn time(&self) -> Time {
136 self.header.time
137 }
138
139 pub fn hash(&self) -> Hash {
141 self.commit.block_id.hash
142 }
143
144 pub fn last_header_hash(&self) -> Hash {
146 self.header
147 .last_block_id
148 .map(|block_id| block_id.hash)
149 .unwrap_or_default()
150 }
151
152 pub fn validate(&self) -> Result<()> {
177 self.header.validate_basic()?;
178 self.commit.validate_basic()?;
179 self.validator_set.validate_basic()?;
180
181 if self.validator_set.hash() != self.header.validators_hash {
183 bail_validation!(
184 "validator_set hash ({}) != header validators_hash ({})",
185 self.validator_set.hash(),
186 self.header.validators_hash,
187 )
188 }
189
190 if self.dah.hash() != self.header.data_hash.unwrap_or_default() {
192 bail_validation!(
193 "dah hash ({}) != header dah hash ({})",
194 self.dah.hash(),
195 self.header.data_hash.unwrap_or_default(),
196 )
197 }
198
199 if self.commit.height != self.height() {
201 bail_validation!(
202 "commit height ({}) != header height ({})",
203 self.commit.height,
204 self.height(),
205 )
206 }
207
208 if self.commit.block_id.hash != self.header.hash() {
209 bail_validation!(
210 "commit block_id hash ({}) != header hash ({})",
211 self.commit.block_id.hash,
212 self.header.hash(),
213 )
214 }
215
216 self.validator_set.verify_commit_light(
217 &self.header.chain_id,
218 &self.height(),
219 &self.commit,
220 )?;
221
222 let app_version = self.app_version()?;
223 self.dah.validate_basic(app_version)?;
224
225 Ok(())
226 }
227
228 pub fn verify(&self, untrusted: &ExtendedHeader) -> Result<()> {
240 if untrusted.height() <= self.height() {
241 bail_verification!(
242 "untrusted header height({}) <= current trusted header({})",
243 untrusted.height(),
244 self.height()
245 );
246 }
247
248 if untrusted.chain_id() != self.chain_id() {
249 bail_verification!(
250 "untrusted header has different chain {}, not {}",
251 untrusted.chain_id(),
252 self.chain_id()
253 );
254 }
255
256 if !untrusted.time().after(self.time()) {
257 bail_verification!(
258 "untrusted header time ({}) must be after current trusted header ({})",
259 untrusted.time(),
260 self.time()
261 );
262 }
263
264 #[cfg(any(
265 not(any(target_arch = "wasm32", target_arch = "riscv32")),
266 feature = "wasm-bindgen"
267 ))]
268 {
269 let now = Time::now();
270 let valid_until = now.checked_add(VERIFY_CLOCK_DRIFT).unwrap();
271
272 if !untrusted.time().before(valid_until) {
273 bail_verification!(
274 "new untrusted header has a time from the future {} (now: {}, clock_drift: {:?})",
275 untrusted.time(),
276 now,
277 VERIFY_CLOCK_DRIFT
278 );
279 }
280 }
281
282 if self.height().increment() == untrusted.height() {
286 if untrusted.header.validators_hash != self.header.next_validators_hash {
287 bail_verification!(
288 "expected old header next validators ({}) to match those from new header ({})",
289 self.header.next_validators_hash,
290 untrusted.header.validators_hash,
291 );
292 }
293
294 if untrusted.last_header_hash() != self.hash() {
295 bail_verification!(
296 "expected new header to point to last header hash ({}), but got {}",
297 self.hash(),
298 untrusted.last_header_hash()
299 );
300 }
301
302 return Ok(());
303 }
304
305 self.validator_set.verify_commit_light_trusting(
306 self.chain_id(),
307 &untrusted.commit,
308 DEFAULT_TRUST_LEVEL,
309 )?;
310
311 Ok(())
312 }
313
314 pub fn verify_range(&self, untrusted: &[ExtendedHeader]) -> Result<()> {
344 let mut trusted = self;
345
346 for (i, untrusted) in untrusted.iter().enumerate() {
347 if i != 0 && trusted.height().increment() != untrusted.height() {
351 bail_verification!(
352 "untrusted header height ({}) not adjacent to the current trusted ({})",
353 untrusted.height(),
354 trusted.height(),
355 );
356 }
357
358 trusted.verify(untrusted)?;
359 trusted = untrusted;
360 }
361
362 Ok(())
363 }
364
365 pub fn verify_adjacent_range(&self, untrusted: &[ExtendedHeader]) -> Result<()> {
401 if untrusted.is_empty() {
402 return Ok(());
403 }
404
405 if self.height().increment() != untrusted[0].height() {
407 bail_verification!(
408 "untrusted header height ({}) not adjacent to the current trusted ({})",
409 untrusted[0].height(),
410 self.height(),
411 );
412 }
413
414 self.verify_range(untrusted)
415 }
416}
417
418#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
419#[wasm_bindgen]
420impl ExtendedHeader {
421 #[wasm_bindgen(js_name = clone)]
423 pub fn js_clone(&self) -> Self {
424 self.clone()
425 }
426
427 #[wasm_bindgen(js_name = height)]
429 pub fn js_height(&self) -> u64 {
430 self.height().value()
431 }
432
433 #[wasm_bindgen(js_name = time)]
435 pub fn js_time(&self) -> Result<f64, JsValue> {
436 Ok(self
437 .time()
438 .duration_since(Time::unix_epoch())
439 .map_err(|e| JsError::new(&e.to_string()))?
440 .as_secs_f64()
441 * 1000.0)
442 }
443
444 #[wasm_bindgen(js_name = hash)]
446 pub fn js_hash(&self) -> String {
447 self.hash().to_string()
448 }
449
450 #[wasm_bindgen(js_name = previousHeaderHash)]
452 pub fn js_previous_header_hash(&self) -> String {
453 self.last_header_hash().to_string()
454 }
455
456 #[wasm_bindgen(getter, js_name = header)]
458 pub fn js_header(&self) -> Result<JsValue, serde_wasm_bindgen::Error> {
459 to_value(&self.header)
460 }
461
462 #[wasm_bindgen(getter, js_name = commit)]
464 pub fn js_commit(&self) -> Result<JsValue, serde_wasm_bindgen::Error> {
465 to_value(&self.commit)
466 }
467
468 #[wasm_bindgen(getter, js_name = validatorSet)]
470 pub fn js_validator_set(&self) -> Result<JsValue, serde_wasm_bindgen::Error> {
471 to_value(&self.validator_set)
472 }
473
474 #[wasm_bindgen(js_name = validate)]
476 pub fn js_validate(&self) -> Result<(), JsValue> {
477 Ok(self.validate()?)
478 }
479
480 #[wasm_bindgen(js_name = verify)]
488 pub fn js_verify(&self, untrusted: &ExtendedHeader) -> Result<(), JsValue> {
489 Ok(self.verify(untrusted)?)
490 }
491
492 #[wasm_bindgen(js_name = verifyRange)]
512 pub fn js_verify_range(&self, untrusted: Vec<ExtendedHeader>) -> Result<(), JsValue> {
513 Ok(self.verify_range(&untrusted)?)
514 }
515
516 #[wasm_bindgen(js_name = verifyAdjacentRange)]
536 pub fn js_verify_adjacent_range(&self, untrusted: Vec<ExtendedHeader>) -> Result<(), JsValue> {
537 Ok(self.verify_adjacent_range(&untrusted)?)
538 }
539}
540
541impl Protobuf<RawExtendedHeader> for ExtendedHeader {}
542
543impl TryFrom<RawExtendedHeader> for ExtendedHeader {
544 type Error = Error;
545
546 fn try_from(value: RawExtendedHeader) -> Result<Self, Self::Error> {
547 let header = value.header.ok_or(Error::MissingHeader)?.try_into()?;
548 let commit = value.commit.ok_or(Error::MissingCommit)?.try_into()?;
549 let validator_set = value
550 .validator_set
551 .ok_or(Error::MissingValidatorSet)?
552 .try_into()?;
553 let dah = value
554 .dah
555 .ok_or(Error::MissingDataAvailabilityHeader)?
556 .try_into()?;
557
558 Ok(ExtendedHeader {
559 header,
560 commit,
561 validator_set,
562 dah,
563 })
564 }
565}
566
567impl From<ExtendedHeader> for RawExtendedHeader {
568 fn from(value: ExtendedHeader) -> RawExtendedHeader {
569 RawExtendedHeader {
570 header: Some(value.header.into()),
571 commit: Some(value.commit.into()),
572 validator_set: Some(value.validator_set.into()),
573 dah: Some(value.dah.into()),
574 }
575 }
576}
577
578mod custom_serde {
579 use celestia_proto::celestia::core::v1::da::DataAvailabilityHeader;
580 use celestia_proto::header::pb::ExtendedHeader as RawExtendedHeader;
581 use serde::{Deserialize, Serialize};
582 use tendermint_proto::v0_34::types::Commit as RawCommit;
583 use tendermint_proto::v0_34::types::{BlockId, CommitSig, Header, ValidatorSet};
584
585 #[derive(Deserialize, Serialize)]
586 pub(super) struct SerdeExtendedHeader {
587 header: Option<Header>,
588 commit: Option<SerdeCommit>,
589 validator_set: Option<ValidatorSet>,
590 dah: Option<DataAvailabilityHeader>,
591 }
592
593 #[derive(Deserialize, Serialize)]
594 pub(super) struct SerdeCommit {
595 height: i64,
596 round: i32,
597 block_id: Option<BlockId>,
598 #[serde(with = "tendermint_proto::serializers::nullable")]
599 signatures: Vec<CommitSig>,
600 }
601
602 impl From<RawExtendedHeader> for SerdeExtendedHeader {
603 fn from(value: RawExtendedHeader) -> Self {
604 SerdeExtendedHeader {
605 header: value.header,
606 commit: value.commit.map(|commit| commit.into()),
607 validator_set: value.validator_set,
608 dah: value.dah,
609 }
610 }
611 }
612
613 impl From<SerdeExtendedHeader> for RawExtendedHeader {
614 fn from(value: SerdeExtendedHeader) -> Self {
615 RawExtendedHeader {
616 header: value.header,
617 commit: value.commit.map(|commit| commit.into()),
618 validator_set: value.validator_set,
619 dah: value.dah,
620 }
621 }
622 }
623
624 impl From<RawCommit> for SerdeCommit {
625 fn from(value: RawCommit) -> Self {
626 SerdeCommit {
627 height: value.height,
628 round: value.round,
629 block_id: value.block_id,
630 signatures: value.signatures,
631 }
632 }
633 }
634
635 impl From<SerdeCommit> for RawCommit {
636 fn from(value: SerdeCommit) -> Self {
637 RawCommit {
638 height: value.height,
639 round: value.round,
640 block_id: value.block_id,
641 signatures: value.signatures,
642 }
643 }
644 }
645}
646
647impl Serialize for ExtendedHeader {
648 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
649 where
650 S: Serializer,
651 {
652 let pb: RawExtendedHeader = self.clone().into();
653 let custom_ser: custom_serde::SerdeExtendedHeader = pb.into();
654 custom_ser.serialize(serializer)
655 }
656}
657
658impl<'de> Deserialize<'de> for ExtendedHeader {
659 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
660 where
661 D: Deserializer<'de>,
662 {
663 let custom_de = custom_serde::SerdeExtendedHeader::deserialize(deserializer)?;
664 let pb: RawExtendedHeader = custom_de.into();
665 ExtendedHeader::try_from(pb).map_err(serde::de::Error::custom)
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 use super::*;
672 use crate::test_utils::{invalidate, unverify};
673
674 #[cfg(target_arch = "wasm32")]
675 use wasm_bindgen_test::wasm_bindgen_test as test;
676
677 fn sample_eh_chain_1_block_1() -> ExtendedHeader {
678 let s = include_str!("../test_data/chain1/extended_header_block_1.json");
679 serde_json::from_str(s).unwrap()
680 }
681
682 fn sample_eh_chain_1_block_27() -> ExtendedHeader {
683 let s = include_str!("../test_data/chain1/extended_header_block_27.json");
684 serde_json::from_str(s).unwrap()
685 }
686
687 fn sample_eh_chain_2_block_1() -> ExtendedHeader {
688 let s = include_str!("../test_data/chain2/extended_header_block_1.json");
689 serde_json::from_str(s).unwrap()
690 }
691
692 fn sample_eh_chain_2_block_27() -> ExtendedHeader {
693 let s = include_str!("../test_data/chain2/extended_header_block_27.json");
694 serde_json::from_str(s).unwrap()
695 }
696
697 fn sample_eh_chain_2_block_28() -> ExtendedHeader {
698 let s = include_str!("../test_data/chain2/extended_header_block_28.json");
699 serde_json::from_str(s).unwrap()
700 }
701
702 fn sample_eh_chain_2_block_35() -> ExtendedHeader {
703 let s = include_str!("../test_data/chain2/extended_header_block_35.json");
704 serde_json::from_str(s).unwrap()
705 }
706
707 fn sample_eh_chain_3_block_1_to_256() -> Vec<ExtendedHeader> {
708 let s = include_str!("../test_data/chain3/extended_header_block_1_to_256.json");
709 serde_json::from_str(s).unwrap()
710 }
711
712 #[test]
713 fn validate_correct() {
714 sample_eh_chain_1_block_1().validate().unwrap();
715 sample_eh_chain_1_block_27().validate().unwrap();
716
717 sample_eh_chain_2_block_1().validate().unwrap();
718 sample_eh_chain_2_block_27().validate().unwrap();
719 sample_eh_chain_2_block_28().validate().unwrap();
720 sample_eh_chain_2_block_35().validate().unwrap();
721 }
722
723 #[test]
724 fn validate_validator_hash_mismatch() {
725 let mut eh = sample_eh_chain_1_block_27();
726 eh.header.validators_hash = Hash::None;
727
728 eh.validate().unwrap_err();
729 }
730
731 #[test]
732 fn validate_dah_hash_mismatch() {
733 let mut eh = sample_eh_chain_1_block_27();
734 eh.header.data_hash = Some(Hash::Sha256([0; 32]));
735
736 eh.validate().unwrap_err();
737 }
738
739 #[test]
740 fn validate_commit_height_mismatch() {
741 let mut eh = sample_eh_chain_1_block_27();
742 eh.commit.height = 0xdeadbeefu32.into();
743
744 eh.validate().unwrap_err();
745 }
746
747 #[test]
748 fn validate_commit_block_hash_mismatch() {
749 let mut eh = sample_eh_chain_1_block_27();
750 eh.commit.block_id.hash = Hash::None;
751
752 eh.validate().unwrap_err();
753 }
754
755 #[test]
756 fn verify() {
757 let eh_block_1 = sample_eh_chain_1_block_1();
758 let eh_block_27 = sample_eh_chain_1_block_27();
759
760 eh_block_1.verify(&eh_block_27).unwrap();
761
762 let eh_block_1 = sample_eh_chain_2_block_1();
763 let eh_block_27 = sample_eh_chain_2_block_27();
764
765 eh_block_1.verify(&eh_block_27).unwrap();
766 }
767
768 #[test]
769 fn verify_adjacent() {
770 let eh_block_27 = sample_eh_chain_2_block_27();
771 let eh_block_28 = sample_eh_chain_2_block_28();
772
773 eh_block_27.verify(&eh_block_28).unwrap();
774 }
775
776 #[test]
777 fn verify_invalid_validator() {
778 let eh_block_27 = sample_eh_chain_2_block_27();
779 let mut eh_block_28 = sample_eh_chain_2_block_28();
780
781 eh_block_28.header.validators_hash = Hash::None;
782
783 eh_block_27.verify(&eh_block_28).unwrap_err();
784 }
785
786 #[test]
787 fn verify_invalid_last_block_hash() {
788 let eh_block_27 = sample_eh_chain_2_block_27();
789 let mut eh_block_28 = sample_eh_chain_2_block_28();
790
791 eh_block_28.header.last_block_id.as_mut().unwrap().hash = Hash::None;
792
793 eh_block_27.verify(&eh_block_28).unwrap_err();
794 }
795
796 #[test]
797 fn verify_invalid_adjacent() {
798 let eh_block_27 = sample_eh_chain_1_block_27();
799 let eh_block_28 = sample_eh_chain_2_block_28();
800
801 eh_block_27.verify(&eh_block_28).unwrap_err();
802 }
803
804 #[test]
805 fn verify_same_chain_id_but_different_chain() {
806 let eh_block_1 = sample_eh_chain_1_block_1();
807 let eh_block_27 = sample_eh_chain_2_block_27();
808
809 eh_block_1.verify(&eh_block_27).unwrap_err();
810 }
811
812 #[test]
813 fn verify_invalid_height() {
814 let eh_block_27 = sample_eh_chain_1_block_27();
815 eh_block_27.verify(&eh_block_27).unwrap_err();
816 }
817
818 #[test]
819 fn verify_invalid_chain_id() {
820 let eh_block_1 = sample_eh_chain_1_block_1();
821 let mut eh_block_27 = sample_eh_chain_1_block_27();
822
823 eh_block_27.header.chain_id = "1112222".parse().unwrap();
824 eh_block_1.verify(&eh_block_27).unwrap_err();
825 }
826
827 #[test]
828 fn verify_invalid_time() {
829 let eh_block_1 = sample_eh_chain_1_block_1();
830 let mut eh_block_27 = sample_eh_chain_1_block_27();
831
832 eh_block_27.header.time = eh_block_1.header.time;
833 eh_block_1.verify(&eh_block_27).unwrap_err();
834 }
835
836 #[test]
837 fn verify_time_from_the_future() {
838 let eh_block_1 = sample_eh_chain_1_block_1();
839 let mut eh_block_27 = sample_eh_chain_1_block_27();
840
841 eh_block_27.header.time = Time::now().checked_add(Duration::from_secs(60)).unwrap();
842 eh_block_1.verify(&eh_block_27).unwrap_err();
843 }
844
845 #[test]
846 fn verify_range() {
847 let eh_chain = sample_eh_chain_3_block_1_to_256();
848
849 eh_chain[0].verify_range(&eh_chain[1..]).unwrap();
850 eh_chain[0].verify_range(&eh_chain[..]).unwrap_err();
851 eh_chain[0].verify_range(&eh_chain[10..]).unwrap();
852
853 eh_chain[10].verify_range(&eh_chain[11..]).unwrap();
854 eh_chain[10].verify_range(&eh_chain[100..]).unwrap();
855 eh_chain[10].verify_range(&eh_chain[..9]).unwrap_err();
856 eh_chain[10].verify_range(&eh_chain[10..]).unwrap_err();
857 }
858
859 #[test]
860 fn verify_range_missing_height() {
861 let eh_chain = sample_eh_chain_3_block_1_to_256();
862
863 let mut headers = eh_chain[10..15].to_vec();
864 headers.remove(2);
865 eh_chain[0].verify_range(&headers).unwrap_err();
866 }
867
868 #[test]
869 fn verify_range_duplicate_height() {
870 let eh_chain = sample_eh_chain_3_block_1_to_256();
871
872 let mut headers = eh_chain[10..15].to_vec();
873 headers.insert(2, eh_chain[12].clone());
874 eh_chain[0].verify_range(&headers).unwrap_err();
875 }
876
877 #[test]
878 fn verify_range_bad_header_in_middle() {
879 let eh_chain = sample_eh_chain_3_block_1_to_256();
880
881 let mut headers = eh_chain[10..15].to_vec();
882
883 unverify(&mut headers[2]);
884
885 eh_chain[0].verify_range(&headers).unwrap_err();
886 }
887
888 #[test]
889 fn verify_range_allow_invalid_header_in_middle() {
890 let eh_chain = sample_eh_chain_3_block_1_to_256();
891
892 let mut headers = eh_chain[10..15].to_vec();
893
894 invalidate(&mut headers[2]);
895
896 eh_chain[0].verify_range(&headers).unwrap();
897 }
898
899 #[test]
900 fn verify_adjacent_range() {
901 let eh_chain = sample_eh_chain_3_block_1_to_256();
902
903 eh_chain[0].verify_adjacent_range(&eh_chain[1..]).unwrap();
904 eh_chain[0]
905 .verify_adjacent_range(&eh_chain[..])
906 .unwrap_err();
907 eh_chain[0]
908 .verify_adjacent_range(&eh_chain[10..])
909 .unwrap_err();
910
911 eh_chain[10].verify_adjacent_range(&eh_chain[11..]).unwrap();
912 eh_chain[10]
913 .verify_adjacent_range(&eh_chain[100..])
914 .unwrap_err();
915 eh_chain[10]
916 .verify_adjacent_range(&eh_chain[..9])
917 .unwrap_err();
918 eh_chain[10]
919 .verify_adjacent_range(&eh_chain[10..])
920 .unwrap_err();
921 }
922}