Skip to content

Commit ab7d221

Browse files
committed
signoff!: MerkleVerify
Migration guide: replace `MERKLE_AUTHENTICATION_ROOT_MISMATCH_ERROR` with `MerkleVerify::ROOT_MISMATCH_ERROR_ID`. BREAKING CHANGE: Turn “error id” constant into associated constant.
1 parent 6dbdb06 commit ab7d221

File tree

2 files changed

+74
-41
lines changed

2 files changed

+74
-41
lines changed

tasm-lib/benchmarks/tasmlib_hashing_merkle_verify.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"clock_cycle_count": 42,
66
"hash_table_height": 72,
77
"u32_table_height": 52,
8-
"op_stack_table_height": 28,
8+
"op_stack_table_height": 26,
99
"ram_table_height": 0
1010
},
1111
"case": "CommonCase"
@@ -16,7 +16,7 @@
1616
"clock_cycle_count": 70,
1717
"hash_table_height": 156,
1818
"u32_table_height": 285,
19-
"op_stack_table_height": 28,
19+
"op_stack_table_height": 26,
2020
"ram_table_height": 0
2121
},
2222
"case": "WorstCase"

tasm-lib/src/hashing/merkle_verify.rs

Lines changed: 72 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,42 @@
1+
use std::collections::HashMap;
2+
13
use triton_vm::prelude::*;
24

35
use crate::prelude::*;
6+
use crate::traits::basic_snippet::Reviewer;
7+
use crate::traits::basic_snippet::SignOffFingerprint;
48

5-
pub const MERKLE_AUTHENTICATION_ROOT_MISMATCH_ERROR: i128 = 2;
6-
7-
/// Verify membership in a Merkle tree.
9+
/// Verify membership in a [Merkle tree](twenty_first::prelude::MerkleTree).
10+
///
11+
/// Verify that a leaf lives in a Merkle tree, given the tree's root, its
12+
/// height, the leaf's index, and the leaf itself. The authentication path is
13+
/// non-deterministically divined. This algorithm asserts that the leaf is a
14+
/// member of the tree; phrased differently, if membership could not be
15+
/// established, it crashes the VM.
16+
///
17+
/// ### Behavior
18+
///
19+
/// ```text
20+
/// BEFORE: _ [root: Digest] tree_height leaf_index [leaf: Digest]
21+
/// AFTER: _
22+
/// ```
23+
///
24+
/// ### Preconditions
25+
///
26+
/// - all input arguments are properly [`BFieldCodec`] encoded
27+
///
28+
/// ### Postconditions
829
///
9-
/// MerkleVerify -- verify that a leaf lives in a Merkle tree,
10-
/// given the root, leaf index, and leaf. The authentication path
11-
/// is non-deterministically divined. This algorithm asserts
12-
/// that the path is valid; phrased differently, it crashes the
13-
/// VM if it is not.
30+
/// None.
1431
#[derive(Clone, Debug)]
1532
pub struct MerkleVerify;
1633

34+
impl MerkleVerify {
35+
pub const TREE_TOO_HIGH_ERROR_ID: i128 = 0;
36+
pub const OUT_OF_BOUNDS_LEAF_ERROR_ID: i128 = 1;
37+
pub const ROOT_MISMATCH_ERROR_ID: i128 = 2;
38+
}
39+
1740
impl BasicSnippet for MerkleVerify {
1841
fn inputs(&self) -> Vec<(DataType, String)> {
1942
vec![
@@ -32,7 +55,7 @@ impl BasicSnippet for MerkleVerify {
3255
"tasmlib_hashing_merkle_verify".to_string()
3356
}
3457

35-
fn code(&self, _library: &mut Library) -> Vec<LabelledInstruction> {
58+
fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> {
3659
let entrypoint = self.entrypoint();
3760
let traverse_tree = format!("{entrypoint}_traverse_tree");
3861
let tree_height_is_not_zero = format!("{entrypoint}_tree_height_is_not_zero");
@@ -42,15 +65,19 @@ impl BasicSnippet for MerkleVerify {
4265
{entrypoint}:
4366
/* Assert reasonable tree height
4467
*
45-
* Don't rely on `pow`'s `is_u32` and `assert leaf_index < num_leaves` only:
46-
* Since bfe!(2)^3784253760 == 1 ∧ 3784253760 < 4294967295 == u32::MAX, weird
47-
* things are possible. It is not immediately obvious how this translates into
48-
* an attack, but there's no point in leaving a potential attack vector open.
68+
* Don't rely only on
69+
* 1. `pow`'s implicit check that the exponent is a u32,
70+
* 2. `assert leaf_index < num_leaves`.
71+
* Since bfe!(2)^192 == 1 and 192 < u32::MAX, weird things are possible. For
72+
* example, the number of leafs for a tree of height 193 would incorrectly be
73+
* computed as 2.
74+
* Any attack would probably still require a hash collision to work, but there's
75+
* no point in leaving a potential attack vector open.
4976
*/
5077
push 32
5178
dup 7
5279
lt
53-
assert error_id 0
80+
assert error_id {Self::TREE_TOO_HIGH_ERROR_ID}
5481

5582
/* Calculate node index from tree height and leaf index */
5683
dup 6
@@ -61,7 +88,7 @@ impl BasicSnippet for MerkleVerify {
6188
dup 0 dup 7 lt
6289
// _ [root; 5] tree_height leaf_index [leaf; 5] num_leaves (leaf_index < num_leaves)
6390

64-
assert error_id 1
91+
assert error_id {Self::OUT_OF_BOUNDS_LEAF_ERROR_ID}
6592
// _ [root; 5] tree_height leaf_index [leaf; 5] num_leaves
6693

6794
pick 6
@@ -71,43 +98,44 @@ impl BasicSnippet for MerkleVerify {
7198
place 5
7299
// _ [root; 5] tree_height node_index [leaf; 5]
73100

74-
dup 6
101+
pick 6
75102
skiz
76103
call {tree_height_is_not_zero}
77-
78-
// _ [root; 5] [0|1] [0|1] [calculated_root; 5]
104+
// _ [root; 5] [0|1] [calculated_root; 5]
79105

80106
/* compare calculated and provided root */
81-
82107
pick 5
83-
pick 6
84-
pop 2
85-
// _ [root; 5] [calculated_root; 5]
86-
87-
assert_vector error_id {MERKLE_AUTHENTICATION_ROOT_MISMATCH_ERROR}
108+
pop 1
109+
assert_vector error_id {Self::ROOT_MISMATCH_ERROR_ID}
88110
pop 5
89111

90112
return
91113

114+
// BEFORE: _ node_index [leaf; 5]
92115
{tree_height_is_not_zero}:
93-
// _ [root; 5] tree_height node_index [leaf; 5]
94-
95116
push 1
96-
swap 7
97-
pop 1
98-
// _ [root; 5] 1 node_index [leaf; 5]
117+
place 6
118+
// _ 1 node_index [leaf; 5]
99119

100120
call {traverse_tree}
101-
// _ [root; 5] 1 1 [calculated_root; 5]
121+
// _ 1 1 [calculated_root; 5]
122+
123+
pick 6
124+
pop 1
102125

103126
return
104127

105128
{traverse_tree}:
106129
merkle_step
107130
recurse_or_return
108-
109131
)
110132
}
133+
134+
fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> {
135+
let mut sign_offs = HashMap::new();
136+
sign_offs.insert(Reviewer("ferdinand"), 0x54be0725136e609e.into());
137+
sign_offs
138+
}
111139
}
112140

113141
#[cfg(test)]
@@ -173,17 +201,18 @@ mod tests {
173201
let leaf = rng.gen();
174202

175203
// walk up tree to calculate root
176-
let mut node_digest = leaf;
204+
let mut current_node = leaf;
177205
let mut node_index = leaf_index + num_leaves;
178206
for &sibling in &path {
179207
let node_is_left_sibling = node_index % 2 == 0;
180-
node_digest = match node_is_left_sibling {
181-
true => Tip5::hash_pair(node_digest, sibling),
182-
false => Tip5::hash_pair(sibling, node_digest),
208+
current_node = if node_is_left_sibling {
209+
Tip5::hash_pair(current_node, sibling)
210+
} else {
211+
Tip5::hash_pair(sibling, current_node)
183212
};
184213
node_index /= 2;
185214
}
186-
let root = node_digest;
215+
let root = current_node;
187216

188217
let mut stack = Self.init_stack_for_isolated_run();
189218
push_encodable(&mut stack, &root);
@@ -286,10 +315,14 @@ mod tests {
286315
.digests
287316
.extend(additional_bogus_tree_nodes);
288317

318+
let expected_errors = [
319+
MerkleVerify::OUT_OF_BOUNDS_LEAF_ERROR_ID,
320+
MerkleVerify::ROOT_MISMATCH_ERROR_ID,
321+
];
289322
test_assertion_failure(
290323
&ShadowedReadOnlyAlgorithm::new(MerkleVerify),
291324
initial_state.into(),
292-
&[1, 2],
325+
&expected_errors,
293326
);
294327
}
295328

@@ -306,7 +339,7 @@ mod tests {
306339
test_assertion_failure(
307340
&ShadowedReadOnlyAlgorithm::new(MerkleVerify),
308341
initial_state.into(),
309-
&[0],
342+
&[MerkleVerify::TREE_TOO_HIGH_ERROR_ID],
310343
);
311344
}
312345

@@ -342,7 +375,7 @@ mod tests {
342375
test_assertion_failure(
343376
&ShadowedReadOnlyAlgorithm::new(MerkleVerify),
344377
initial_state.into(),
345-
&[2],
378+
&[MerkleVerify::ROOT_MISMATCH_ERROR_ID],
346379
);
347380
}
348381
}

0 commit comments

Comments
 (0)