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 6, 2024
1 parent e7ebef8 commit aa5acbb
Show file tree
Hide file tree
Showing 23 changed files with 473 additions and 147 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
116 changes: 84 additions & 32 deletions barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,29 +147,48 @@ 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;
});

const bool exists = bytecode_hint.contract_instance.exists;
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);
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;
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 is a membership proof!
// Enforce that the tree access membership checked the contract address nullifier.
ASSERT(nullifier_read_hint.low_leaf_preimage.nullifier == contract_address_nullifier);
} else {
// This is 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 +3216,56 @@ 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);
exists = instance.exists;

// nullifier read hint for the contract address
NullifierReadTreeHint nullifier_read_hint = instance.membership_hint;

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 is a membership proof!
// Enforce that the tree access membership checked the contract address nullifier.
ASSERT(nullifier_read_hint.low_leaf_preimage.nullifier == contract_address_nullifier);

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 is 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 +3309,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
3 changes: 3 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,9 @@
#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 DEPLOYER_CONTRACT_ADDRESS 2
#define REGISTERER_CONTRACT_ADDRESS 3
#define FEE_JUICE_ADDRESS 5
#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
3 changes: 3 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,9 @@ const CPP_CONSTANTS = [
'MEM_TAG_FF',
'MAX_L2_GAS_PER_ENQUEUED_CALL',
'MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS',
'DEPLOYER_CONTRACT_ADDRESS',
'REGISTERER_CONTRACT_ADDRESS',
'FEE_JUICE_ADDRESS',
];

const CPP_GENERATORS: string[] = [
Expand Down
12 changes: 10 additions & 2 deletions yarn-project/circuits.js/src/structs/avm/avm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ export class AvmContractInstanceHint {
public readonly contractClassId: Fr,
public readonly initializationHash: Fr,
public readonly publicKeys: PublicKeys,
public readonly membershipHint: AvmNullifierReadTreeHint = AvmNullifierReadTreeHint.empty(),
) {}
/**
* Serializes the inputs to a buffer.
Expand Down Expand Up @@ -288,7 +289,8 @@ export class AvmContractInstanceHint {
this.deployer.isZero() &&
this.contractClassId.isZero() &&
this.initializationHash.isZero() &&
this.publicKeys.isEmpty()
this.publicKeys.isEmpty() &&
this.membershipHint.isEmpty()
);
}

Expand All @@ -315,6 +317,7 @@ export class AvmContractInstanceHint {
fields.contractClassId,
fields.initializationHash,
fields.publicKeys,
fields.membershipHint,
] as const;
}

Expand All @@ -333,6 +336,7 @@ export class AvmContractInstanceHint {
Fr.fromBuffer(reader),
Fr.fromBuffer(reader),
PublicKeys.fromBuffer(reader),
AvmNullifierReadTreeHint.fromBuffer(reader),
);
}

Expand Down Expand Up @@ -592,7 +596,7 @@ export class AvmNullifierReadTreeHint {
constructor(
public readonly lowLeafPreimage: NullifierLeafPreimage,
public readonly lowLeafIndex: Fr,
public readonly _lowLeafSiblingPath: Fr[],
public _lowLeafSiblingPath: Fr[],
) {
this.lowLeafSiblingPath = new Vector(_lowLeafSiblingPath);
}
Expand Down Expand Up @@ -630,6 +634,10 @@ export class AvmNullifierReadTreeHint {
return new AvmNullifierReadTreeHint(fields.lowLeafPreimage, fields.lowLeafIndex, fields.lowLeafSiblingPath.items);
}

static empty(): AvmNullifierReadTreeHint {
return new AvmNullifierReadTreeHint(NullifierLeafPreimage.empty(), Fr.ZERO, []);
}

/**
* Extracts fields from an instance.
* @param fields - Fields to create the instance from.
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/circuits.js/src/tests/factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,7 @@ export function makeAvmBytecodeHints(seed = 0): AvmContractBytecodeHints {
instance.contractClassId,
instance.initializationHash,
instance.publicKeys,
makeAvmNullifierReadTreeHints(seed + 0x2000),
);

const publicBytecodeCommitment = computePublicBytecodeCommitment(packedBytecode);
Expand Down Expand Up @@ -1366,6 +1367,7 @@ export function makeAvmContractInstanceHint(seed = 0): AvmContractInstanceHint {
new Point(new Fr(seed + 0x10), new Fr(seed + 0x11), false),
new Point(new Fr(seed + 0x12), new Fr(seed + 0x13), false),
),
makeAvmNullifierReadTreeHints(seed + 0x1000),
);
}

Expand Down
Loading

0 comments on commit aa5acbb

Please sign in to comment.