Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
merkle_tree.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
5// =====================
6
7#pragma once
14#include "hash_path.hpp"
15#include "merkle_tree.hpp"
16#include <iostream>
17#include <sstream>
18
20
21class MemoryStore;
22
23template <typename Store, typename HashingPolicy> class MerkleTree {
24 public:
26
27 MerkleTree(Store& store, size_t depth, uint8_t tree_id = 0);
28 MerkleTree(MerkleTree const& other) = delete;
29 MerkleTree(MerkleTree&& other);
31
33
35
36 fr update_element(index_t index, fr const& value);
37
38 fr root() const;
39
40 size_t depth() const { return depth_; }
41
42 index_t size() const;
43
44 protected:
46
56 fr update_element(fr const& root, fr const& value, index_t index, size_t height);
57
58 fr get_element(fr const& root, index_t index, size_t height);
59
67 fr compute_zero_path_hash(size_t height, index_t index, fr const& value);
68
77 fr binary_put(index_t a_index, fr const& a, fr const& b, size_t height);
78
80 fr const& value1, index_t index1, fr const& value2, index_t index2, size_t height, size_t stump_height);
81
89 void put(fr const& key, fr const& left, fr const& right);
90
99 void put_stump(fr const& key, index_t index, fr const& value);
100
101 void remove(fr const& key);
102
103 protected:
105 std::vector<fr> zero_hashes_;
106 size_t depth_;
107 uint8_t tree_id_;
108};
109
110// Size of merkle tree nodes in bytes.
111constexpr size_t REGULAR_NODE_SIZE = 64;
112constexpr size_t STUMP_NODE_SIZE = 65;
113
114template <typename T> inline bool bit_set(T const& index, size_t i)
115{
116 return bool((index >> i) & 0x1);
117}
118
119template <typename Store, typename HashingPolicy>
120MerkleTree<Store, HashingPolicy>::MerkleTree(Store& store, size_t depth, uint8_t tree_id)
121 : store_(store)
122 , depth_(depth)
123 , tree_id_(tree_id)
124{
126 BB_ASSERT_LTE(depth, 256U);
127 zero_hashes_.resize(depth);
128
129 // Compute the zero values at each layer.
130 auto current = fr(0);
131 for (size_t i = 0; i < depth; ++i) {
132 zero_hashes_[i] = current;
133 current = HashingPolicy::hash_pair(current, current);
134 }
135}
136
137template <typename Store, typename HashingPolicy>
139 : store_(other.store_)
140 , zero_hashes_(std::move(other.zero_hashes_))
141 , depth_(other.depth_)
142 , tree_id_(other.tree_id_)
143{}
144
145template <typename Store, typename HashingPolicy> MerkleTree<Store, HashingPolicy>::~MerkleTree() {}
146
147template <typename Store, typename HashingPolicy> fr MerkleTree<Store, HashingPolicy>::root() const
148{
149 std::vector<uint8_t> root;
150 std::vector<uint8_t> key = { tree_id_ };
151 bool status = store_.get(key, root);
152 return status ? from_buffer<fr>(root) : HashingPolicy::hash_pair(zero_hashes_.back(), zero_hashes_.back());
153}
154
155template <typename Store, typename HashingPolicy>
157{
158 std::vector<uint8_t> size_buf;
159 std::vector<uint8_t> key = { tree_id_ };
160 bool status = store_.get(key, size_buf);
161 return status ? from_buffer<index_t>(size_buf, 32) : 0;
162}
163
164template <typename Store, typename HashingPolicy>
166{
167 fr_hash_path path(depth_);
168
169 std::vector<uint8_t> data;
170 bool status = store_.get(root().to_buffer(), data);
171
172 for (size_t i = depth_ - 1; i < depth_; --i) {
173 if (!status) {
174 // This is an empty subtree. Fill in zero value.
175 path[i] = std::make_pair(zero_hashes_[i], zero_hashes_[i]);
176 continue;
177 }
178
179 if (data.size() == REGULAR_NODE_SIZE) {
180 // This is a regular node with left and right trees. Descend according to index path.
181 auto left = from_buffer<fr>(data, 0);
182 auto right = from_buffer<fr>(data, 32);
183 path[i] = std::make_pair(left, right);
184 bool is_right = bit_set(index, i);
185 auto it = data.data() + (is_right ? 32 : 0);
186 status = store_.get(std::vector<uint8_t>(it, it + 32), data);
187 } else {
188 // This is a stump. The hash path can be fully restored from this node.
189 BB_ASSERT_EQ(data.size(),
191 "We store: [key : (value, local_index, true)], i.e. 65-byte data, in a thump.");
192 fr current = from_buffer<fr>(data, 0);
193 index_t element_index = from_buffer<index_t>(data, 32);
194 index_t subtree_index = numeric::keep_n_lsb(index, i + 1);
195 index_t diff = element_index ^ subtree_index;
196
197 if (diff < 2) {
198 // Requesting path to either the same element in the stump, or it's partner element.
199 // Starting at the bottom of the tree, compute the remaining path pairs.
200 for (size_t j = 0; j <= i; ++j) {
201 bool is_right = bit_set(element_index, j);
202 if (is_right) {
203 path[j] = std::make_pair(zero_hashes_[j], current);
204 } else {
205 path[j] = std::make_pair(current, zero_hashes_[j]);
206 }
207 current = HashingPolicy::hash_pair(path[j].first, path[j].second);
208 }
209 } else {
210 // Requesting path to a different, independent element.
211 // We know that this element exists in an empty subtree, of height determined by the common bits in the
212 // stumps index and the requested index.
213 size_t common_bits = numeric::count_leading_zeros(diff);
214 size_t common_height = sizeof(index_t) * 8 - common_bits - 1;
215
216 for (size_t j = 0; j < common_height; ++j) {
217 path[j] = std::make_pair(zero_hashes_[j], zero_hashes_[j]);
218 }
219 current = compute_zero_path_hash(common_height, element_index, current);
220 for (size_t j = common_height; j <= i; ++j) {
221 bool is_right = bit_set(element_index, j);
222 if (is_right) {
223 path[j] = std::make_pair(zero_hashes_[j], current);
224 } else {
225 path[j] = std::make_pair(current, zero_hashes_[j]);
226 }
227 current = HashingPolicy::hash_pair(path[j].first, path[j].second);
228 }
229 }
230 break;
231 }
232 }
233
234 return path;
235}
236
237template <typename Store, typename HashingPolicy>
239{
240 fr_sibling_path path(depth_);
241
242 std::vector<uint8_t> data;
243 bool status = store_.get(root().to_buffer(), data);
244
245 for (size_t i = depth_ - 1; i < depth_; --i) {
246 if (!status) {
247 // This is an empty subtree. Fill in zero value.
248 path[i] = zero_hashes_[i];
249 continue;
250 }
251
252 if (data.size() == REGULAR_NODE_SIZE) {
253 // This is a regular node with left and right trees. Descend according to index path.
254 bool is_right = bit_set(index, i);
255 path[i] = from_buffer<fr>(data, is_right ? 0 : 32);
256
257 auto it = data.data() + (is_right ? 32 : 0);
258 status = store_.get(std::vector<uint8_t>(it, it + 32), data);
259 } else {
260 // This is a stump. The sibling path can be fully restored from this node.
261 // In case of a stump, we store: [key : (value, local_index, true)], i.e. 65-byte data.
263 fr current = from_buffer<fr>(data, 0);
264 index_t element_index = from_buffer<index_t>(data, 32);
265 index_t subtree_index = numeric::keep_n_lsb(index, i + 1);
266 index_t diff = element_index ^ subtree_index;
267
268 // Populate the sibling path with zero hashes.
269 for (size_t j = 0; j <= i; ++j) {
270 path[j] = zero_hashes_[j];
271 }
272
273 // If diff == 0, we are requesting the sibling path of the only non-zero element in the stump which is
274 // populated only with zero hashes.
275 if (diff == 1) {
276 // Requesting path of the sibling of the non-zero leaf in the stump.
277 // Set the bottom element of the path to the non-zero leaf (the rest is already populated correctly
278 // with zero hashes).
279 path[0] = current;
280 } else if (diff > 1) {
281 // Requesting path to a different, independent element.
282 // We know that this element exists in an empty subtree, of height determined by the common bits in the
283 // stumps index and the requested index.
284 size_t common_bits = numeric::count_leading_zeros(diff);
285 size_t common_height = sizeof(index_t) * 8 - common_bits - 1;
286
287 // Insert the only non-zero sibling at the common height.
288 path[common_height] = compute_zero_path_hash(common_height, element_index, current);
289 }
290 break;
291 }
292 }
293
294 return path;
295}
296
297template <typename Store, typename HashingPolicy>
299{
300 auto leaf = value;
301 using serialize::write;
302 std::vector<uint8_t> leaf_key;
303 write(leaf_key, tree_id_);
304 write(leaf_key, index);
305 store_.put(leaf_key, to_buffer(leaf));
306
307 auto r = update_element(root(), leaf, index, depth_);
308
309 std::vector<uint8_t> meta_key = { tree_id_ };
310 std::vector<uint8_t> meta_buf;
311 write(meta_buf, r);
312 write(meta_buf, index + 1);
313 store_.put(meta_key, meta_buf);
314
315 return r;
316}
317
318template <typename Store, typename HashingPolicy>
319fr MerkleTree<Store, HashingPolicy>::binary_put(index_t a_index, fr const& a, fr const& b, size_t height)
320{
321 bool a_is_right = bit_set(a_index, height - 1);
322 auto left = a_is_right ? b : a;
323 auto right = a_is_right ? a : b;
324 auto key = HashingPolicy::hash_pair(left, right);
325 put(key, left, right);
326 return key;
327}
328
329template <typename Store, typename HashingPolicy>
331 fr const& value1, index_t index1, fr const& value2, index_t index2, size_t height, size_t common_height)
332{
333 if (height == common_height) {
334 if (height == 1) {
335 index1 = numeric::keep_n_lsb(index1, 1);
336 index2 = numeric::keep_n_lsb(index2, 1);
337 return binary_put(index1, value1, value2, height);
338 } else {
339 size_t stump_height = height - 1;
340 index_t stump1_index = numeric::keep_n_lsb(index1, stump_height);
341 index_t stump2_index = numeric::keep_n_lsb(index2, stump_height);
342
343 fr stump1_hash = compute_zero_path_hash(stump_height, stump1_index, value1);
344 fr stump2_hash = compute_zero_path_hash(stump_height, stump2_index, value2);
345 put_stump(stump1_hash, stump1_index, value1);
346 put_stump(stump2_hash, stump2_index, value2);
347 return binary_put(index1, stump1_hash, stump2_hash, height);
348 }
349 } else {
350 auto new_root = fork_stump(value1, index1, value2, index2, height - 1, common_height);
351 return binary_put(index1, new_root, zero_hashes_[height - 1], height);
352 }
353}
354
355template <typename Store, typename HashingPolicy>
356fr MerkleTree<Store, HashingPolicy>::update_element(fr const& root, fr const& value, index_t index, size_t height)
357{
358 // Base layer of recursion at height = 0.
359 if (height == 0) {
360 return value;
361 }
362
363 std::vector<uint8_t> data;
364 auto status = store_.get(root.to_buffer(), data);
365
366 if (!status) {
367 fr key = compute_zero_path_hash(height, index, value);
368 put_stump(key, index, value);
369 return key;
370 }
371
372 if (data.size() == STUMP_NODE_SIZE) {
373 // We've come across a stump.
374 index_t existing_index = from_buffer<index_t>(data, 32);
375
376 if (existing_index == index) {
377 // We are updating the stumps element. Easy update.
378 fr new_hash = compute_zero_path_hash(height, index, value);
379 put_stump(new_hash, existing_index, value);
380 return new_hash;
381 }
382
383 fr existing_value = from_buffer<fr>(data, 0);
384 size_t common_bits = numeric::count_leading_zeros(existing_index ^ index);
385 size_t common_height = sizeof(index_t) * 8 - common_bits;
386
387 return fork_stump(existing_value, existing_index, value, index, height, common_height);
388 } else {
389 BB_ASSERT_EQ(data.size(), REGULAR_NODE_SIZE, "If it's not a stump, the data size must be 64 bytes.");
390 bool is_right = bit_set(index, height - 1);
391 fr subtree_root = from_buffer<fr>(data, is_right ? 32 : 0);
392 fr subtree_root_copy = subtree_root;
393 auto left = from_buffer<fr>(data, 0);
394 auto right = from_buffer<fr>(data, 32);
395 subtree_root = update_element(subtree_root, value, numeric::keep_n_lsb(index, height - 1), height - 1);
396 if (is_right) {
397 right = subtree_root;
398 } else {
399 left = subtree_root;
400 }
401 auto new_root = HashingPolicy::hash_pair(left, right);
402 put(new_root, left, right);
403
404 // Remove the old node only while rolling back in recursion.
405 if (!(subtree_root_copy == subtree_root)) {
406 remove(subtree_root_copy);
407 }
408 return new_root;
409 }
410}
411
412template <typename Store, typename HashingPolicy>
414{
415 fr current = value;
416 for (size_t i = 0; i < height; ++i) {
417 bool is_right = bit_set(index, i);
418 fr left, right;
419 if (is_right) {
420 left = zero_hashes_[i];
421 right = current;
422 } else {
423 right = zero_hashes_[i];
424 left = current;
425 }
426 current = HashingPolicy::hash_pair(left, right);
427 }
428 return current;
429}
430
431template <typename Store, typename HashingPolicy>
432void MerkleTree<Store, HashingPolicy>::put(fr const& key, fr const& left, fr const& right)
433{
434 std::vector<uint8_t> value;
435 write(value, left);
436 write(value, right);
437 store_.put(key.to_buffer(), value);
438}
439
440template <typename Store, typename HashingPolicy>
442{
443 std::vector<uint8_t> buf;
444 write(buf, value);
445 write(buf, index);
446 // Add an additional byte, to signify we are a stump.
447 write(buf, true);
448 store_.put(key.to_buffer(), buf);
449}
450
451template <typename Store, typename HashingPolicy> void MerkleTree<Store, HashingPolicy>::remove(fr const& key)
452{
453 store_.del(key.to_buffer());
454}
455
456} // namespace bb::crypto::merkle_tree
#define BB_ASSERT_GTE(left, right,...)
Definition assert.hpp:101
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:59
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:129
MerkleTree(MerkleTree const &other)=delete
void put_stump(fr const &key, index_t index, fr const &value)
void put(fr const &key, fr const &left, fr const &right)
fr fork_stump(fr const &value1, index_t index1, fr const &value2, index_t index2, size_t height, size_t stump_height)
MerkleTree(Store &store, size_t depth, uint8_t tree_id=0)
fr_hash_path get_hash_path(index_t index)
fr compute_zero_path_hash(size_t height, index_t index, fr const &value)
fr binary_put(index_t a_index, fr const &a, fr const &b, size_t height)
fr get_element(fr const &root, index_t index, size_t height)
fr_sibling_path get_sibling_path(index_t index)
fr update_element(index_t index, fr const &value)
const std::vector< FF > data
FF a
FF b
ContentAddressedCachedTreeStore< bb::fr > Store
uint8_t const * buf
Definition data_store.hpp:9
constexpr size_t STUMP_NODE_SIZE
constexpr size_t REGULAR_NODE_SIZE
std::vector< std::pair< fr, fr > > fr_hash_path
Definition hash_path.hpp:15
std::vector< fr > fr_sibling_path
Definition hash_path.hpp:16
bool bit_set(T const &index, size_t i)
constexpr size_t count_leading_zeros(T const &u)
T keep_n_lsb(T const &input, size_t num_bits)
field< Bn254FrParams > fr
Definition fr.hpp:174
void write(B &buf, field2< base_field, Params > const &value)
void write(auto &buf, const msgpack_concepts::HasMsgPack auto &obj)
Automatically derived write for any object that defines .msgpack() (implicitly defined by MSGPACK_FIE...
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::vector< uint8_t > to_buffer(T const &value)
BB_INLINE std::vector< uint8_t > to_buffer() const