light_client/consensus/
header.rs1use anyhow::{Context, Result, anyhow, ensure};
4use committable::Committable;
5use espresso_types::{BlockMerkleTree, Header};
6use jf_merkle_tree_compat::MerkleTreeScheme;
7use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Debug, Deserialize, Serialize)]
11pub struct HeaderProof {
12 header: Header,
13 proof: <BlockMerkleTree as MerkleTreeScheme>::MembershipProof,
14}
15
16impl HeaderProof {
17 pub fn new(
19 header: Header,
20 proof: <BlockMerkleTree as MerkleTreeScheme>::MembershipProof,
21 ) -> Self {
22 Self { header, proof }
23 }
24
25 pub fn verify(self, root: <BlockMerkleTree as MerkleTreeScheme>::Commitment) -> Result<Header> {
27 self.verify_proof(root)?;
28 Ok(self.header)
29 }
30
31 pub fn verify_ref(
36 &self,
37 root: <BlockMerkleTree as MerkleTreeScheme>::Commitment,
38 ) -> Result<&Header> {
39 self.verify_proof(root)?;
40 Ok(&self.header)
41 }
42
43 fn verify_proof(&self, root: <BlockMerkleTree as MerkleTreeScheme>::Commitment) -> Result<()> {
44 ensure!(
47 self.proof.elem() == Some(&self.header.commit()),
48 "proof is not for the correct header"
49 );
50 ensure!(
51 self.proof.index() == &self.header.height(),
52 "proof is not for the correct height"
53 );
54
55 BlockMerkleTree::verify(root, self.header.height(), &self.proof)
56 .context("malformed proof")?
57 .map_err(|()| {
58 anyhow!(
59 "incorrect proof for element {} relative to {root}",
60 self.header.height()
61 )
62 })?;
63 Ok(())
64 }
65}
66
67#[cfg(test)]
68mod test {
69 use espresso_types::BLOCK_MERKLE_TREE_HEIGHT;
70 use jf_merkle_tree_compat::{AppendableMerkleTreeScheme, prelude::SHA3MerkleTree};
71 use pretty_assertions::assert_eq;
72 use versions::EPOCH_VERSION;
73
74 use super::*;
75 use crate::testing::leaf_chain;
76
77 #[tokio::test]
78 #[test_log::test]
79 async fn test_header_proof_valid() {
80 let leaves = leaf_chain(0..=3, EPOCH_VERSION).await;
81
82 let mut mt = SHA3MerkleTree::new(BLOCK_MERKLE_TREE_HEIGHT);
85 for (root, leaf) in leaves.iter().enumerate() {
86 for (height, expected) in leaves.iter().enumerate().take(root) {
87 let proof = mt.lookup(height as u64).expect_ok().unwrap().1;
88 let header = expected.header();
89 let proof = HeaderProof::new(header.clone(), proof);
90 assert_eq!(proof.verify_ref(mt.commitment()).unwrap(), header);
91 assert_eq!(proof.verify(mt.commitment()).unwrap(), *header);
92 }
93 mt.push(leaf.block_hash()).unwrap();
94 }
95 }
96
97 #[tokio::test]
98 #[test_log::test]
99 async fn test_header_proof_invalid_wrong_path() {
100 let leaves = leaf_chain(0..=1, EPOCH_VERSION).await;
101 let mt = BlockMerkleTree::from_elems(
102 Some(BLOCK_MERKLE_TREE_HEIGHT),
103 [leaves[0].block_hash(), leaves[1].block_hash()],
104 )
105 .unwrap();
106 let proof = HeaderProof::new(
107 leaves[0].header().clone(),
108 mt.lookup(1).expect_ok().unwrap().1,
109 );
110 let err = proof.verify(mt.commitment()).unwrap_err();
111 assert!(
112 err.to_string()
113 .contains("proof is not for the correct header"),
114 "{err:#}"
115 );
116 }
117
118 #[tokio::test]
119 #[test_log::test]
120 async fn test_header_proof_invalid_wrong_height() {
121 let leaf = leaf_chain(0..1, EPOCH_VERSION).await.remove(0);
122 let mts = [0, 1]
123 .into_iter()
124 .map(|height_diff| {
125 BlockMerkleTree::from_elems(
126 Some(BLOCK_MERKLE_TREE_HEIGHT + height_diff),
127 [leaf.block_hash()],
128 )
129 .unwrap()
130 })
131 .collect::<Vec<_>>();
132 let proof = HeaderProof::new(
133 leaf.header().clone(),
134 mts[0].lookup(0).expect_ok().unwrap().1,
135 );
136 let err = proof.verify(mts[1].commitment()).unwrap_err();
137 assert!(err.to_string().contains("malformed proof"), "{err:#}");
138 }
139}