celestia_types/
merkle_proof.rs

1use celestia_proto::celestia::core::v1::proof::Proof as RawMerkleProof;
2use serde::{Deserialize, Serialize};
3use tendermint::crypto::default::Sha256;
4use tendermint::merkle::{Hash, MerkleHash};
5use tendermint_proto::Protobuf;
6
7use crate::{
8    bail_validation, bail_verification, validation_error, verification_error, Error, Result,
9};
10
11/// A proof of inclusion of some leaf in a merkle tree.
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13#[serde(try_from = "RawMerkleProof", into = "RawMerkleProof")]
14pub struct MerkleProof {
15    /// Leaf index.
16    pub index: usize,
17    /// Total number of leaves in the Merkle tree.
18    pub total: usize,
19    /// Hash of the leaf.
20    pub leaf_hash: Hash,
21    /// Hashes of the sibling nodes at each level of the tree.
22    pub aunts: Vec<Hash>,
23}
24
25impl MerkleProof {
26    /// Create a merkle proof of inclusion of a given leaf in a list.
27    ///
28    /// Returns a proof together with the root hash of a merkle tree built from given leaves.
29    ///
30    /// # Errors
31    ///
32    /// This function will return an error if `leaf_to_prove` index is out of `leaves` bounds.
33    ///
34    /// # Example
35    ///
36    /// ```
37    /// use celestia_types::MerkleProof;
38    ///
39    /// let (proof, root) = MerkleProof::new(0, &[b"a", b"b", b"c"]).unwrap();
40    ///
41    /// assert!(proof.verify(b"a", root).is_ok());
42    /// ```
43    pub fn new(leaf_to_prove: usize, leaves: &[impl AsRef<[u8]>]) -> Result<(Self, Hash)> {
44        if leaf_to_prove >= leaves.len() {
45            return Err(Error::IndexOutOfRange(leaf_to_prove, leaves.len()));
46        }
47
48        let tree_height = leaves.len().next_power_of_two().ilog2() as usize;
49        let mut aunts = Vec::with_capacity(tree_height);
50
51        let root = hash_leaves_collecting_aunts(0, leaf_to_prove, leaves, &mut aunts);
52        let proof = Self {
53            index: leaf_to_prove,
54            total: leaves.len(),
55            leaf_hash: Sha256::default().leaf_hash(leaves[leaf_to_prove].as_ref()),
56            aunts,
57        };
58
59        Ok((proof, root))
60    }
61
62    /// Verify that given leaf is included under the root hash.
63    ///
64    /// # Errors
65    ///
66    /// This function will return an error if:
67    ///  - provided leaf is different than the one for which proof was created
68    ///  - proof is malformed, meaning some inconsistency between leaf index, leaves count,
69    ///    or amount of inner nodes
70    ///  - the recomputed root hash differs from expected one
71    pub fn verify(&self, leaf: impl AsRef<[u8]>, root: Hash) -> Result<()> {
72        let mut hasher = Sha256::default();
73        let leaf = hasher.leaf_hash(leaf.as_ref());
74
75        if leaf != self.leaf_hash {
76            return Err(verification_error!("proof created for a different leaf").into());
77        }
78
79        let computed_root = subtree_root_from_aunts(self.index, self.total, leaf, &self.aunts)?;
80
81        if computed_root != root {
82            return Err(Error::RootMismatch);
83        }
84
85        Ok(())
86    }
87}
88
89// creates a merkle tree out of the given leaves and returns its root hash.
90// inner nodes needed to prove leaf with given index are collected in `aunts`.
91fn hash_leaves_collecting_aunts(
92    // for recursion, index of the first leaf in current subtree wrt the whole tree
93    first_leaf_index: usize,
94    leaf_to_prove: usize,
95    leaves: &[impl AsRef<[u8]>],
96    aunts: &mut Vec<Hash>,
97) -> Hash {
98    let mut hasher = Sha256::default();
99    let total = leaves.len();
100
101    match total {
102        // can only happen if there are 0 leaves without recursing
103        0 => hasher.empty_hash(),
104        // we reached a leaf
105        1 => hasher.leaf_hash(leaves[0].as_ref()),
106        // split leaves into subtrees and hash them recursively
107        _ => {
108            let subtrees_split = total.next_power_of_two() / 2;
109            let left = hash_leaves_collecting_aunts(
110                first_leaf_index,
111                leaf_to_prove,
112                &leaves[..subtrees_split],
113                aunts,
114            );
115            let right = hash_leaves_collecting_aunts(
116                first_leaf_index + subtrees_split,
117                leaf_to_prove,
118                &leaves[subtrees_split..],
119                aunts,
120            );
121
122            // if current subtree has the leaf to prove
123            if (first_leaf_index..first_leaf_index + total).contains(&leaf_to_prove) {
124                if leaf_to_prove < first_leaf_index + subtrees_split {
125                    // leaf was in left subtree, so its aunt is right hash
126                    aunts.push(right)
127                } else {
128                    // leaf was in right subtree, so its aunt is left hash
129                    aunts.push(left)
130                }
131            }
132
133            hasher.inner_hash(left, right)
134        }
135    }
136}
137
138// aunts are effectively a merkle proof for the leaf, so inner hashes needed
139// to recompute root hash of a merkle tree
140fn subtree_root_from_aunts(index: usize, total: usize, leaf: Hash, aunts: &[Hash]) -> Result<Hash> {
141    debug_assert_ne!(total, 0);
142
143    let root = if total == 1 {
144        // we reached the leaf
145        if !aunts.is_empty() {
146            bail_verification!("extra aunts in proof");
147        }
148        leaf
149    } else {
150        let mut hasher = Sha256::default();
151        let subtrees_split = total.next_power_of_two() / 2;
152
153        // take next subtree root's sibling
154        let (sibling, aunts) = aunts
155            .split_last()
156            .ok_or_else(|| verification_error!("aunts missing in proof"))?;
157
158        if index < subtrees_split {
159            // and recurse into left subtree
160            let left_hash = subtree_root_from_aunts(index, subtrees_split, leaf, aunts)?;
161            hasher.inner_hash(left_hash, *sibling)
162        } else {
163            // and recurse into right subtree
164            let right_hash = subtree_root_from_aunts(
165                index - subtrees_split,
166                total - subtrees_split,
167                leaf,
168                aunts,
169            )?;
170            hasher.inner_hash(*sibling, right_hash)
171        }
172    };
173
174    Ok(root)
175}
176
177impl Protobuf<RawMerkleProof> for MerkleProof {}
178
179impl TryFrom<RawMerkleProof> for MerkleProof {
180    type Error = Error;
181
182    fn try_from(value: RawMerkleProof) -> Result<Self, Self::Error> {
183        if value.index < 0 {
184            bail_validation!("negative index");
185        }
186        if value.total <= 0 {
187            bail_validation!("total <= 0");
188        }
189        Ok(Self {
190            index: value.index as usize,
191            total: value.total as usize,
192            leaf_hash: value
193                .leaf_hash
194                .try_into()
195                .map_err(|_| validation_error!("invalid hash size"))?,
196            aunts: value
197                .aunts
198                .into_iter()
199                .map(TryInto::try_into)
200                .collect::<Result<_, _>>()
201                .map_err(|_| validation_error!("invalid hash size"))?,
202        })
203    }
204}
205
206impl From<MerkleProof> for RawMerkleProof {
207    fn from(value: MerkleProof) -> Self {
208        Self {
209            index: value.index as i64,
210            total: value.total as i64,
211            leaf_hash: value.leaf_hash.into(),
212            aunts: value.aunts.into_iter().map(Into::into).collect(),
213        }
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use tendermint::crypto::default::Sha256;
220    use tendermint::merkle::simple_hash_from_byte_vectors;
221
222    use crate::test_utils::random_bytes;
223
224    use super::MerkleProof;
225
226    #[test]
227    fn create_and_verify() {
228        for _ in 0..100 {
229            let leaf_size = (rand::random::<usize>() % 1024) + 1;
230            let leaves_amount = 8;
231            let data = random_bytes(leaves_amount * leaf_size);
232
233            let leaves: Vec<_> = data.chunks(leaf_size).collect();
234            let leaf_to_prove = rand::random::<usize>() % leaves_amount;
235
236            let (proof, root) = MerkleProof::new(leaf_to_prove, &leaves).unwrap();
237
238            proof.verify(leaves[leaf_to_prove], root).unwrap();
239            proof.verify(random_bytes(leaf_size), root).unwrap_err();
240            proof
241                .verify(leaves[leaf_to_prove], rand::random())
242                .unwrap_err();
243        }
244    }
245
246    #[test]
247    fn tendermint_compatibility() {
248        for _ in 0..100 {
249            let leaves_amount = (rand::random::<usize>() % 500) + 1;
250            let leaves: Vec<_> = (0..leaves_amount)
251                .map(|_| {
252                    let leaf_size = (rand::random::<usize>() % 1024) + 1;
253                    random_bytes(leaf_size)
254                })
255                .collect();
256
257            let (_, root) = MerkleProof::new(0, &leaves).unwrap();
258            let tendermint_root = simple_hash_from_byte_vectors::<Sha256>(&leaves);
259
260            assert_eq!(root, tendermint_root);
261        }
262    }
263}