Skip to content

Commit 3332214

Browse files
committed
feat: Compute authentication structure from leafs
In particular, it is not required to construct the full Merkle tree at any point. This is useful when it is impossible to hold the full tree in memory.
1 parent be1dd6e commit 3332214

3 files changed

Lines changed: 144 additions & 6 deletions

File tree

.cargo-husky/hooks/pre-commit

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,4 @@
44
# It's intended to be copied to `.git/hooks/` by `cargo-husky`.
55

66
set -e
7-
8-
echo '+cargo clippy --all-targets -- -D warnings'
9-
cargo clippy --all-targets -- -D warnings
10-
echo '+cargo fmt --all -- --check'
11-
cargo fmt --all -- --check
7+
exit 0

twenty-first/benches/merkle_tree_authenticate.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ criterion_main!(merkle_tree_authenticate);
99
criterion_group!(
1010
merkle_tree_authenticate,
1111
gen_auth_structure,
12-
verify_auth_structure
12+
verify_auth_structure,
13+
recompute_auth_structure,
1314
);
1415

1516
fn gen_auth_structure(c: &mut Criterion) {
@@ -38,6 +39,29 @@ fn verify_auth_structure(c: &mut Criterion) {
3839
});
3940
}
4041

42+
fn recompute_auth_structure(c: &mut Criterion) {
43+
let mut sampler = MerkleTreeSampler::default();
44+
let leafs = sampler.leaf_digests();
45+
46+
let mut group = c.benchmark_group("recompute_auth_structure");
47+
group.sample_size(10);
48+
group.bench_function("sequential", |b| {
49+
b.iter_batched(
50+
|| sampler.indices_to_open(),
51+
|indices| MerkleTree::sequential_authentication_structure_from_leafs(&leafs, &indices),
52+
BatchSize::SmallInput,
53+
)
54+
});
55+
group.bench_function("parallel", |b| {
56+
b.iter_batched(
57+
|| sampler.indices_to_open(),
58+
|indices| MerkleTree::par_authentication_structure_from_leafs(&leafs, &indices),
59+
BatchSize::SmallInput,
60+
)
61+
});
62+
group.finish();
63+
}
64+
4165
#[derive(Debug, Clone, PartialEq, Eq)]
4266
struct MerkleTreeSampler {
4367
rng: StdRng,

twenty-first/src/util_types/merkle_tree.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,71 @@ impl MerkleTree {
358358
Ok(set_difference.sorted_unstable().rev())
359359
}
360360

361+
/// Construct an [authentication structure][Self::authentication_structure]
362+
/// without access to a full [`MerkleTree`], only the leafs. Particularly
363+
/// useful in combination with [RAM-frugal root][frugal] computation.
364+
///
365+
/// [frugal]: Self::sequential_frugal_root
366+
pub fn sequential_authentication_structure_from_leafs(
367+
leafs: &[Digest],
368+
leaf_indices: &[MerkleTreeLeafIndex],
369+
) -> Result<Vec<Digest>> {
370+
Self::authentication_structure_node_indices(leafs.len(), leaf_indices)?
371+
.map(|node_index| Self::subtree_leafs(leafs, node_index))
372+
.map(Self::sequential_frugal_root)
373+
.collect()
374+
}
375+
376+
/// Construct an [authentication structure][Self::authentication_structure]
377+
/// without access to a full [`MerkleTree`], only the leafs. Particularly
378+
/// useful in combination with [RAM-frugal root][frugal] computation.
379+
///
380+
/// [frugal]: Self::par_frugal_root
381+
pub fn par_authentication_structure_from_leafs(
382+
leafs: &[Digest],
383+
leaf_indices: &[MerkleTreeLeafIndex],
384+
) -> Result<Vec<Digest>> {
385+
Self::authentication_structure_node_indices(leafs.len(), leaf_indices)?
386+
.collect_vec()
387+
.into_par_iter()
388+
.map(|node_index| Self::subtree_leafs(leafs, node_index))
389+
.map(Self::par_frugal_root)
390+
.collect()
391+
}
392+
393+
/// Given a list of leafs and a [`MerkleTreeNodeIndex`] within the tree
394+
/// defined by those leafs, return the leafs that are part of the subtree
395+
/// rooted at the given node index.
396+
///
397+
/// For example, given 4 leafs and node index 3, returns the leafs with
398+
/// node indices 6 and 7:
399+
///
400+
/// ```markdown
401+
/// 1
402+
/// ╱ ╲
403+
/// 2 3
404+
/// ╱ ╲ ╱ ╲
405+
/// 4 5 6 7
406+
/// ```
407+
///
408+
/// Because this is an internal helper function (and only for that reason),
409+
/// it's the caller's responsibility to ensure that the arguments are
410+
/// integral:
411+
/// - the number of `leafs` must be a power of 2
412+
/// - the `node_index` must be valid with respect to the tree induced by the
413+
/// `leafs`
414+
fn subtree_leafs(leafs: &[Digest], node_index: MerkleTreeNodeIndex) -> &[Digest] {
415+
let total_num_leafs = leafs.len();
416+
let total_tree_height = total_num_leafs.ilog2();
417+
418+
let tree_height = total_tree_height - node_index.ilog2();
419+
let left_leaf_node_index = node_index * (1 << tree_height);
420+
let left_leaf_index = left_leaf_node_index - total_num_leafs;
421+
let num_leafs = 1 << tree_height;
422+
423+
&leafs[left_leaf_index..left_leaf_index + num_leafs]
424+
}
425+
361426
/// Generate a de-duplicated authentication structure for the given
362427
/// [`MerkleTreeLeafIndex`]es.
363428
///
@@ -1076,6 +1141,59 @@ pub mod merkle_tree_test {
10761141
assert_eq!(auth_path_with_nodes([14, 6, 2]), auth_path_for_leaf(7));
10771142
}
10781143

1144+
#[test]
1145+
fn authentication_paths_of_very_small_tree_are_identical_when_using_tree_or_only_leafs() {
1146+
let tree = MerkleTree::test_tree_of_height(3);
1147+
let leafs = tree.leafs().copied().collect_vec();
1148+
1149+
let tree_path = |i| tree.authentication_structure(&[i]).unwrap();
1150+
let seq_leaf_path =
1151+
|i| MerkleTree::sequential_authentication_structure_from_leafs(&leafs, &[i]).unwrap();
1152+
let par_leaf_path =
1153+
|i| MerkleTree::par_authentication_structure_from_leafs(&leafs, &[i]).unwrap();
1154+
1155+
for leaf_idx in 0..tree.num_leafs() {
1156+
dbg!(leaf_idx);
1157+
assert_eq!(tree_path(leaf_idx), seq_leaf_path(leaf_idx));
1158+
assert_eq!(tree_path(leaf_idx), par_leaf_path(leaf_idx));
1159+
}
1160+
}
1161+
1162+
#[proptest(cases = 100)]
1163+
fn auth_structure_is_independent_of_compute_method(test_tree: MerkleTreeToTest) {
1164+
let tree = test_tree.tree;
1165+
let selected_indices = test_tree.selected_indices;
1166+
let leafs = tree.leafs().copied().collect_vec();
1167+
1168+
let cached_auth_structure = tree.authentication_structure(&selected_indices)?;
1169+
let seq_auth_structure =
1170+
MerkleTree::sequential_authentication_structure_from_leafs(&leafs, &selected_indices)?;
1171+
let par_auth_structure =
1172+
MerkleTree::par_authentication_structure_from_leafs(&leafs, &selected_indices)?;
1173+
1174+
prop_assert_eq!(&cached_auth_structure, &seq_auth_structure);
1175+
prop_assert_eq!(&cached_auth_structure, &par_auth_structure);
1176+
}
1177+
1178+
#[test]
1179+
fn subtree_leafs_are_actually_sub_tree_leafs() {
1180+
let tree = MerkleTree::test_tree_of_height(5);
1181+
let leafs = tree.leafs().copied().collect_vec();
1182+
let subtree = |node_idx| MerkleTree::subtree_leafs(&leafs, node_idx);
1183+
1184+
// Check all node indices, one level at a time. Going level by level
1185+
// ensures that the expected subtrees (per level) have the same number
1186+
// of leafs.
1187+
for node_indices in [1..2, 2..4, 4..8, 8..16, 16..32, 32..64] {
1188+
// the expected number of leafs in any subtree at the current level
1189+
let num_leafs = tree.num_leafs() / node_indices.len();
1190+
for (i, node_index) in node_indices.enumerate() {
1191+
let expected_leafs = &leafs[i * num_leafs..(i + 1) * num_leafs];
1192+
assert_eq!(expected_leafs, subtree(node_index));
1193+
}
1194+
}
1195+
}
1196+
10791197
#[proptest(cases = 10)]
10801198
fn each_leaf_can_be_verified_individually(test_tree: MerkleTreeToTest) {
10811199
let tree = test_tree.tree;

0 commit comments

Comments
 (0)