Skip to content

Commit 9301253

Browse files
authored
feat: GETCONTRACTINSTANCE and bytecode retrieval perform nullifier membership checks (#10445)
Resolves #10376 Resolves #10377 Resolves #10378 Resolves #10379
1 parent 89cb8d3 commit 9301253

27 files changed

+561
-154
lines changed

barretenberg/cpp/src/barretenberg/bb/main.cpp

+2-3
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,6 @@ void vk_as_fields(const std::string& vk_path, const std::string& output_path)
587587
* Communication:
588588
* - Filesystem: The proof and vk are written to the paths output_path/proof and output_path/{vk, vk_fields.json}
589589
*
590-
* @param bytecode_path Path to the file containing the serialised bytecode
591590
* @param public_inputs_path Path to the file containing the serialised avm public inputs
592591
* @param hints_path Path to the file containing the serialised avm circuit hints
593592
* @param output_path Path (directory) to write the output proof and verification keys
@@ -597,8 +596,8 @@ void avm_prove(const std::filesystem::path& public_inputs_path,
597596
const std::filesystem::path& output_path)
598597
{
599598

600-
auto const avm_public_inputs = AvmPublicInputs::from(read_file(public_inputs_path));
601-
auto const avm_hints = bb::avm_trace::ExecutionHints::from(read_file(hints_path));
599+
const auto avm_public_inputs = AvmPublicInputs::from(read_file(public_inputs_path));
600+
const auto avm_hints = bb::avm_trace::ExecutionHints::from(read_file(hints_path));
602601

603602
// Using [0] is fine now for the top-level call, but we might need to index by address in future
604603
vinfo("bytecode size: ", avm_hints.all_contract_bytecode[0].bytecode.size());

barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ class AvmExecutionTests : public ::testing::Test {
119119
PublicKeysHint public_keys{ nullifier_key, incoming_viewing_key, outgoing_viewing_key, tagging_key };
120120
ContractInstanceHint contract_instance = {
121121
FF::one() /* temp address */, true /* exists */, FF(2) /* salt */, FF(3) /* deployer_addr */, class_id,
122-
FF(8) /* initialisation_hash */, public_keys
122+
FF(8) /* initialisation_hash */, public_keys,
123+
/*membership_hint=*/ { .low_leaf_preimage = { .nullifier = 0, .next_nullifier = 0, .next_index = 0, }, .low_leaf_index = 0, .low_leaf_sibling_path = {} },
123124
};
124125
FF address = AvmBytecodeTraceBuilder::compute_address_from_instance(contract_instance);
125126
contract_instance.address = address;
@@ -2348,6 +2349,8 @@ TEST_F(AvmExecutionTests, opCallOpcodes)
23482349

23492350
TEST_F(AvmExecutionTests, opGetContractInstanceOpcode)
23502351
{
2352+
// FIXME: Skip until we have an easy way to mock contract instance nullifier memberhip
2353+
GTEST_SKIP();
23512354
const uint8_t address_byte = 0x42;
23522355
const FF address(address_byte);
23532356

@@ -2369,6 +2372,7 @@ TEST_F(AvmExecutionTests, opGetContractInstanceOpcode)
23692372
.contract_class_id = 66,
23702373
.initialisation_hash = 99,
23712374
.public_keys = public_keys_hints,
2375+
.membership_hint = { .low_leaf_preimage = { .nullifier = 0, .next_nullifier = 0, .next_index = 0, }, .low_leaf_index = 0, .low_leaf_sibling_path = {} },
23722376
};
23732377
auto execution_hints = ExecutionHints().with_contract_instance_hints({ { address, instance } });
23742378

barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp

+11-8
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ std::vector<FF> Execution::getDefaultPublicInputs()
206206
* @brief Run the bytecode, generate the corresponding execution trace and prove the correctness
207207
* of the execution of the supplied bytecode.
208208
*
209-
* @param bytecode A vector of bytes representing the bytecode to execute.
210209
* @throws runtime_error exception when the bytecode is invalid.
211210
* @return The verifier key and zk proof of the execution.
212211
*/
@@ -219,7 +218,7 @@ std::tuple<AvmFlavor::VerificationKey, HonkProof> Execution::prove(AvmPublicInpu
219218
calldata.insert(calldata.end(), enqueued_call_hints.calldata.begin(), enqueued_call_hints.calldata.end());
220219
}
221220
std::vector<Row> trace = AVM_TRACK_TIME_V(
222-
"prove/gen_trace", gen_trace(public_inputs, returndata, execution_hints, /*apply_end_gas_assertions=*/true));
221+
"prove/gen_trace", gen_trace(public_inputs, returndata, execution_hints, /*apply_e2e_assertions=*/true));
223222
if (!avm_dump_trace_path.empty()) {
224223
info("Dumping trace as CSV to: " + avm_dump_trace_path.string());
225224
dump_trace_as_csv(trace, avm_dump_trace_path);
@@ -297,13 +296,13 @@ bool Execution::verify(AvmFlavor::VerificationKey vk, HonkProof const& proof)
297296
* @param public_inputs - to constrain execution inputs & results against
298297
* @param returndata - to add to for each enqueued call
299298
* @param execution_hints - to inform execution
300-
* @param apply_end_gas_assertions - should we apply assertions that public input's end gas is right?
299+
* @param apply_e2e_assertions - should we apply assertions on public inputs (like end gas) and bytecode membership?
301300
* @return The trace as a vector of Row.
302301
*/
303302
std::vector<Row> Execution::gen_trace(AvmPublicInputs const& public_inputs,
304303
std::vector<FF>& returndata,
305304
ExecutionHints const& execution_hints,
306-
bool apply_end_gas_assertions)
305+
bool apply_e2e_assertions)
307306

308307
{
309308
vinfo("------- GENERATING TRACE -------");
@@ -364,7 +363,8 @@ std::vector<Row> Execution::gen_trace(AvmPublicInputs const& public_inputs,
364363
trace_builder.set_public_call_request(public_call_request);
365364
trace_builder.set_call_ptr(call_ctx++);
366365
// Execute!
367-
phase_error = Execution::execute_enqueued_call(trace_builder, public_call_request, returndata);
366+
phase_error =
367+
Execution::execute_enqueued_call(trace_builder, public_call_request, returndata, apply_e2e_assertions);
368368

369369
if (!is_ok(phase_error)) {
370370
info("Phase ", to_name(phase), " reverted.");
@@ -381,7 +381,7 @@ std::vector<Row> Execution::gen_trace(AvmPublicInputs const& public_inputs,
381381
break;
382382
}
383383
}
384-
auto trace = trace_builder.finalize(apply_end_gas_assertions);
384+
auto trace = trace_builder.finalize(apply_e2e_assertions);
385385

386386
show_trace_info(trace);
387387
return trace;
@@ -398,11 +398,14 @@ std::vector<Row> Execution::gen_trace(AvmPublicInputs const& public_inputs,
398398
*/
399399
AvmError Execution::execute_enqueued_call(AvmTraceBuilder& trace_builder,
400400
PublicCallRequest& public_call_request,
401-
std::vector<FF>& returndata)
401+
std::vector<FF>& returndata,
402+
bool check_bytecode_membership)
402403
{
403404
AvmError error = AvmError::NO_ERROR;
404405
// Find the bytecode based on contract address of the public call request
405-
std::vector<uint8_t> bytecode = trace_builder.get_bytecode(public_call_request.contract_address);
406+
// TODO(dbanks12): accept check_membership flag as arg
407+
std::vector<uint8_t> bytecode =
408+
trace_builder.get_bytecode(public_call_request.contract_address, check_bytecode_membership);
406409

407410
// Set this also on nested call
408411

barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.hpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ class Execution {
4040
static std::vector<Row> gen_trace(AvmPublicInputs const& public_inputs,
4141
std::vector<FF>& returndata,
4242
ExecutionHints const& execution_hints,
43-
bool apply_end_gas_assertions = false);
43+
bool apply_e2e_assertions = false);
4444

4545
static AvmError execute_enqueued_call(AvmTraceBuilder& trace_builder,
4646
PublicCallRequest& public_call_request,
47-
std::vector<FF>& returndata);
47+
std::vector<FF>& returndata,
48+
bool check_bytecode_membership);
4849

4950
// For testing purposes only.
5051
static void set_trace_builder_constructor(TraceBuilderConstructor constructor)

barretenberg/cpp/src/barretenberg/vm/avm/trace/execution_hints.hpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ struct ContractInstanceHint {
166166
FF contract_class_id{};
167167
FF initialisation_hash{};
168168
PublicKeysHint public_keys;
169+
NullifierReadTreeHint membership_hint;
169170
};
170171

171172
inline void read(uint8_t const*& it, PublicKeysHint& hint)
@@ -189,6 +190,7 @@ inline void read(uint8_t const*& it, ContractInstanceHint& hint)
189190
read(it, hint.contract_class_id);
190191
read(it, hint.initialisation_hash);
191192
read(it, hint.public_keys);
193+
read(it, hint.membership_hint);
192194
}
193195

194196
struct AvmContractBytecode {
@@ -201,7 +203,7 @@ struct AvmContractBytecode {
201203
ContractInstanceHint contract_instance,
202204
ContractClassIdHint contract_class_id_preimage)
203205
: bytecode(std::move(bytecode))
204-
, contract_instance(contract_instance)
206+
, contract_instance(std::move(contract_instance))
205207
, contract_class_id_preimage(contract_class_id_preimage)
206208
{}
207209
AvmContractBytecode(std::vector<uint8_t> bytecode)

barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp

+108-33
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ bool check_tag_integral(AvmMemoryTag tag)
130130
}
131131
}
132132

133+
bool isCanonical(FF contract_address)
134+
{
135+
// TODO: constrain this!
136+
return contract_address == CANONICAL_AUTH_REGISTRY_ADDRESS || contract_address == DEPLOYER_CONTRACT_ADDRESS ||
137+
contract_address == REGISTERER_CONTRACT_ADDRESS || contract_address == MULTI_CALL_ENTRYPOINT_ADDRESS ||
138+
contract_address == FEE_JUICE_ADDRESS || contract_address == ROUTER_ADDRESS;
139+
}
140+
133141
} // anonymous namespace
134142

135143
/**************************************************************************************************
@@ -147,29 +155,53 @@ void AvmTraceBuilder::rollback_to_non_revertible_checkpoint()
147155

148156
std::vector<uint8_t> AvmTraceBuilder::get_bytecode(const FF contract_address, bool check_membership)
149157
{
150-
// uint32_t clk = 0;
151-
// auto clk = static_cast<uint32_t>(main_trace.size()) + 1;
158+
auto clk = static_cast<uint32_t>(main_trace.size()) + 1;
152159

153160
// Find the bytecode based on contract address of the public call request
154161
const AvmContractBytecode bytecode_hint =
155162
*std::ranges::find_if(execution_hints.all_contract_bytecode, [contract_address](const auto& contract) {
156163
return contract.contract_instance.address == contract_address;
157164
});
158-
if (check_membership) {
159-
// NullifierReadTreeHint nullifier_read_hint = bytecode_hint.contract_instance.membership_hint;
160-
//// hinted nullifier should match the specified contract address
161-
// ASSERT(nullifier_read_hint.low_leaf_preimage.nullifier == contract_address);
162-
// bool is_member = merkle_tree_trace_builder.perform_nullifier_read(clk,
163-
// nullifier_read_hint.low_leaf_preimage,
164-
// nullifier_read_hint.low_leaf_index,
165-
// nullifier_read_hint.low_leaf_sibling_path);
166-
//// TODO(dbanks12): handle non-existent bytecode
167-
//// if the contract address nullifier is hinted as "exists", the membership check should agree
168-
// ASSERT(is_member);
165+
166+
bool exists = true;
167+
if (check_membership && !isCanonical(contract_address)) {
168+
const auto contract_address_nullifier = AvmMerkleTreeTraceBuilder::unconstrained_silo_nullifier(
169+
DEPLOYER_CONTRACT_ADDRESS, /*nullifier=*/contract_address);
170+
// nullifier read hint for the contract address
171+
NullifierReadTreeHint nullifier_read_hint = bytecode_hint.contract_instance.membership_hint;
172+
173+
// If the hinted preimage matches the contract address nullifier, the membership check will prove its existence,
174+
// otherwise the membership check will prove that a low-leaf exists that skips the contract address nullifier.
175+
exists = nullifier_read_hint.low_leaf_preimage.nullifier == contract_address_nullifier;
176+
// perform the membership or non-membership check
177+
bool is_member = merkle_tree_trace_builder.perform_nullifier_read(clk,
178+
nullifier_read_hint.low_leaf_preimage,
179+
nullifier_read_hint.low_leaf_index,
180+
nullifier_read_hint.low_leaf_sibling_path);
181+
// membership check must always pass
182+
ASSERT(is_member);
183+
184+
if (exists) {
185+
// This was a membership proof!
186+
// Assert that the hint's exists flag matches. The flag isn't really necessary...
187+
ASSERT(bytecode_hint.contract_instance.exists);
188+
} else {
189+
// This was a non-membership proof!
190+
// Enforce that the tree access membership checked a low-leaf that skips the contract address nullifier.
191+
// Show that the contract address nullifier meets the non membership conditions (sandwich or max)
192+
ASSERT(contract_address_nullifier < nullifier_read_hint.low_leaf_preimage.nullifier &&
193+
(nullifier_read_hint.low_leaf_preimage.next_nullifier == FF::zero() ||
194+
contract_address_nullifier > nullifier_read_hint.low_leaf_preimage.next_nullifier));
195+
}
169196
}
170197

171-
vinfo("Found bytecode for contract address: ", contract_address);
172-
return bytecode_hint.bytecode;
198+
if (exists) {
199+
vinfo("Found bytecode for contract address: ", contract_address);
200+
return bytecode_hint.bytecode;
201+
}
202+
// TODO(dbanks12): handle non-existent bytecode
203+
vinfo("Bytecode not found for contract address: ", contract_address);
204+
throw std::runtime_error("Bytecode not found");
173205
}
174206

175207
void AvmTraceBuilder::insert_private_state(const std::vector<FF>& siloed_nullifiers,
@@ -3181,23 +3213,66 @@ AvmError AvmTraceBuilder::op_get_contract_instance(
31813213
error = AvmError::CHECK_TAG_ERROR;
31823214
}
31833215

3184-
// Read the contract instance
3185-
ContractInstanceHint instance = execution_hints.contract_instance_hints.at(read_address.val);
3186-
3187-
FF member_value;
3188-
switch (chosen_member) {
3189-
case ContractInstanceMember::DEPLOYER:
3190-
member_value = instance.deployer_addr;
3191-
break;
3192-
case ContractInstanceMember::CLASS_ID:
3193-
member_value = instance.contract_class_id;
3194-
break;
3195-
case ContractInstanceMember::INIT_HASH:
3196-
member_value = instance.initialisation_hash;
3197-
break;
3198-
default:
3199-
member_value = 0;
3200-
break;
3216+
FF member_value = 0;
3217+
bool exists = false;
3218+
3219+
if (is_ok(error)) {
3220+
const auto contract_address = read_address.val;
3221+
const auto contract_address_nullifier = AvmMerkleTreeTraceBuilder::unconstrained_silo_nullifier(
3222+
DEPLOYER_CONTRACT_ADDRESS, /*nullifier=*/contract_address);
3223+
// Read the contract instance hint
3224+
ContractInstanceHint instance = execution_hints.contract_instance_hints.at(contract_address);
3225+
3226+
if (isCanonical(contract_address)) {
3227+
// skip membership check for canonical contracts
3228+
exists = true;
3229+
} else {
3230+
// nullifier read hint for the contract address
3231+
NullifierReadTreeHint nullifier_read_hint = instance.membership_hint;
3232+
3233+
// If the hinted preimage matches the contract address nullifier, the membership check will prove its
3234+
// existence, otherwise the membership check will prove that a low-leaf exists that skips the contract
3235+
// address nullifier.
3236+
exists = nullifier_read_hint.low_leaf_preimage.nullifier == contract_address_nullifier;
3237+
3238+
bool is_member =
3239+
merkle_tree_trace_builder.perform_nullifier_read(clk,
3240+
nullifier_read_hint.low_leaf_preimage,
3241+
nullifier_read_hint.low_leaf_index,
3242+
nullifier_read_hint.low_leaf_sibling_path);
3243+
// membership check must always pass
3244+
ASSERT(is_member);
3245+
3246+
if (exists) {
3247+
// This was a membership proof!
3248+
// Assert that the hint's exists flag matches. The flag isn't really necessary...
3249+
ASSERT(instance.exists);
3250+
} else {
3251+
// This was a non-membership proof!
3252+
// Enforce that the tree access membership checked a low-leaf that skips the contract address nullifier.
3253+
// Show that the contract address nullifier meets the non membership conditions (sandwich or max)
3254+
ASSERT(contract_address_nullifier < nullifier_read_hint.low_leaf_preimage.nullifier &&
3255+
(nullifier_read_hint.low_leaf_preimage.next_nullifier == FF::zero() ||
3256+
contract_address_nullifier > nullifier_read_hint.low_leaf_preimage.next_nullifier));
3257+
}
3258+
}
3259+
3260+
if (exists) {
3261+
switch (chosen_member) {
3262+
case ContractInstanceMember::DEPLOYER:
3263+
member_value = instance.deployer_addr;
3264+
break;
3265+
case ContractInstanceMember::CLASS_ID:
3266+
member_value = instance.contract_class_id;
3267+
break;
3268+
case ContractInstanceMember::INIT_HASH:
3269+
member_value = instance.initialisation_hash;
3270+
break;
3271+
default:
3272+
member_value = 0;
3273+
break;
3274+
}
3275+
}
32013276
}
32023277

32033278
// TODO(8603): once instructions can have multiple different tags for writes, write dst as FF and exists as
@@ -3241,7 +3316,7 @@ AvmError AvmTraceBuilder::op_get_contract_instance(
32413316
// TODO(8603): once instructions can have multiple different tags for writes, remove this and do a
32423317
// constrained writes
32433318
write_to_memory(resolved_dst_offset, member_value, AvmMemoryTag::FF);
3244-
write_to_memory(resolved_exists_offset, FF(static_cast<uint32_t>(instance.exists)), AvmMemoryTag::U1);
3319+
write_to_memory(resolved_exists_offset, FF(static_cast<uint32_t>(exists)), AvmMemoryTag::U1);
32453320

32463321
// TODO(dbanks12): compute contract address nullifier from instance preimage and perform membership check
32473322

barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
#define MAX_UNENCRYPTED_LOGS_PER_TX 8
2121
#define MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS 3000
2222
#define MAX_L2_GAS_PER_ENQUEUED_CALL 12000000
23+
#define CANONICAL_AUTH_REGISTRY_ADDRESS 1
24+
#define DEPLOYER_CONTRACT_ADDRESS 2
25+
#define REGISTERER_CONTRACT_ADDRESS 3
26+
#define MULTI_CALL_ENTRYPOINT_ADDRESS 4
27+
#define FEE_JUICE_ADDRESS 5
28+
#define ROUTER_ADDRESS 6
2329
#define AZTEC_ADDRESS_LENGTH 1
2430
#define GAS_FEES_LENGTH 2
2531
#define GAS_LENGTH 2

noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr

+1-1
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ contract AvmTest {
639639
dep::aztec::oracle::debug_log::debug_log("pedersen_hash_with_index");
640640
let _ = pedersen_hash_with_index(args_field);
641641
dep::aztec::oracle::debug_log::debug_log("test_get_contract_instance");
642-
test_get_contract_instance(AztecAddress::from_field(args_field[0]));
642+
test_get_contract_instance(AztecAddress::from_field(0x4444));
643643
dep::aztec::oracle::debug_log::debug_log("get_address");
644644
let _ = get_address();
645645
dep::aztec::oracle::debug_log::debug_log("get_sender");

yarn-project/circuits.js/src/scripts/constants.in.ts

+6
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ const CPP_CONSTANTS = [
8484
'MEM_TAG_FF',
8585
'MAX_L2_GAS_PER_ENQUEUED_CALL',
8686
'MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS',
87+
'CANONICAL_AUTH_REGISTRY_ADDRESS',
88+
'DEPLOYER_CONTRACT_ADDRESS',
89+
'REGISTERER_CONTRACT_ADDRESS',
90+
'MULTI_CALL_ENTRYPOINT_ADDRESS',
91+
'FEE_JUICE_ADDRESS',
92+
'ROUTER_ADDRESS',
8793
];
8894

8995
const CPP_GENERATORS: string[] = [

0 commit comments

Comments
 (0)