fuel_merkle/binary/
verify.rs1use crate::{
2 binary::{
3 leaf_sum,
4 node_sum,
5 },
6 common::{
7 Bytes32,
8 ProofSet,
9 },
10};
11
12fn 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)] 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)] 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 return path_length.try_into().ok();
35 };
36
37 if num_leaves_left_subtree == 1 || subtree_leaves <= 1 {
39 return Some(1);
40 }
41
42 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)] 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)] let height = parent + 1;
78
79 let subtree_size = 1u64 << height;
80 #[allow(clippy::arithmetic_side_effects)] 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)] 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)] {
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)] {
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)] {
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]; 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 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 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}