Skip to content

Commit

Permalink
feat: GETCONTRACTINSTANCE and bytecode retrieval perform nullifier me…
Browse files Browse the repository at this point in the history
…mbership checks
  • Loading branch information
dbanks12 committed Dec 7, 2024
1 parent e7ebef8 commit 19fd177
Show file tree
Hide file tree
Showing 24 changed files with 538 additions and 148 deletions.
5 changes: 2 additions & 3 deletions barretenberg/cpp/src/barretenberg/bb/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,6 @@ void vk_as_fields(const std::string& vk_path, const std::string& output_path)
* Communication:
* - Filesystem: The proof and vk are written to the paths output_path/proof and output_path/{vk, vk_fields.json}
*
* @param bytecode_path Path to the file containing the serialised bytecode
* @param public_inputs_path Path to the file containing the serialised avm public inputs
* @param hints_path Path to the file containing the serialised avm circuit hints
* @param output_path Path (directory) to write the output proof and verification keys
Expand All @@ -597,8 +596,8 @@ void avm_prove(const std::filesystem::path& public_inputs_path,
const std::filesystem::path& output_path)
{

auto const avm_public_inputs = AvmPublicInputs::from(read_file(public_inputs_path));
auto const avm_hints = bb::avm_trace::ExecutionHints::from(read_file(hints_path));
const auto avm_public_inputs = AvmPublicInputs::from(read_file(public_inputs_path));
const auto avm_hints = bb::avm_trace::ExecutionHints::from(read_file(hints_path));

// Using [0] is fine now for the top-level call, but we might need to index by address in future
vinfo("bytecode size: ", avm_hints.all_contract_bytecode[0].bytecode.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ class AvmExecutionTests : public ::testing::Test {
PublicKeysHint public_keys{ nullifier_key, incoming_viewing_key, outgoing_viewing_key, tagging_key };
ContractInstanceHint contract_instance = {
FF::one() /* temp address */, true /* exists */, FF(2) /* salt */, FF(3) /* deployer_addr */, class_id,
FF(8) /* initialisation_hash */, public_keys
FF(8) /* initialisation_hash */, public_keys,
/*membership_hint=*/ { .low_leaf_preimage = { .nullifier = 0, .next_nullifier = 0, .next_index = 0, }, .low_leaf_index = 0, .low_leaf_sibling_path = {} },
};
FF address = AvmBytecodeTraceBuilder::compute_address_from_instance(contract_instance);
contract_instance.address = address;
Expand Down Expand Up @@ -2368,6 +2369,8 @@ TEST_F(AvmExecutionTests, opCallOpcodes)

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

Expand All @@ -2389,6 +2392,7 @@ TEST_F(AvmExecutionTests, opGetContractInstanceOpcode)
.contract_class_id = 66,
.initialisation_hash = 99,
.public_keys = public_keys_hints,
.membership_hint = { .low_leaf_preimage = { .nullifier = 0, .next_nullifier = 0, .next_index = 0, }, .low_leaf_index = 0, .low_leaf_sibling_path = {} },
};
auto execution_hints = ExecutionHints().with_contract_instance_hints({ { address, instance } });

Expand Down
19 changes: 11 additions & 8 deletions barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ std::vector<FF> Execution::getDefaultPublicInputs()
* @brief Run the bytecode, generate the corresponding execution trace and prove the correctness
* of the execution of the supplied bytecode.
*
* @param bytecode A vector of bytes representing the bytecode to execute.
* @throws runtime_error exception when the bytecode is invalid.
* @return The verifier key and zk proof of the execution.
*/
Expand All @@ -219,7 +218,7 @@ std::tuple<AvmFlavor::VerificationKey, HonkProof> Execution::prove(AvmPublicInpu
calldata.insert(calldata.end(), enqueued_call_hints.calldata.begin(), enqueued_call_hints.calldata.end());
}
std::vector<Row> trace = AVM_TRACK_TIME_V(
"prove/gen_trace", gen_trace(public_inputs, returndata, execution_hints, /*apply_end_gas_assertions=*/true));
"prove/gen_trace", gen_trace(public_inputs, returndata, execution_hints, /*apply_e2e_assertions=*/true));
if (!avm_dump_trace_path.empty()) {
info("Dumping trace as CSV to: " + avm_dump_trace_path.string());
dump_trace_as_csv(trace, avm_dump_trace_path);
Expand Down Expand Up @@ -297,13 +296,13 @@ bool Execution::verify(AvmFlavor::VerificationKey vk, HonkProof const& proof)
* @param public_inputs - to constrain execution inputs & results against
* @param returndata - to add to for each enqueued call
* @param execution_hints - to inform execution
* @param apply_end_gas_assertions - should we apply assertions that public input's end gas is right?
* @param apply_e2e_assertions - should we apply assertions on public inputs (like end gas) and bytecode membership?
* @return The trace as a vector of Row.
*/
std::vector<Row> Execution::gen_trace(AvmPublicInputs const& public_inputs,
std::vector<FF>& returndata,
ExecutionHints const& execution_hints,
bool apply_end_gas_assertions)
bool apply_e2e_assertions)

{
vinfo("------- GENERATING TRACE -------");
Expand Down Expand Up @@ -364,7 +363,8 @@ std::vector<Row> Execution::gen_trace(AvmPublicInputs const& public_inputs,
trace_builder.set_public_call_request(public_call_request);
trace_builder.set_call_ptr(call_ctx++);
// Execute!
phase_error = Execution::execute_enqueued_call(trace_builder, public_call_request, returndata);
phase_error =
Execution::execute_enqueued_call(trace_builder, public_call_request, returndata, apply_e2e_assertions);

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

show_trace_info(trace);
return trace;
Expand All @@ -398,11 +398,14 @@ std::vector<Row> Execution::gen_trace(AvmPublicInputs const& public_inputs,
*/
AvmError Execution::execute_enqueued_call(AvmTraceBuilder& trace_builder,
PublicCallRequest& public_call_request,
std::vector<FF>& returndata)
std::vector<FF>& returndata,
bool check_bytecode_membership)
{
AvmError error = AvmError::NO_ERROR;
// Find the bytecode based on contract address of the public call request
std::vector<uint8_t> bytecode = trace_builder.get_bytecode(public_call_request.contract_address);
// TODO(dbanks12): accept check_membership flag as arg
std::vector<uint8_t> bytecode =
trace_builder.get_bytecode(public_call_request.contract_address, check_bytecode_membership);

// Set this also on nested call

Expand Down
5 changes: 3 additions & 2 deletions barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ class Execution {
static std::vector<Row> gen_trace(AvmPublicInputs const& public_inputs,
std::vector<FF>& returndata,
ExecutionHints const& execution_hints,
bool apply_end_gas_assertions = false);
bool apply_e2e_assertions = false);

static AvmError execute_enqueued_call(AvmTraceBuilder& trace_builder,
PublicCallRequest& public_call_request,
std::vector<FF>& returndata);
std::vector<FF>& returndata,
bool check_bytecode_membership);

// For testing purposes only.
static void set_trace_builder_constructor(TraceBuilderConstructor constructor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ struct ContractInstanceHint {
FF contract_class_id{};
FF initialisation_hash{};
PublicKeysHint public_keys;
NullifierReadTreeHint membership_hint;
};

inline void read(uint8_t const*& it, PublicKeysHint& hint)
Expand All @@ -189,6 +190,7 @@ inline void read(uint8_t const*& it, ContractInstanceHint& hint)
read(it, hint.contract_class_id);
read(it, hint.initialisation_hash);
read(it, hint.public_keys);
read(it, hint.membership_hint);
}

struct AvmContractBytecode {
Expand All @@ -201,7 +203,7 @@ struct AvmContractBytecode {
ContractInstanceHint contract_instance,
ContractClassIdHint contract_class_id_preimage)
: bytecode(std::move(bytecode))
, contract_instance(contract_instance)
, contract_instance(std::move(contract_instance))
, contract_class_id_preimage(contract_class_id_preimage)
{}
AvmContractBytecode(std::vector<uint8_t> bytecode)
Expand Down
140 changes: 107 additions & 33 deletions barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ bool check_tag_integral(AvmMemoryTag tag)
}
}

bool isCanonical(FF contract_address)
{
return contract_address == CANONICAL_AUTH_REGISTRY_ADDRESS || contract_address == DEPLOYER_CONTRACT_ADDRESS ||
contract_address == REGISTERER_CONTRACT_ADDRESS || contract_address == MULTI_CALL_ENTRYPOINT_ADDRESS ||
contract_address == FEE_JUICE_ADDRESS || contract_address == ROUTER_ADDRESS;
}

} // anonymous namespace

/**************************************************************************************************
Expand All @@ -147,29 +154,53 @@ void AvmTraceBuilder::rollback_to_non_revertible_checkpoint()

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

// Find the bytecode based on contract address of the public call request
const AvmContractBytecode bytecode_hint =
*std::ranges::find_if(execution_hints.all_contract_bytecode, [contract_address](const auto& contract) {
return contract.contract_instance.address == contract_address;
});
if (check_membership) {
// NullifierReadTreeHint nullifier_read_hint = bytecode_hint.contract_instance.membership_hint;
//// hinted nullifier should match the specified contract address
// ASSERT(nullifier_read_hint.low_leaf_preimage.nullifier == contract_address);
// bool is_member = merkle_tree_trace_builder.perform_nullifier_read(clk,
// nullifier_read_hint.low_leaf_preimage,
// nullifier_read_hint.low_leaf_index,
// nullifier_read_hint.low_leaf_sibling_path);
//// TODO(dbanks12): handle non-existent bytecode
//// if the contract address nullifier is hinted as "exists", the membership check should agree
// ASSERT(is_member);

bool exists = true;
if (check_membership && !isCanonical(contract_address)) {
const auto contract_address_nullifier = AvmMerkleTreeTraceBuilder::unconstrained_silo_nullifier(
DEPLOYER_CONTRACT_ADDRESS, /*nullifier=*/contract_address);
// nullifier read hint for the contract address
NullifierReadTreeHint nullifier_read_hint = bytecode_hint.contract_instance.membership_hint;

// If the hinted preimage matches the contract address nullifier, the membership check will prove its existence,
// otherwise the membership check will prove that a low-leaf exists that skips the contract address nullifier.
exists = nullifier_read_hint.low_leaf_preimage.nullifier == contract_address_nullifier;
// perform the membership or non-membership check
bool is_member = merkle_tree_trace_builder.perform_nullifier_read(clk,
nullifier_read_hint.low_leaf_preimage,
nullifier_read_hint.low_leaf_index,
nullifier_read_hint.low_leaf_sibling_path);
// membership check must always pass
ASSERT(is_member);

if (exists) {
// This was a membership proof!
// Assert that the hint's exists flag matches. The flag isn't really necessary...
ASSERT(bytecode_hint.contract_instance.exists);
} else {
// This was a non-membership proof!
// Enforce that the tree access membership checked a low-leaf that skips the contract address nullifier.
// Show that the contract address nullifier meets the non membership conditions (sandwich or max)
ASSERT(contract_address_nullifier < nullifier_read_hint.low_leaf_preimage.nullifier &&
(nullifier_read_hint.low_leaf_preimage.next_nullifier == FF::zero() ||
contract_address_nullifier > nullifier_read_hint.low_leaf_preimage.next_nullifier));
}
}

vinfo("Found bytecode for contract address: ", contract_address);
return bytecode_hint.bytecode;
if (exists) {
vinfo("Found bytecode for contract address: ", contract_address);
return bytecode_hint.bytecode;
}
// TODO(dbanks12): handle non-existent bytecode
vinfo("Bytecode not found for contract address: ", contract_address);
throw std::runtime_error("Bytecode not found");
}

void AvmTraceBuilder::insert_private_state(const std::vector<FF>& siloed_nullifiers,
Expand Down Expand Up @@ -3197,23 +3228,66 @@ AvmError AvmTraceBuilder::op_get_contract_instance(
error = AvmError::CHECK_TAG_ERROR;
}

// Read the contract instance
ContractInstanceHint instance = execution_hints.contract_instance_hints.at(read_address.val);

FF member_value;
switch (chosen_member) {
case ContractInstanceMember::DEPLOYER:
member_value = instance.deployer_addr;
break;
case ContractInstanceMember::CLASS_ID:
member_value = instance.contract_class_id;
break;
case ContractInstanceMember::INIT_HASH:
member_value = instance.initialisation_hash;
break;
default:
member_value = 0;
break;
FF member_value = 0;
bool exists = false;

if (is_ok(error)) {
const auto contract_address = read_address.val;
const auto contract_address_nullifier = AvmMerkleTreeTraceBuilder::unconstrained_silo_nullifier(
DEPLOYER_CONTRACT_ADDRESS, /*nullifier=*/contract_address);
// Read the contract instance hint
ContractInstanceHint instance = execution_hints.contract_instance_hints.at(contract_address);

if (isCanonical(contract_address)) {
// skip membership check for canonical contracts
exists = true;
} else {
// nullifier read hint for the contract address
NullifierReadTreeHint nullifier_read_hint = instance.membership_hint;

// If the hinted preimage matches the contract address nullifier, the membership check will prove its
// existence, otherwise the membership check will prove that a low-leaf exists that skips the contract
// address nullifier.
exists = nullifier_read_hint.low_leaf_preimage.nullifier == contract_address_nullifier;

bool is_member =
merkle_tree_trace_builder.perform_nullifier_read(clk,
nullifier_read_hint.low_leaf_preimage,
nullifier_read_hint.low_leaf_index,
nullifier_read_hint.low_leaf_sibling_path);
// membership check must always pass
ASSERT(is_member);
}

if (exists) {
// This was a membership proof!
// Assert that the hint's exists flag matches. The flag isn't really necessary...
ASSERT(instance.exists);

switch (chosen_member) {
case ContractInstanceMember::DEPLOYER:
member_value = instance.deployer_addr;
break;
case ContractInstanceMember::CLASS_ID:
member_value = instance.contract_class_id;
break;
case ContractInstanceMember::INIT_HASH:
member_value = instance.initialisation_hash;
break;
default:
member_value = 0;
break;
}
} else {
// This was a non-membership proof!
// Enforce that the tree access membership checked a low-leaf that skips the contract address nullifier.
// Show that the contract address nullifier meets the non membership conditions (sandwich or max)
ASSERT(contract_address_nullifier < nullifier_read_hint.low_leaf_preimage.nullifier &&
(nullifier_read_hint.low_leaf_preimage.next_nullifier == FF::zero() ||
contract_address_nullifier > nullifier_read_hint.low_leaf_preimage.next_nullifier));
// Instance DNE. Return 0 to user code.
member_value = 0;
}
}

// TODO(8603): once instructions can have multiple different tags for writes, write dst as FF and exists as
Expand Down Expand Up @@ -3257,7 +3331,7 @@ AvmError AvmTraceBuilder::op_get_contract_instance(
// TODO(8603): once instructions can have multiple different tags for writes, remove this and do a
// constrained writes
write_to_memory(resolved_dst_offset, member_value, AvmMemoryTag::FF);
write_to_memory(resolved_exists_offset, FF(static_cast<uint32_t>(instance.exists)), AvmMemoryTag::U1);
write_to_memory(resolved_exists_offset, FF(static_cast<uint32_t>(exists)), AvmMemoryTag::U1);

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

Expand Down
6 changes: 6 additions & 0 deletions barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
#define MAX_UNENCRYPTED_LOGS_PER_TX 8
#define MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS 3000
#define MAX_L2_GAS_PER_ENQUEUED_CALL 12000000
#define CANONICAL_AUTH_REGISTRY_ADDRESS 1
#define DEPLOYER_CONTRACT_ADDRESS 2
#define REGISTERER_CONTRACT_ADDRESS 3
#define MULTI_CALL_ENTRYPOINT_ADDRESS 4
#define FEE_JUICE_ADDRESS 5
#define ROUTER_ADDRESS 6
#define AZTEC_ADDRESS_LENGTH 1
#define GAS_FEES_LENGTH 2
#define GAS_LENGTH 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ contract AvmTest {
dep::aztec::oracle::debug_log::debug_log("pedersen_hash_with_index");
let _ = pedersen_hash_with_index(args_field);
dep::aztec::oracle::debug_log::debug_log("test_get_contract_instance");
test_get_contract_instance(AztecAddress::from_field(args_field[0]));
test_get_contract_instance(AztecAddress::from_field(0x4444));
dep::aztec::oracle::debug_log::debug_log("get_address");
let _ = get_address();
dep::aztec::oracle::debug_log::debug_log("get_sender");
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/circuits.js/src/scripts/constants.in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ const CPP_CONSTANTS = [
'MEM_TAG_FF',
'MAX_L2_GAS_PER_ENQUEUED_CALL',
'MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS',
'CANONICAL_AUTH_REGISTRY_ADDRESS',
'DEPLOYER_CONTRACT_ADDRESS',
'REGISTERER_CONTRACT_ADDRESS',
'MULTI_CALL_ENTRYPOINT_ADDRESS',
'FEE_JUICE_ADDRESS',
'ROUTER_ADDRESS',
];

const CPP_GENERATORS: string[] = [
Expand Down
Loading

0 comments on commit 19fd177

Please sign in to comment.