fuel_merkle/binary/
verify.rs

1use crate::{
2    binary::{
3        leaf_sum,
4        node_sum,
5    },
6    common::{
7        Bytes32,
8        ProofSet,
9    },
10};
11
12/// Returns None if:
13/// - `num_leaves` is 0
14/// - the result doens't fit in an usize
15fn path_length_from_key(key: u64, num_leaves: u64) -> Option<usize> {
16    if num_leaves == 0 {
17        return None;
18    }
19
20    #[allow(clippy::arithmetic_side_effects)] // ilog2(..) < 64
21    let path_length = if num_leaves.is_power_of_two() {
22        num_leaves.ilog2()
23    } else {
24        num_leaves.ilog2() + 1
25    };
26
27    #[allow(clippy::arithmetic_side_effects)] // ilog2(..) > 0
28    let num_leaves_left_subtree = 1 << (path_length - 1);
29
30    let subtree_leaves = num_leaves.saturating_sub(num_leaves_left_subtree);
31
32    let Some(subtree_key) = key.checked_sub(num_leaves_left_subtree) else {
33        // If leaf is in left subtree, path length is full height of left subtree
34        return path_length.try_into().ok();
35    };
36
37    // Otherwise, if left or right subtree has only one leaf, path has one additional step
38    if num_leaves_left_subtree == 1 || subtree_leaves <= 1 {
39        return Some(1);
40    }
41
42    // Otherwise, add 1 to height and recurse into right subtree
43    path_length_from_key(subtree_key, subtree_leaves)?.checked_add(1)
44}
45
46pub fn verify<T: AsRef<[u8]>>(
47    root: &Bytes32,
48    data: &T,
49    proof_set: &ProofSet,
50    proof_index: u64,
51    num_leaves: u64,
52) -> bool {
53    if num_leaves <= 1 {
54        if !proof_set.is_empty() {
55            return false;
56        }
57    } else if Some(proof_set.len()) != path_length_from_key(proof_index, num_leaves) {
58        return false;
59    }
60
61    if proof_index >= num_leaves {
62        return false;
63    }
64
65    let mut sum = leaf_sum(data.as_ref());
66    if proof_set.is_empty() {
67        return if num_leaves == 1 { *root == sum } else { false }
68    }
69    #[allow(clippy::arithmetic_side_effects)] // checked above
70    let last_leaf = num_leaves - 1;
71
72    let mut parent = 0usize;
73    let mut stable_end = proof_index;
74
75    loop {
76        #[allow(clippy::arithmetic_side_effects)] // path_length_from_key checks
77        let height = parent + 1;
78
79        let subtree_size = 1u64 << height;
80        #[allow(clippy::arithmetic_side_effects)] // floor(a / b) * b <= a
81        let subtree_start_index = proof_index / subtree_size * subtree_size;
82        #[allow(clippy::arithmetic_side_effects)]
83        let subtree_end_index = subtree_start_index + subtree_size - 1;
84
85        if subtree_end_index >= num_leaves {
86            break
87        }
88
89        stable_end = subtree_end_index;
90
91        if proof_set.len() < height {
92            return false
93        }
94
95        let proof_data = proof_set[parent];
96        #[allow(clippy::arithmetic_side_effects)] // proof_index > subtree_start_index
97        if proof_index - subtree_start_index < (1 << parent) {
98            sum = node_sum(&sum, &proof_data);
99        } else {
100            sum = node_sum(&proof_data, &sum);
101        }
102
103        #[allow(clippy::arithmetic_side_effects)] // path_length_from_key checks
104        {
105            parent += 1;
106        }
107    }
108
109    if stable_end != last_leaf {
110        if proof_set.len() <= parent {
111            return false
112        }
113        let proof_data = proof_set[parent];
114        sum = node_sum(&sum, &proof_data);
115        #[allow(clippy::arithmetic_side_effects)] // path_length_from_key checks
116        {
117            parent += 1;
118        }
119    }
120
121    while parent < proof_set.len() {
122        let proof_data = proof_set[parent];
123        sum = node_sum(&proof_data, &sum);
124        #[allow(clippy::arithmetic_side_effects)] // path_length_from_key checks
125        {
126            parent += 1;
127        }
128    }
129
130    sum == *root
131}
132
133#[cfg(test)]
134mod test {
135    use super::verify;
136    use crate::{
137        binary::{
138            MerkleTree,
139            Primitive,
140        },
141        common::StorageMap,
142    };
143    use fuel_merkle_test_helpers::TEST_DATA;
144    use fuel_storage::Mappable;
145
146    #[derive(Debug)]
147    struct TestTable;
148
149    impl Mappable for TestTable {
150        type Key = Self::OwnedKey;
151        type OwnedKey = u64;
152        type OwnedValue = Primitive;
153        type Value = Self::OwnedValue;
154    }
155
156    #[test]
157    fn verify_returns_true_when_the_given_proof_set_matches_the_given_merkle_root() {
158        let mut storage_map = StorageMap::<TestTable>::new();
159        let mut tree = MerkleTree::new(&mut storage_map);
160
161        const PROOF_INDEX: usize = 2;
162        const LEAVES_COUNT: usize = 5;
163
164        let data = &TEST_DATA[0..LEAVES_COUNT]; // 5 leaves
165        for datum in data.iter() {
166            tree.push(datum).unwrap();
167        }
168
169        let (root, proof_set) = tree.prove(PROOF_INDEX as u64).unwrap();
170        let verification = verify(
171            &root,
172            &TEST_DATA[PROOF_INDEX],
173            &proof_set,
174            PROOF_INDEX as u64,
175            LEAVES_COUNT as u64,
176        );
177        assert!(verification);
178    }
179
180    #[test]
181    fn verify_returns_false_when_the_given_proof_set_does_not_match_the_given_merkle_root(
182    ) {
183        // Check the Merkle root of one tree against the computed Merkle root of
184        // another tree's proof set: because the two roots come from different
185        // trees, the comparison should fail.
186
187        // Generate the first Merkle tree and get its root
188        let mut storage_map = StorageMap::<TestTable>::new();
189        let mut tree = MerkleTree::new(&mut storage_map);
190
191        const PROOF_INDEX: usize = 2;
192        const LEAVES_COUNT: usize = 5;
193
194        let data = &TEST_DATA[0..LEAVES_COUNT - 1];
195        for datum in data.iter() {
196            tree.push(datum).unwrap();
197        }
198        let proof = tree.prove(PROOF_INDEX as u64).unwrap();
199        let root = proof.0;
200
201        // Generate the second Merkle tree and get its proof set
202        let mut storage_map = StorageMap::<TestTable>::new();
203        let mut tree = MerkleTree::new(&mut storage_map);
204
205        let data = &TEST_DATA[5..10];
206        for datum in data.iter() {
207            tree.push(datum).unwrap();
208        }
209        let proof = tree.prove(PROOF_INDEX as u64).unwrap();
210        let set = proof.1;
211
212        let verification = verify(
213            &root,
214            &TEST_DATA[PROOF_INDEX],
215            &set,
216            PROOF_INDEX as u64,
217            LEAVES_COUNT as u64,
218        );
219        assert!(!verification);
220    }
221
222    #[test]
223    fn verify_returns_false_when_the_proof_set_is_empty() {
224        const PROOF_INDEX: usize = 0;
225        const LEAVES_COUNT: usize = 0;
226
227        let verification = verify(
228            &Default::default(),
229            &TEST_DATA[PROOF_INDEX],
230            &vec![],
231            PROOF_INDEX as u64,
232            LEAVES_COUNT as u64,
233        );
234        assert!(!verification);
235    }
236
237    #[test]
238    fn verify_returns_false_when_the_proof_index_is_invalid() {
239        let mut storage_map = StorageMap::<TestTable>::new();
240        let mut tree = MerkleTree::new(&mut storage_map);
241
242        const PROOF_INDEX: usize = 0;
243        const LEAVES_COUNT: usize = 5;
244
245        let data = &TEST_DATA[0..LEAVES_COUNT - 1];
246        for datum in data.iter() {
247            tree.push(datum).unwrap();
248        }
249
250        let proof = tree.prove(PROOF_INDEX as u64).unwrap();
251        let root = proof.0;
252        let set = proof.1;
253
254        let verification = verify(
255            &root,
256            &TEST_DATA[PROOF_INDEX],
257            &set,
258            PROOF_INDEX as u64 + 15,
259            LEAVES_COUNT as u64,
260        );
261        assert!(!verification);
262    }
263}