Skip to content

Commit cbb63e5

Browse files
committed
Add more tests for merkle trees and add sorting to validate_set_membership_proof
1 parent 5c98126 commit cbb63e5

File tree

5 files changed

+133
-17
lines changed

5 files changed

+133
-17
lines changed

libiop/algebra/utils.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ void bitreverse_vector(std::vector<T> &a);
1919
template<typename T>
2020
std::vector<T> random_vector(const std::size_t count);
2121

22+
template<typename T, typename U>
23+
bool compare_first(const std::pair<T, U> &a, const std::pair<T, U> &b);
24+
2225
template<typename T>
2326
std::vector<T> all_subset_sums(const std::vector<T> &basis, const T& shift = 0)
2427
#if defined(__clang__)

libiop/algebra/utils.tcc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ std::vector<T> random_vector(const std::size_t count)
168168
return result;
169169
}
170170

171+
template<typename T, typename U>
172+
bool compare_first(const std::pair<T, U> &a, const std::pair<T, U> &b)
173+
{
174+
return a.first < b.first;
175+
}
176+
171177
template<typename FieldT>
172178
std::vector<FieldT> random_FieldT_vector(const std::size_t count)
173179
{

libiop/bcs/merkle_tree.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ class merkle_tree {
126126

127127
hash_digest_type get_root() const;
128128

129+
/* These two functions do not currently work if the given positions aren't sorted or
130+
have duplicates, AND the tree is set to be zero knowledge. */
129131
merkle_tree_set_membership_proof<hash_digest_type> get_set_membership_proof(
130132
const std::vector<std::size_t> &positions) const;
131133
bool validate_set_membership_proof(

libiop/bcs/merkle_tree.tcc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,9 @@ bool merkle_tree<FieldT, hash_digest_type>::validate_set_membership_proof(
407407
if (this->make_zk_) {
408408
for (auto &leaf : leaf_contents)
409409
{
410+
/* FIXME: This code is currently incorrect if the given list of positions is not
411+
sorted or has duplicates. This could be fixed if both positions and leaf_contents
412+
are sorted before the leaf hashes are calculated, which would require refactoring. */
410413
const zk_salt_type zk_salt = *rand_it++;
411414
leaf_hashes.emplace_back(this->leaf_hasher_->zk_hash(leaf, zk_salt));
412415
}
@@ -427,6 +430,7 @@ bool merkle_tree<FieldT, hash_digest_type>::validate_set_membership_proof(
427430
return std::make_pair(pos, hash);
428431
});
429432

433+
std::sort(S.begin(), S.end(), compare_first<size_t, hash_digest_type>);
430434
S.erase(std__unique(S.begin(), S.end()), S.end()); /* remove possible duplicates */
431435

432436
if (std__adjacent_find(S.begin(), S.end(),
@@ -446,7 +450,7 @@ bool merkle_tree<FieldT, hash_digest_type>::validate_set_membership_proof(
446450
throw std::invalid_argument("All positions must be between 0 and num_leaves-1.");
447451
}
448452

449-
/* transform to set of indices */
453+
/* transform to sorted set of indices */
450454
for (auto &pos : S)
451455
{
452456
pos.first += this->num_leaves_ - 1;

libiop/tests/bcs/test_merkle_tree.cpp

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <gtest/gtest.h>
33
#include <vector>
44
#include <type_traits>
5+
#include <random>
56

67
#include <libff/algebra/fields/binary/gf64.hpp>
78
#include "libiop/algebra/utils.hpp"
@@ -108,14 +109,17 @@ TEST(MerkleTreeTest, SimpleTest) {
108109
typedef libff::gf64 FieldT;
109110

110111
const size_t size = 16;
111-
const size_t cap_size = 2;
112+
const std::vector<size_t> cap_sizes = {2, 4, 8, 16}; // Test all possible cap sizes.
112113
const size_t digest_len_bytes = 256/8;
113114
const size_t security_parameter = 128;
114115

115-
run_simple_MT_test<FieldT, binary_hash_digest>(size, digest_len_bytes, false,
116-
security_parameter, cap_size);
117-
run_simple_MT_test<FieldT, FieldT>(size, digest_len_bytes, false,
118-
security_parameter, cap_size);
116+
for (size_t cap_size : cap_sizes)
117+
{
118+
run_simple_MT_test<FieldT, binary_hash_digest>(size, digest_len_bytes, false,
119+
security_parameter, cap_size);
120+
run_simple_MT_test<FieldT, FieldT>(size, digest_len_bytes, false,
121+
security_parameter, cap_size);
122+
}
119123
}
120124

121125
TEST(MerkleTreeZKTest, SimpleTest) {
@@ -132,10 +136,14 @@ TEST(MerkleTreeZKTest, SimpleTest) {
132136
security_parameter, cap_size);
133137
}
134138

135-
void run_multi_test(const bool make_zk) {
139+
/** Constructs a merkle tree with 8 leaves each of size 2, and cap size 4. Generates and verifies
140+
* membership proofs for every possible subset of leaves. */
141+
void run_fixed_multi_test(const bool make_zk) {
136142
typedef libff::gf64 FieldT;
137143

144+
// The size is fixed because large values would quickly cause the program run out of memory.
138145
const size_t size = 8;
146+
const size_t cap_size = 4;
139147
const size_t security_parameter = 128;
140148
const size_t digest_len_bytes = 256/8;
141149
const bool algebraic_hash = false;
@@ -144,7 +152,8 @@ void run_multi_test(const bool make_zk) {
144152
size,
145153
digest_len_bytes,
146154
make_zk,
147-
security_parameter);
155+
security_parameter,
156+
cap_size);
148157

149158
const std::vector<FieldT> vec1 = random_vector<FieldT>(size);
150159
const std::vector<FieldT> vec2 = random_vector<FieldT>(size);
@@ -153,23 +162,25 @@ void run_multi_test(const bool make_zk) {
153162

154163
const binary_hash_digest root = tree.get_root();
155164

156-
std::vector<std::vector<FieldT>> leafs;
165+
std::vector<std::vector<FieldT>> leaves;
157166
for (size_t i = 0; i < size; ++i)
158167
{
159168
std::vector<FieldT> leaf({ vec1[i], vec2[i] });
160-
leafs.emplace_back(leaf);
169+
leaves.emplace_back(leaf);
161170
}
162171

172+
/* This code generates every possible subset. `subset` is a binary string that encodes for each
173+
element, whether it is in this subset. */
163174
for (size_t subset = 0; subset < (1ull<<size); ++subset)
164175
{
165176
std::vector<size_t> subset_elements;
166-
std::vector<std::vector<FieldT>> subset_leafs;
177+
std::vector<std::vector<FieldT>> subset_leaves;
167178
for (size_t k = 0; k < size; ++k)
168179
{
169180
if (subset & (1ull<<k))
170181
{
171182
subset_elements.emplace_back(k);
172-
subset_leafs.emplace_back(leafs[k]);
183+
subset_leaves.emplace_back(leaves[k]);
173184
}
174185
}
175186

@@ -178,20 +189,110 @@ void run_multi_test(const bool make_zk) {
178189

179190
const bool is_valid = tree.validate_set_membership_proof(root,
180191
subset_elements,
181-
subset_leafs,
192+
subset_leaves,
193+
mp);
194+
EXPECT_TRUE(is_valid);
195+
}
196+
}
197+
198+
TEST(MerkleTreeTest, FixedMultiTest) {
199+
const bool make_zk = false;
200+
run_fixed_multi_test(make_zk);
201+
}
202+
203+
TEST(MerkleTreeZKTest, FixedMultiTest) {
204+
const bool make_zk = true;
205+
run_fixed_multi_test(make_zk);
206+
}
207+
208+
/** Constructs a merkle tree with leaf size 2. Generates and verifies membership proofs for some
209+
* randomly generated sorted subset of leaves of specified size, with no duplicates. Queries with
210+
* unsorted, duplicated lists of leaves currently only work when it is not zero knowledge. */
211+
void run_random_multi_test(const size_t size, const size_t digest_len_bytes, const bool make_zk,
212+
const size_t security_parameter, const size_t cap_size,
213+
const size_t subset_size) {
214+
typedef libff::gf64 FieldT;
215+
216+
const bool algebraic_hash = false;
217+
const size_t num_iterations = 1; // The number of randomly generated subsets to test.
218+
219+
merkle_tree<FieldT, binary_hash_digest> tree = new_MT<FieldT, binary_hash_digest>(
220+
size,
221+
digest_len_bytes,
222+
make_zk,
223+
security_parameter,
224+
cap_size);
225+
226+
const std::vector<FieldT> vec1 = random_vector<FieldT>(size);
227+
const std::vector<FieldT> vec2 = random_vector<FieldT>(size);
228+
229+
tree.construct({ vec1, vec2 });
230+
231+
const binary_hash_digest root = tree.get_root();
232+
233+
std::vector<std::vector<FieldT>> leaves;
234+
leaves.reserve(size);
235+
std::vector<size_t> shuffled_leaf_indices;
236+
shuffled_leaf_indices.reserve(size);
237+
for (size_t i = 0; i < size; ++i)
238+
{
239+
std::vector<FieldT> leaf({ vec1[i], vec2[i] });
240+
leaves.emplace_back(leaf);
241+
shuffled_leaf_indices.emplace_back(i);
242+
}
243+
244+
for (size_t i = 0; i < num_iterations; i++)
245+
{
246+
std::vector<size_t> subset_elements;
247+
std::vector<std::vector<FieldT>> subset_leaves;
248+
/* The commented-out code generates subsets that are unsorted and may be repeats.
249+
They are not used because the code currently cannot handle these cases if it is
250+
zero knowledge. */
251+
// for (size_t j = 0; j < subset_size; j++)
252+
// {
253+
// size_t k = randombytes_uniform(size);
254+
// subset_elements.emplace_back(k);
255+
// subset_leaves.emplace_back(leaves[k]);
256+
// }
257+
258+
// Generate a random sorted subset of indices at the beginning of shuffled_leaf_indices.
259+
std::shuffle(shuffled_leaf_indices.begin(), shuffled_leaf_indices.end(),
260+
std::default_random_engine(i));
261+
std::sort(shuffled_leaf_indices.begin(), shuffled_leaf_indices.begin() + subset_size);
262+
for (size_t j = 0; j < subset_size; j++)
263+
{
264+
size_t k = shuffled_leaf_indices[j];
265+
subset_elements.emplace_back(k);
266+
subset_leaves.emplace_back(leaves[k]);
267+
}
268+
269+
const merkle_tree_set_membership_proof<binary_hash_digest> mp =
270+
tree.get_set_membership_proof(subset_elements);
271+
272+
const bool is_valid = tree.validate_set_membership_proof(root,
273+
subset_elements,
274+
subset_leaves,
182275
mp);
183276
EXPECT_TRUE(is_valid);
184277
}
185278
}
186279

187-
TEST(MerkleTreeTest, MultiTest) {
280+
TEST(MerkleTreeTest, RandomMultiTest) {
281+
const size_t security_parameter = 128;
282+
const size_t digest_len_bytes = 256/8;
188283
const bool make_zk = false;
189-
run_multi_test(make_zk);
284+
// Test a small and a large tree.
285+
run_random_multi_test(16, digest_len_bytes, make_zk, security_parameter, 4, 5);
286+
run_random_multi_test(1ull << 16, digest_len_bytes, make_zk, security_parameter, 256, 100);
190287
}
191288

192-
TEST(MerkleTreeZKTest, MultiTest) {
289+
TEST(MerkleTreeZKTest, RandomMultiTest) {
290+
const size_t security_parameter = 128;
291+
const size_t digest_len_bytes = 256/8;
193292
const bool make_zk = true;
194-
run_multi_test(make_zk);
293+
// Test a small and a large tree.
294+
run_random_multi_test(16, digest_len_bytes, make_zk, security_parameter, 4, 5);
295+
run_random_multi_test(1ull << 16, digest_len_bytes, make_zk, security_parameter, 256, 100);
195296
}
196297

197298
TEST(MerkleTreeHashCountTest, SimpleTest)

0 commit comments

Comments
 (0)