Skip to content

Commit af6d732

Browse files
authored
feat(AVM)!: Implement bytecodes limit in circuit (#16684)
Implements bytecodes limit in circuit via a transient tree
2 parents 7f1b198 + 85c6584 commit af6d732

File tree

79 files changed

+2918
-506
lines changed

Some content is hidden

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

79 files changed

+2918
-506
lines changed

barretenberg/cpp/pil/vm2/bytecode/bc_retrieval.pil

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,15 @@ pol commit private_function_root;
6161
// These should be looked up and constrained by the caller.
6262
pol commit public_data_tree_root;
6363
pol commit nullifier_tree_root;
64+
pol commit prev_retrieved_bytecodes_tree_root;
65+
pol commit prev_retrieved_bytecodes_tree_size;
66+
67+
// next state
68+
pol commit next_retrieved_bytecodes_tree_root;
69+
pol commit next_retrieved_bytecodes_tree_size;
6470

6571
pol commit instance_exists;
66-
// The only error that can happen is if the nullifier does not exist.
67-
error = sel * (1 - instance_exists);
72+
instance_exists * (1 - instance_exists) = 0;
6873

6974
#[CONTRACT_INSTANCE_RETRIEVAL]
7075
sel {
@@ -81,11 +86,45 @@ sel {
8186
contract_instance_retrieval.nullifier_tree_root
8287
};
8388

89+
pol commit no_remaining_bytecodes;
90+
no_remaining_bytecodes * (1 - no_remaining_bytecodes) = 0;
91+
92+
// The tree size is 1 (AVM_RETRIEVED_BYTECODES_TREE_INITIAL_SIZE) + retrieved_bytecodes_count
93+
pol REMAINING_BYTECODES = constants.MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS + constants.AVM_RETRIEVED_BYTECODES_TREE_INITIAL_SIZE - prev_retrieved_bytecodes_tree_size;
94+
95+
pol commit remaining_bytecodes_inv;
96+
97+
#[NO_REMAINING_BYTECODES]
98+
sel * (REMAINING_BYTECODES * (no_remaining_bytecodes * (1 - remaining_bytecodes_inv) + remaining_bytecodes_inv) - 1 + no_remaining_bytecodes) = 0;
99+
100+
pol commit is_new_class;
101+
102+
#[IS_NEW_CLASS_CHECK]
103+
instance_exists {
104+
current_class_id,
105+
is_new_class,
106+
prev_retrieved_bytecodes_tree_root
107+
} in retrieved_bytecodes_tree_check.sel {
108+
retrieved_bytecodes_tree_check.class_id,
109+
retrieved_bytecodes_tree_check.leaf_not_exists,
110+
retrieved_bytecodes_tree_check.root
111+
};
112+
113+
sel * (1 - instance_exists) * is_new_class = 0;
114+
115+
pol TOO_MANY_BYTECODES = no_remaining_bytecodes * is_new_class;
116+
117+
// We error if instance doesn't exist or if we have too many bytecodes.
118+
sel * (instance_exists * (1 - TOO_MANY_BYTECODES) - (1 - error)) = 0;
119+
120+
pol commit should_retrieve;
121+
should_retrieve = sel * (1 - error);
122+
84123
// Observe the following also connects the current_class_id of the instance to the class members.
85124
// Note: only need to derive the class id if the instance exists.
86125
// TODO: Probably some latch is also needed.
87126
#[CLASS_ID_DERIVATION]
88-
instance_exists {
127+
should_retrieve {
89128
current_class_id,
90129
artifact_hash,
91130
private_function_root,
@@ -97,20 +136,42 @@ instance_exists {
97136
class_id_derivation.public_bytecode_commitment
98137
};
99138

139+
#[RETRIEVED_BYTECODES_INSERTION]
140+
should_retrieve {
141+
current_class_id,
142+
should_retrieve, // 1
143+
prev_retrieved_bytecodes_tree_root,
144+
prev_retrieved_bytecodes_tree_size,
145+
next_retrieved_bytecodes_tree_root,
146+
next_retrieved_bytecodes_tree_size
147+
} in retrieved_bytecodes_tree_check.sel {
148+
retrieved_bytecodes_tree_check.class_id,
149+
retrieved_bytecodes_tree_check.write,
150+
retrieved_bytecodes_tree_check.root,
151+
retrieved_bytecodes_tree_check.tree_size_before_write,
152+
retrieved_bytecodes_tree_check.write_root,
153+
retrieved_bytecodes_tree_check.tree_size_after_write
154+
};
155+
100156
// TODO(dbanks12): re-enable once C++ and PIL use standard poseidon2 hashing for bytecode commitments.
101-
// Note: only need to hash the bytecode if the instance exists. Otherwise there is nothing to hash!
157+
// Note: only need to hash the bytecode if there is no error. Otherwise there is nothing to hash!
102158
//#[BYTECODE_HASH_IS_CORRECT]
103-
//instance_exists { bytecode_id, /*public_bytecode_commitment=*/ bytecode_id } in bc_hashing.latch { bc_hashing.bytecode_id, bc_hashing.output_hash };
159+
//should_retrieve { bytecode_id, /*public_bytecode_commitment=*/ bytecode_id } in bc_hashing.latch { bc_hashing.bytecode_id, bc_hashing.output_hash };
104160

105-
// If class ID derivation is disabled (instance does not exist), force all members to 0.
161+
// If instance does not exist, force current_class_id to 0.
106162
#[CURRENT_CLASS_ID_IS_ZERO_IF_INSTANCE_DOES_NOT_EXIST]
107163
sel * (1 - instance_exists) * (current_class_id - 0) = 0;
108-
#[ARTIFACT_HASH_IS_ZERO_IF_INSTANCE_DOES_NOT_EXIST]
109-
sel * (1 - instance_exists) * (artifact_hash - 0) = 0;
110-
#[PRIVATE_FUNCTION_ROOT_IS_ZERO_IF_INSTANCE_DOES_NOT_EXIST]
111-
sel * (1 - instance_exists) * (private_function_root - 0) = 0;
112-
#[BYTECODE_ID_IS_ZERO_IF_INSTANCE_DOES_NOT_EXIST]
113-
sel * (1 - instance_exists) * (bytecode_id - 0) = 0;
164+
// If class ID derivation is disabled (error), force other members to 0.
165+
#[ARTIFACT_HASH_IS_ZERO_IF_ERROR]
166+
error * (artifact_hash - 0) = 0;
167+
#[PRIVATE_FUNCTION_ROOT_IS_ZERO_IF_ERROR]
168+
error * (private_function_root - 0) = 0;
169+
#[BYTECODE_ID_IS_ZERO_IF_ERROR]
170+
error * (bytecode_id - 0) = 0;
171+
172+
// On error, constrain next root and size to be the same as the previous ones.
173+
error * (next_retrieved_bytecodes_tree_root - prev_retrieved_bytecodes_tree_root) = 0;
174+
error * (next_retrieved_bytecodes_tree_size - prev_retrieved_bytecodes_tree_size) = 0;
114175

115176
// Note: we don't need to silo and check the class id because the deployer contract guarrantees
116177
// that if a contract instance exists, the class has been registered.

barretenberg/cpp/pil/vm2/constants_gen.pil

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ namespace constants;
145145
pol AVM_WRITTEN_PUBLIC_DATA_SLOTS_TREE_HEIGHT = 6;
146146
pol AVM_WRITTEN_PUBLIC_DATA_SLOTS_TREE_INITIAL_ROOT = 18291678969210913367302010540259942201271604198321103848479209155223586227821;
147147
pol AVM_WRITTEN_PUBLIC_DATA_SLOTS_TREE_INITIAL_SIZE = 1;
148+
pol AVM_RETRIEVED_BYTECODES_TREE_HEIGHT = 5;
149+
pol AVM_RETRIEVED_BYTECODES_TREE_INITIAL_ROOT = 7257575663883662864904159007845791361042428565864275462740313586853981161757;
150+
pol AVM_RETRIEVED_BYTECODES_TREE_INITIAL_SIZE = 1;
148151
pol TIMESTAMP_OF_CHANGE_BIT_SIZE = 32;
149152
pol UPDATES_DELAYED_PUBLIC_MUTABLE_VALUES_LEN = 3;
150153
pol UPDATES_DELAYED_PUBLIC_MUTABLE_METADATA_BIT_SIZE = 144;

barretenberg/cpp/pil/vm2/context.pil

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ namespace execution;
8383
// L1 to L2 tree doesn't evolve during execution of the AVM
8484
pol commit l1_l2_tree_root;
8585

86+
pol commit prev_retrieved_bytecodes_tree_root;
87+
pol commit prev_retrieved_bytecodes_tree_size;
88+
8689
// Next Tree State
8790
pol commit note_hash_tree_root; // TODO: Constrain root, sizes and emitted not changing unless specific opcode selectors are on
8891
pol commit note_hash_tree_size;
@@ -98,6 +101,9 @@ namespace execution;
98101
pol commit written_public_data_slots_tree_root;
99102
pol commit written_public_data_slots_tree_size;
100103

104+
pol commit retrieved_bytecodes_tree_root;
105+
pol commit retrieved_bytecodes_tree_size;
106+
101107
// Prev side effects state
102108
pol commit prev_num_unencrypted_logs;
103109
pol commit prev_num_l2_to_l1_messages;
@@ -307,6 +313,13 @@ namespace execution;
307313
#[PARENT_DA_GAS_USED_STORE_ON_ENTER]
308314
NOT_LAST_EXEC * sel_enter_call * (parent_da_gas_used' - da_gas_used) = 0;
309315

316+
// The state of the retrieved bytecodes tree should be continuous unless we have finished an enqueued call
317+
// The tx trace looks up the tree state at the start and end of an enqueued call
318+
#[RETRIEVED_BYTECODES_TREE_ROOT_CONTINUITY]
319+
sel * (1 - enqueued_call_end) * (retrieved_bytecodes_tree_root - prev_retrieved_bytecodes_tree_root') = 0;
320+
#[RETRIEVED_BYTECODES_TREE_SIZE_CONTINUITY]
321+
sel * (1 - enqueued_call_end) * (retrieved_bytecodes_tree_size - prev_retrieved_bytecodes_tree_size') = 0;
322+
310323
#[CTX_STACK_CALL]
311324
sel_enter_call {
312325
next_context_id,
@@ -333,6 +346,7 @@ namespace execution;
333346
public_data_tree_size,
334347
written_public_data_slots_tree_root,
335348
written_public_data_slots_tree_size,
349+
// Retrieved bytecodes tree is not commited and restored, since it's a tx global state.
336350
num_unencrypted_logs,
337351
num_l2_to_l1_messages
338352
} in

barretenberg/cpp/pil/vm2/execution.pil

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ include "trees/nullifier_check.pil";
2828
include "trees/public_data_check.pil";
2929
include "trees/written_public_data_slots_tree_check.pil";
3030
include "trees/l1_to_l2_message_tree_check.pil";
31+
include "trees/retrieved_bytecodes_tree_check.pil";
3132

3233
include "bytecode/address_derivation.pil";
3334
include "bytecode/bc_decomposition.pil";
@@ -108,12 +109,20 @@ sel_first_row_in_context {
108109
contract_address,
109110
prev_nullifier_tree_root, // from context.pil
110111
prev_public_data_tree_root, // from context.pil
112+
prev_retrieved_bytecodes_tree_root, // from context.pil
113+
prev_retrieved_bytecodes_tree_size, // from context.pil
114+
retrieved_bytecodes_tree_root, // from context.pil
115+
retrieved_bytecodes_tree_size, // from context.pil
111116
sel_bytecode_retrieval_failure
112117
} in bc_retrieval.sel {
113118
bc_retrieval.bytecode_id,
114119
bc_retrieval.address,
115120
bc_retrieval.nullifier_tree_root,
116121
bc_retrieval.public_data_tree_root,
122+
bc_retrieval.prev_retrieved_bytecodes_tree_root,
123+
bc_retrieval.prev_retrieved_bytecodes_tree_size,
124+
bc_retrieval.next_retrieved_bytecodes_tree_root,
125+
bc_retrieval.next_retrieved_bytecodes_tree_size,
117126
bc_retrieval.error
118127
};
119128

@@ -641,6 +650,11 @@ sel * (1 - sel_execute_emit_nullifier) * (prev_num_nullifiers_emitted - num_null
641650
sel * (1 - sel_execute_emit_unencrypted_log) * (prev_num_unencrypted_logs - num_unencrypted_logs) = 0;
642651
#[NUM_L2_TO_L1_MESSAGES_NOT_CHANGED]
643652
sel * (1 - sel_execute_send_l2_to_l1_msg) * (prev_num_l2_to_l1_messages - num_l2_to_l1_messages) = 0;
653+
// Retrieved bytecodes tree state can only change in the first row of a new context
654+
#[RETRIEVED_BYTECODES_TREE_ROOT_NOT_CHANGED]
655+
sel * (1 - sel_first_row_in_context) * (prev_retrieved_bytecodes_tree_root - retrieved_bytecodes_tree_root) = 0;
656+
#[RETRIEVED_BYTECODES_TREE_SIZE_NOT_CHANGED]
657+
sel * (1 - sel_first_row_in_context) * (prev_retrieved_bytecodes_tree_size - retrieved_bytecodes_tree_size) = 0;
644658

645659
// Some opcodes cannot fail during opcode execution. They should not be able to set sel_opcode_error.
646660
// Note that some of these opcodes can fail in the stages before execution.
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
include "merkle_check.pil";
2+
3+
include "../ff_gt.pil";
4+
include "../poseidon2_hash.pil";
5+
include "../constants_gen.pil";
6+
include "../precomputed.pil";
7+
8+
// This gadget is used to track the unique class ids that have been retrieved in the TX.
9+
// It's a transient indexed tree that starts empty (with a prefill) on every transaction,
10+
// and it's discarded at the end of the transaction execution.
11+
// The leaves only contain the class id and the indexed tree pointers.
12+
//
13+
// Read usage:
14+
// sel {
15+
// class_id,
16+
// leaf_not_exists,
17+
// retrieved_bytecodes_tree_root
18+
// } in retrieved_bytecodes_tree_check.sel {
19+
// retrieved_bytecodes_tree_check.class_id,
20+
// retrieved_bytecodes_tree_check.leaf_not_exists,
21+
// retrieved_bytecodes_tree_check.root
22+
// };
23+
//
24+
// Write usage:
25+
// sel {
26+
// class_id,
27+
// sel, // 1
28+
// prev_retrieved_bytecodes_tree_root,
29+
// prev_retrieved_bytecodes_tree_size,
30+
// next_retrieved_bytecodes_tree_root,
31+
// next_retrieved_bytecodes_tree_size
32+
// } in retrieved_bytecodes_tree_check.sel {
33+
// retrieved_bytecodes_tree_check.class_id,
34+
// retrieved_bytecodes_tree_check.write,
35+
// retrieved_bytecodes_tree_check.root,
36+
// retrieved_bytecodes_tree_check.tree_size_before_write,
37+
// retrieved_bytecodes_tree_check.write_root,
38+
// retrieved_bytecodes_tree_check.tree_size_after_write
39+
// };
40+
41+
namespace retrieved_bytecodes_tree_check;
42+
pol commit sel;
43+
sel * (1 - sel) = 0;
44+
45+
#[skippable_if]
46+
sel = 0;
47+
48+
// Inputs to the gadget
49+
pol commit write;
50+
write * (1 - write) = 0;
51+
pol READ = 1 - write;
52+
53+
pol commit class_id;
54+
pol commit root;
55+
pol commit leaf_not_exists;
56+
leaf_not_exists * (1 - leaf_not_exists) = 0;
57+
pol EXISTS = 1 - leaf_not_exists;
58+
59+
// Write specific inputs
60+
pol commit write_root;
61+
pol commit tree_size_before_write;
62+
pol commit tree_size_after_write;
63+
64+
// Hints
65+
pol commit low_leaf_class_id;
66+
pol commit low_leaf_next_index;
67+
pol commit low_leaf_next_class_id;
68+
69+
pol commit updated_low_leaf_next_index;
70+
pol commit updated_low_leaf_next_class_id;
71+
72+
pol commit low_leaf_index;
73+
74+
// ========= HANDLE REDUNDANT WRITES =========
75+
pol commit should_insert;
76+
should_insert = write * leaf_not_exists;
77+
// On a failing write, the root must not change
78+
write * EXISTS * (root - write_root) = 0;
79+
80+
tree_size_after_write = tree_size_before_write + should_insert;
81+
82+
// ========= COMPUTE LOW LEAF UPDATE =========
83+
should_insert * (tree_size_before_write - updated_low_leaf_next_index) = 0;
84+
should_insert * (class_id - updated_low_leaf_next_class_id) = 0;
85+
86+
// ========= LOW LEAF MERKLE CHECK =========
87+
pol commit low_leaf_hash;
88+
// The intermediate root is the root of the tree after the low leaf update but before the new leaf is inserted.
89+
pol commit intermediate_root;
90+
// TODO: We need this temporarily while we do not allow for aliases in the lookup tuple
91+
pol commit tree_height;
92+
sel * (constants.AVM_RETRIEVED_BYTECODES_TREE_HEIGHT - tree_height) = 0;
93+
94+
#[LOW_LEAF_POSEIDON2]
95+
sel { sel, low_leaf_class_id, low_leaf_next_class_id, low_leaf_next_index, low_leaf_hash }
96+
in poseidon2_hash.end { poseidon2_hash.start, poseidon2_hash.input_0, poseidon2_hash.input_1, poseidon2_hash.input_2, poseidon2_hash.output };
97+
98+
pol commit updated_low_leaf_hash;
99+
100+
#[UPDATED_LOW_LEAF_POSEIDON2]
101+
should_insert { sel, low_leaf_class_id, updated_low_leaf_next_class_id, updated_low_leaf_next_index, updated_low_leaf_hash }
102+
in poseidon2_hash.end { poseidon2_hash.start, poseidon2_hash.input_0, poseidon2_hash.input_1, poseidon2_hash.input_2, poseidon2_hash.output };
103+
104+
#[LOW_LEAF_MERKLE_CHECK]
105+
sel { should_insert, low_leaf_hash, updated_low_leaf_hash,
106+
low_leaf_index, tree_height, root, intermediate_root }
107+
in merkle_check.start { merkle_check.write, merkle_check.read_node, merkle_check.write_node,
108+
merkle_check.index, merkle_check.path_len, merkle_check.read_root, merkle_check.write_root };
109+
110+
// ========= LOW LEAF VALIDATION =========
111+
pol commit class_id_low_leaf_class_id_diff_inv;
112+
pol CLASS_ID_LOW_LEAF_CLASS_ID_DIFF = class_id - low_leaf_class_id;
113+
114+
// CLASS_ID_LOW_LEAF_CLASS_ID_DIFF == 0 <==> EXISTS == 1
115+
#[EXISTS_CHECK]
116+
sel * (CLASS_ID_LOW_LEAF_CLASS_ID_DIFF * (EXISTS * (1 - class_id_low_leaf_class_id_diff_inv) + class_id_low_leaf_class_id_diff_inv) - 1 + EXISTS) = 0;
117+
118+
// If the leaf doesn't exist, we need to validate that the class id is greater than the low leaf class id
119+
120+
#[LOW_LEAF_CLASS_ID_VALIDATION]
121+
leaf_not_exists { class_id, low_leaf_class_id, sel }
122+
in ff_gt.sel_gt { ff_gt.a, ff_gt.b, ff_gt.result };
123+
124+
// If next class id is not zero (which would be infinity), it has to be greater than the class id.
125+
// We commit next_class_id_is_nonzero instead of next_class_id_is_zero since it'll be used as a selector for a lookup
126+
pol commit next_class_id_is_nonzero;
127+
next_class_id_is_nonzero * (1 - next_class_id_is_nonzero) = 0;
128+
pol NEXT_CLASS_ID_IS_ZERO = 1 - next_class_id_is_nonzero;
129+
130+
pol commit next_class_id_inv;
131+
#[NEXT_CLASS_ID_IS_ZERO_CHECK]
132+
leaf_not_exists * (low_leaf_next_class_id * (NEXT_CLASS_ID_IS_ZERO * (1 - next_class_id_inv) + next_class_id_inv) - 1 + NEXT_CLASS_ID_IS_ZERO) = 0;
133+
134+
#[LOW_LEAF_NEXT_CLASS_ID_VALIDATION]
135+
next_class_id_is_nonzero { low_leaf_next_class_id, class_id, sel }
136+
in ff_gt.sel_gt { ff_gt.a, ff_gt.b, ff_gt.result };
137+
138+
// ========= NEW LEAF INSERTION =========
139+
pol commit new_leaf_hash;
140+
141+
#[NEW_LEAF_POSEIDON2]
142+
should_insert { sel, class_id, low_leaf_next_class_id, low_leaf_next_index, new_leaf_hash }
143+
in poseidon2_hash.end { poseidon2_hash.start, poseidon2_hash.input_0, poseidon2_hash.input_1, poseidon2_hash.input_2, poseidon2_hash.output };
144+
145+
#[NEW_LEAF_MERKLE_CHECK]
146+
should_insert { sel, precomputed.zero, new_leaf_hash,
147+
tree_size_before_write, tree_height, intermediate_root, write_root }
148+
in merkle_check.start { merkle_check.write, merkle_check.read_node, merkle_check.write_node,
149+
merkle_check.index, merkle_check.path_len, merkle_check.read_root, merkle_check.write_root };
150+

0 commit comments

Comments
 (0)