Skip to content

Commit a24c341

Browse files
authored
feat: merge-train/avm (#17862)
BEGIN_COMMIT_OVERRIDE fix(avm)!: Append-only tree reads (#17757) chore: avm TS tests should only ever create a single world state instance (#17703) feat!: merkle check pre-audit (#17771) feat: AVM simulate via NAPI with hinted DBs (#17704) feat(avm): simulate with ws (#17635) chore: static batch inverses (#17899) END_COMMIT_OVERRIDE
2 parents a97aea8 + 769b1a0 commit a24c341

File tree

107 files changed

+2020
-889
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+2020
-889
lines changed

barretenberg/cpp/pil/vm2/poseidon2_hash.pil

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ namespace poseidon2_hash;
1818
#[skippable_if]
1919
sel = 0;
2020

21+
// If the current row is not active, then there are no more active rows after that.
22+
// Note that sel cannot be activated in the first row as sel' is defined.
23+
// As a consequence, if a row is activated (sel == 1) somewhere in this sub-trace, then
24+
// the activated rows start from the second row and are contiguous.
25+
#[TRACE_CONTINUITY]
26+
(1 - precomputed.first_row) * (1 - sel) * sel' = 0;
27+
2128
// === Input Values ===
2229
// Unpadded Length of the inputs to be hashed
2330
pol commit input_len;
@@ -46,8 +53,11 @@ namespace poseidon2_hash;
4653
// The last row of hashing for the given input
4754
pol commit end;
4855
end * (1 - end) = 0;
49-
// End and first row are NAND - they cannot both be one.
50-
end * precomputed.first_row = 0;
56+
57+
// Selector must be 1 in end row
58+
#[SELECTOR_ON_END]
59+
end * (1 - sel) = 0;
60+
5161
// Our latch condition can either be the designated end of the computation or the first row
5262
// The previous relation ensures they cannot both be 1
5363
pol LATCH_CONDITION = end + precomputed.first_row;

barretenberg/cpp/pil/vm2/trees/merkle_check.pil

Lines changed: 120 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,43 @@ include "../precomputed.pil";
33

44
/**
55
* A gadget that checks a merkle read or a write.
6-
* One sibling node is processed per row, performing 1 hash if it's a merkle read
7-
* or 2 hashes if it's a merkle write.
6+
* A merkle read consists in proving membership of a leaf (`read_leaf`) in a Merkle tree (against a given root).
7+
* For a merkle write, we additionally compute the new root of the tree with the new leaf (`updated_leaf`)
8+
* value. The sibling path is the same for both reads and writes and therefore the membership check and
9+
* the new root computation can be processed in "parallel".
10+
* One sibling node is processed per row, performing 1 hash (Poseidon2) if it is a merkle read
11+
* or 2 hashes if it is a merkle write.
812
*
913
* WARNING: This gadget will break if used with `tree_height >= 254`
1014
*
15+
* Precondition: tree_height/path_len < 254
16+
*
17+
* Errors: None
18+
*
19+
* Number of rows per computation: tree_height/path_len
20+
*
1121
* Read usage:
12-
* caller_sel { read_leaf, leaf_index, tree_height, expected_root }
13-
* in merkle_check.start { merkle_check.read_node, merkle_check.index, merkle_check.path_len, merkle_check.read_root }
22+
* caller_sel {
23+
* read_leaf, leaf_index, tree_height, expected_root
24+
* } in merkle_check.start {
25+
* merkle_check.read_node, merkle_check.index, merkle_check.path_len, merkle_check.read_root
26+
* };
1427
*
1528
* Write usage:
16-
* caller_sel { one, prev_leaf_value, next_leaf_value,
17-
* leaf_index, tree_height, prev_root, next_root }
18-
* in merkle_check.start { merkle_check.write, merkle_check.read_node, merkle_check.write_node,
19-
* merkle_check.index, merkle_check.path_len, merkle_check.read_root, merkle_check.write_root }
29+
* caller_sel {
30+
* one, current_leaf, updated_leaf,
31+
* leaf_index, tree_height, prev_root, next_root
32+
* } in merkle_check.start {
33+
* merkle_check.write, merkle_check.read_node, merkle_check.write_node,
34+
* merkle_check.index, merkle_check.path_len, merkle_check.read_root, merkle_check.write_root
35+
* };
36+
*
37+
* Read/Write flexible usage: One can call the write usage variant with a boolean in the first column
38+
* to toggle between read and write. However, the caller needs to ensure that trace generation will
39+
* set `updated_leaf` and `next_root` to 0 when `write` is 0.
40+
*
41+
* USAGE WARNING: Never invoke this gadget using `merkle_check.write` as destination selector. This would be
42+
* completely under-constrained as we do not enforce `sel == 1` when `write == 1`.
2043
*
2144
* Common inputs
2245
* @column read_node The value of the node being read. When `start == 1`, this is the leaf.
@@ -27,7 +50,10 @@ include "../precomputed.pil";
2750
* Write specific inputs
2851
* @column write Whether this is a write operation.
2952
* @column write_node The value of the node being written. When `start == 1`, this is the new leaf.
30-
* @column write_root The merkle root that results of replacing the leaf with the new leaf at the index provided in the read_root. This will be propagated down to be matched internally on `end`.
53+
*
54+
* Write specific output
55+
* @column write_root The merkle root that results of replacing the leaf with the new leaf at the index provided
56+
* in the read_root. This will be propagated down to be matched internally on `end`.
3157
*
3258
* Internals/hints
3359
* @column sibling The value of the sibling node to be hashed with `read_node`.
@@ -49,110 +75,121 @@ include "../precomputed.pil";
4975
* +-----------+-------+----------+---------+---------------+----------------+-----------------+------------------+-----------+-------+------------+-----------------+------------------+-------------------+------------+-------+-----+-----+
5076
*/
5177
namespace merkle_check;
52-
pol commit sel;
53-
sel * (1 - sel) = 0;
54-
// No relations will be checked if this identity is satisfied.
55-
#[skippable_if]
78+
#[skippable_if]
5679
sel = 0;
5780

58-
// If the current row is not active, then there are no more active rows after that.
59-
// Note that sel cannot be activated in the first row as sel' is defined.
60-
// As a consequence, if a row is activated (sel == 1) somewhere in this sub-trace, then
61-
// the activated rows start from the second row and are contiguous.
62-
#[TRACE_CONTINUITY]
63-
(1 - precomputed.first_row) * (1 - sel) * sel' = 0;
81+
pol commit sel; // @boolean
82+
sel * (1 - sel) = 0;
6483

6584
// Common inputs - These must be looked up both on read and write
6685
pol commit read_node;
6786
pol commit index;
6887
pol commit path_len;
6988
pol commit read_root;
7089

71-
// Write specific inputs - These are only looked up when we want to update the root
72-
pol commit write;
73-
write * (1 - write) = 0; // bool
90+
// Write specific inputs/outputs - These are only looked up when we want to update the root
91+
pol commit write; // This should be a boolean passed by the caller. For a read usage, it is under-constrained but read
92+
// relevant values are constrained. For write, the caller must pass 1.
7493
pol commit write_node;
7594
pol commit write_root;
7695

7796
// Hints
7897
pol commit sibling;
7998

8099
// Boundaries
81-
pol commit start;
82-
pol commit end;
83-
pol NOT_END = sel * (1 - end);
84-
start * (1 - start) = 0; // bool
85-
end * (1 - end) = 0; // bool
86-
87-
// only one of end or first_row can be 1
88-
// end can't be 1 for first row
89-
end * precomputed.first_row = 0;
90-
// LATCH_CONDITION is true if either end is 1 or first_row is 1
91-
pol LATCH_CONDITION = end + precomputed.first_row;
92-
93-
#[START_AFTER_LATCH]
94-
sel' * (start' - LATCH_CONDITION) = 0;
100+
pol commit start; // @boolean
101+
pol commit end; // @boolean
102+
start * (1 - start) = 0;
103+
end * (1 - end) = 0;
95104

96-
// Selector must be 1 in end row
97-
#[SELECTOR_ON_END]
98-
end * (1 - sel) = 0;
105+
// End when remaining path reaches 1. In other words, (path_len == 1) <==> (end == 1)
106+
pol PATH_LEN_MIN_ONE = path_len - 1;
107+
pol commit path_len_min_one_inv;
108+
#[END_IFF_REM_PATH_EMPTY]
109+
sel * (PATH_LEN_MIN_ONE * (end * (1 - path_len_min_one_inv) + path_len_min_one_inv) - 1 + end) = 0;
110+
111+
// This prevents a computation to be stopped prematurely, i.e., truncating last rows before
112+
// we reach a row where end == 1. Note that #[END_IFF_REM_PATH_EMPTY] is conditioned on `sel == 1`
113+
// and a malicious prover could prematurely stop by setting `sel == 0` and `end == 0`.
114+
#[COMPUTATION_FINISH_AT_END]
115+
sel * (1 - sel') * (1 - end) = 0;
116+
117+
// This gadget is looked up based on `start` destination selector and therefore we
118+
// need to ensure that this computation is performed on the active (`sel == 1`) rows. Otherwise,
119+
// a malicious prover could circumvent most of the constraints. Note that #[START_AFTER_LATCH]
120+
// does not prevent this from happening.
121+
// In addition, we want `end == 1` to be on an active row as well.
122+
#[SELECTOR_ON_START_OR_END]
123+
(start + end) * (1 - sel) = 0;
124+
125+
// LATCH_CONDITION is true if either `end` is 1 or `first_row` is 1
126+
// LATCH_CONDITION is a boolean because `end` cannot be 1 on the first row (as `sel == 1` when `end == 1`
127+
// and `sel == 0` on first row)
128+
pol LATCH_CONDITION = end + precomputed.first_row;
99129

100130
#[PROPAGATE_READ_ROOT]
101-
NOT_END * (read_root' - read_root) = 0;
131+
(1 - LATCH_CONDITION) * (read_root' - read_root) = 0;
102132
#[PROPAGATE_WRITE]
103-
NOT_END * (write' - write) = 0;
133+
(1 - LATCH_CONDITION) * (write' - write) = 0;
104134
#[PROPAGATE_WRITE_ROOT]
105-
NOT_END * (write_root' - write_root) = 0;
135+
(1 - LATCH_CONDITION) * (write_root' - write_root) = 0;
106136

107137
// If we are not done, the path_len decrements by 1
108138
#[PATH_LEN_DECREMENTS]
109-
NOT_END * (path_len' - path_len + 1) = 0;
139+
sel * (1 - end) * (path_len' - path_len + 1) = 0;
110140

111-
// End when remaining path reaches 1. In other words, (path_len == 1) <==> (end == 1)
112-
pol REMAINING_PATH_LEN = path_len - 1;
113-
114-
pol commit remaining_path_len_inv;
115-
#[END_WHEN_PATH_EMPTY]
116-
sel * (REMAINING_PATH_LEN * (end * (1 - remaining_path_len_inv) + remaining_path_len_inv) - 1 + end) = 0;
117-
118-
// index_is_even is constrained to be correct by the NEXT_INDEX_IS_HALVED and FINAL_INDEX_IS_0_OR_1 constraints
119-
pol commit index_is_even;
141+
// index_is_even is constrained to be correct by the NEXT_INDEX_IS_HALVED and FINAL_INDEX_IS_ODD_BOOLEAN constraints
142+
pol commit index_is_even; // @boolean
120143
index_is_even * (1 - index_is_even) = 0;
121144
pol INDEX_IS_ODD = (1 - index_is_even);
122145
// The index into the next layer is half the current index.
123146
// We don't need to worry about underflowing the field since (index - INDEX_IS_ODD)
124147
// will be even (over the integers) and as the field is not of characteristic 2, index' == index / 2 over the integers
125148
#[NEXT_INDEX_IS_HALVED]
126-
NOT_END * (index' * 2 + INDEX_IS_ODD - index) = 0;
149+
sel * (1 - end) * (2 * index' + INDEX_IS_ODD - index) = 0;
127150

128-
// Ensure that the final index is 0 or 1.
151+
// Ensure that the final index is 0 or 1 by setting it to INDEX_IS_ODD.
129152
// This ensures that the previous layer cannot overflow the field in the halving constraint
130-
// when doing `index' * 2`. This propagates backwards ensuring that no
153+
// when doing `2 * index'`. This propagates backwards ensuring that no
131154
// layer can overflow on the halving constraint's multiplication by 2 as long as
132155
// tree_height < 254.
133-
#[FINAL_INDEX_IS_0_OR_1]
134-
end * (index * (1 - index)) = 0;
135-
// NOTE: index_is_even is essentially a vertical bit-decomposition of leaf_index.
156+
// In addition, this relation constrains the value of `index_is_even` for the last row. without
157+
// it `index_is_even` would be under-constrained and a malicious prover could swap the sibling
158+
// and the node at the top Merkle tree layer in some relations further below.
159+
#[FINAL_INDEX_EQUAL_TO_FIRST_BIT]
160+
end * (index - INDEX_IS_ODD) = 0;
161+
162+
// The bit decomposition of leaf_index corresponds to the INDEX_IS_ODD bits. Namely,
163+
// for an initial path_len == LEN, we have:
164+
// leaf_index = sum_{i=0}^{LEN-1} INDEX_IS_ODD[i] * 2^i
165+
// where INDEX_IS_ODD[i] denotes the value of INDEX_IS_ODD at row i (incrementing
166+
// top-down and starting from 0). Therefore, any leaf_index < 2^LEN will be decomposed
167+
// and constrained correctly.
136168

137169
// left_node and right_node are sent to poseidon2
138170
// The constraints below arrange node and sibling for read and write into proper left/right order
139171
pol commit read_left_node;
140172
pol commit read_right_node;
141173
pol commit write_left_node;
142174
pol commit write_right_node;
143-
// This is accomplished by using index_is_even to toggle the (left_node - right_node) term.
144-
// If index is even, left_node (to send to poseidon2) is node and right_node is sibling.
145-
// And vice-versa.
146-
#[ASSIGN_NODE_LEFT_OR_RIGHT_READ]
147-
sel * (index_is_even * (read_left_node - read_right_node) + read_right_node - read_node) = 0;
148-
#[ASSIGN_SIBLING_LEFT_OR_RIGHT_READ]
149-
sel * (index_is_even * (read_right_node - read_left_node) + read_left_node - sibling) = 0;
150-
151-
#[ASSIGN_NODE_LEFT_OR_RIGHT_WRITE]
152-
write * (index_is_even * (write_left_node - write_right_node) + write_right_node - write_node) = 0;
153-
#[ASSIGN_SIBLING_LEFT_OR_RIGHT_WRITE]
154-
write * (index_is_even * (write_right_node - write_left_node) + write_left_node - sibling) = 0;
155-
// NOTE: don't think these can be safely combined
175+
176+
// This is accomplished by using the value of `index_is_even`. If `index_is_even == 1`, it means that
177+
// the current node position at the current layer is even. As a layer index starts at 0, this means that
178+
// the current node is the left child of its parent and the sibling is the right child.
179+
// Conversly, if `index_is_even == 0`, the curren node is the right child of its parent and the sibling
180+
// is the left child.
181+
// For `index_is_even == 1`, we assign `read_left_node := read_node` and `read_right_node := sibling`.
182+
// For `index_is_even == 0`, we assign `read_left_node := sibling` and `read_right_node := read_node`.
183+
#[READ_LEFT_NODE]
184+
read_left_node = index_is_even * (read_node - sibling) + sibling;
185+
#[READ_RIGHT_NODE]
186+
read_right_node = index_is_even * (sibling - read_node) + read_node;
187+
188+
// Same as above for write (but conditioned on `write == 1`).
189+
#[WRITE_LEFT_NODE]
190+
write_left_node = write * (index_is_even * (write_node - sibling) + sibling);
191+
#[WRITE_RIGHT_NODE]
192+
write_right_node = write * (index_is_even * (sibling - write_node) + write_node);
156193

157194
// output_hash = hash(left_node, right_node)
158195
// if index_is_even: output_hash = hash(node, sibling)
@@ -163,18 +200,24 @@ namespace merkle_check;
163200

164201
// Lookup to the full poseidon2 gadget
165202
#[MERKLE_POSEIDON2_READ]
166-
sel { read_left_node, read_right_node, /*input_2=*/ precomputed.zero, /*start=1=*/ sel, read_output_hash }
167-
in poseidon2_hash.end { poseidon2_hash.input_0, poseidon2_hash.input_1, poseidon2_hash.input_2, poseidon2_hash.start, poseidon2_hash.output };
203+
sel {
204+
read_left_node, read_right_node, /*input_2=*/ precomputed.zero, /*start=1=*/ sel, read_output_hash
205+
} in poseidon2_hash.end {
206+
poseidon2_hash.input_0, poseidon2_hash.input_1, poseidon2_hash.input_2, poseidon2_hash.start, poseidon2_hash.output
207+
};
168208

169209
#[MERKLE_POSEIDON2_WRITE]
170-
write { write_left_node, write_right_node, /*input_2=*/ precomputed.zero, /*start=1=*/ sel, write_output_hash }
171-
in poseidon2_hash.end { poseidon2_hash.input_0, poseidon2_hash.input_1, poseidon2_hash.input_2, poseidon2_hash.start, poseidon2_hash.output };
210+
write {
211+
write_left_node, write_right_node, /*input_2=*/ precomputed.zero, /*start=1=*/ write, write_output_hash
212+
} in poseidon2_hash.end {
213+
poseidon2_hash.input_0, poseidon2_hash.input_1, poseidon2_hash.input_2, poseidon2_hash.start, poseidon2_hash.output
214+
};
172215

173216
// If we are not done, this row's output_hash is the next row's node
174217
#[OUTPUT_HASH_IS_NEXT_ROWS_READ_NODE]
175-
NOT_END * (read_node' - read_output_hash) = 0;
218+
(1 - LATCH_CONDITION) * (read_node' - read_output_hash) = 0;
176219
#[OUTPUT_HASH_IS_NEXT_ROWS_WRITE_NODE]
177-
NOT_END * (write_node' - write_output_hash) = 0;
220+
(1 - LATCH_CONDITION) * (write_node' - write_output_hash) = 0;
178221

179222
// If we are done, the output hash is the root
180223
#[READ_OUTPUT_HASH_IS_READ_ROOT]

barretenberg/cpp/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ set(BARRETENBERG_TARGET_OBJECTS
193193
if(NOT DISABLE_AZTEC_VM AND NOT FUZZING)
194194
# enable AVM
195195
list(APPEND BARRETENBERG_TARGET_OBJECTS $<TARGET_OBJECTS:vm2_objects>)
196+
list(APPEND BARRETENBERG_TARGET_OBJECTS $<TARGET_OBJECTS:world_state_objects>)
196197
endif()
197198

198199
if(NOT WASM AND NOT FUZZING)

barretenberg/cpp/src/barretenberg/api/api_avm.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ void avm_simulate(const std::filesystem::path& inputs_path)
8181
AVM_TRACK_TIME("command/avm_simulate", {
8282
avm2::AvmAPI avm;
8383
auto inputs = avm2::AvmAPI::ProvingInputs::from(read_file(inputs_path));
84-
avm.simulate(inputs.hints);
84+
avm.simulate_with_hinted_dbs(inputs);
8585
});
8686

8787
print_avm_stats();

0 commit comments

Comments
 (0)