Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions barretenberg/cpp/pil/vm2/constants_gen.pil
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace constants;
pol NOTE_HASH_TREE_HEIGHT = 40;
pol PUBLIC_DATA_TREE_HEIGHT = 40;
pol NULLIFIER_TREE_HEIGHT = 40;
pol NOTE_HASH_TREE_LEAF_COUNT = 1099511627776;
pol MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX = 63;
pol MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 3000;
pol CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS = 2;
Expand Down Expand Up @@ -54,6 +55,7 @@ namespace constants;
pol AVM_EXEC_OP_ID_DEBUGLOG = 8192;
pol AVM_EXEC_OP_ID_SLOAD = 16384;
pol AVM_EXEC_OP_ID_SSTORE = 32768;
pol AVM_EXEC_OP_ID_NOTEHASH_EXISTS = 65536;
pol AVM_EXEC_OP_ID_ALU_ADD = 1;
pol AVM_EXEC_OP_ID_ALU_SUB = 2;
pol AVM_EXEC_OP_ID_ALU_MUL = 4;
Expand Down
6 changes: 5 additions & 1 deletion barretenberg/cpp/pil/vm2/execution.pil
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ include "opcodes/internal_call.pil";
include "opcodes/external_call.pil";
include "opcodes/sload.pil";
include "opcodes/sstore.pil";
include "opcodes/notehash_exists.pil";

namespace execution;

Expand Down Expand Up @@ -377,6 +378,7 @@ pol commit sel_execute_returndata_size;
pol commit sel_execute_debug_log; // unused but we are forced to have it
pol commit sel_execute_sload;
pol commit sel_execute_sstore;
pol commit sel_execute_notehash_exists;
sel_execute_get_env_var * (1 - sel_execute_get_env_var) = 0;
sel_execute_set * (1 - sel_execute_set) = 0;
sel_execute_mov * (1 - sel_execute_mov) = 0;
Expand All @@ -393,6 +395,7 @@ sel_execute_returndata_size * (1 - sel_execute_returndata_size) = 0;
sel_execute_debug_log * (1 - sel_execute_debug_log) = 0;
sel_execute_sload * (1 - sel_execute_sload) = 0;
sel_execute_sstore * (1 - sel_execute_sstore) = 0;
sel_execute_notehash_exists * (1 - sel_execute_notehash_exists) = 0;

#[EXEC_OP_ID_DECOMPOSITION]
sel_execute_get_env_var * constants.AVM_EXEC_OP_ID_GETENVVAR +
Expand All @@ -410,7 +413,8 @@ sel_execute_success_copy * constants.AVM_EXEC_OP_ID_SUCCESSCOPY +
sel_execute_returndata_size * constants.AVM_EXEC_OP_ID_RETURNDATASIZE +
sel_execute_debug_log * constants.AVM_EXEC_OP_ID_DEBUGLOG +
sel_execute_sload * constants.AVM_EXEC_OP_ID_SLOAD +
sel_execute_sstore * constants.AVM_EXEC_OP_ID_SSTORE
sel_execute_sstore * constants.AVM_EXEC_OP_ID_SSTORE +
sel_execute_notehash_exists * constants.AVM_EXEC_OP_ID_NOTEHASH_EXISTS
// We force the selectors to be 0 if we are not executing an opcode
// or when we are not in the execution subtrace.
= sel_should_execute_opcode * sel_execute_execution * subtrace_operation_id;
Expand Down
64 changes: 64 additions & 0 deletions barretenberg/cpp/pil/vm2/opcodes/notehash_exists.pil
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
include "../constants_gen.pil";
include "../range_check.pil";
include "../trees/note_hash_tree_check.pil";

// NOTEHASH_EXISTS opcode: Checks if a note hash exists in the note hash tree at a given leaf index
//
// Register usage:
// - register[0]: Contains the unique_note_hash to check for existence
// - register[1]: Contains the leaf_index where the unique_note_hash is going to be checked
// - register[2]: Boolean output of the opcode indicating whether the note hash exists at the given leaf index
//
// The opcode leverages the note_hash_tree_check gadget to verify the existence check operation
// against the current note hash tree root. The opcode will write false if the
// leaf index is greater than or equal to the total number of leaves in the note hash tree.
//
namespace execution; // this is a virtual gadget that shares rows with the execution trace

#[skippable_if]
sel_execute_notehash_exists = 0; // from execution.pil.

pol commit note_hash_leaf_in_range;
note_hash_leaf_in_range * (1 - note_hash_leaf_in_range) = 0;

// Compare note_hash_leaf_in_range = register[1] (leaf_index) < NOTE_HASH_TREE_LEAF_COUNT
pol LEAF_INDEX_GTE_NOTE_HASH_LEAF_COUNT = register[1] - constants.NOTE_HASH_TREE_LEAF_COUNT;
pol LEAF_INDEX_LT_NOTE_HASH_LEAF_COUNT = constants.NOTE_HASH_TREE_LEAF_COUNT - register[1] - 1;
pol commit note_hash_leaf_index_leaf_count_cmp_diff;
sel_execute_notehash_exists * ((LEAF_INDEX_LT_NOTE_HASH_LEAF_COUNT - LEAF_INDEX_GTE_NOTE_HASH_LEAF_COUNT) * note_hash_leaf_in_range + LEAF_INDEX_GTE_NOTE_HASH_LEAF_COUNT - note_hash_leaf_index_leaf_count_cmp_diff) = 0;

#[NOTE_HASH_INDEX_RANGE]
sel_execute_notehash_exists {
note_hash_leaf_index_leaf_count_cmp_diff,
constant_64 // From gas.pil
} in range_check.sel {
range_check.value,
range_check.rng_chk_bits
};

// If the leaf index is out of range, set the output to false
#[NOTE_HASH_EXISTS_OUT_OF_RANGE_FALSE]
sel_execute_notehash_exists * (1 - note_hash_leaf_in_range) * register[2] = 0;

// If the leaf index is in range, check if the note hash exists in the note hash tree
#[NOTE_HASH_READ]
note_hash_leaf_in_range {
precomputed.zero,
register[0], // unique_note_hash input
register[1], // leaf_index input
prev_note_hash_tree_root,
register[2] // exists output
} in note_hash_tree_check.sel {
note_hash_tree_check.write,
note_hash_tree_check.note_hash,
note_hash_tree_check.leaf_index,
note_hash_tree_check.prev_root,
note_hash_tree_check.exists
};

#[NOTEHASH_EXISTS_U1_OUTPUT_TAG]
sel_execute_notehash_exists * (constants.MEM_TAG_U1 - mem_tag_reg[2]) = 0;

#[NOTE_HASH_EXISTS_SUCCESS]
sel_execute_notehash_exists * sel_opcode_error = 0;

11 changes: 8 additions & 3 deletions barretenberg/cpp/pil/vm2/trees/note_hash_tree_check.pil
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ include "../public_inputs.pil";
* For reading, we enforce that the note hash is already unique.
*
* Read usage (lookup):
* sel { precomputed.zero, unique_note_hash, leaf_index, note_hash_tree_root }
* sel { precomputed.zero, unique_note_hash, leaf_index, note_hash_tree_root, exists }
* in note_hash_tree_check.sel { note_hash_tree_check.write, note_hash_tree_check.note_hash,
* note_hash_tree_check.leaf_index, note_hash_tree_check.prev_root };
* note_hash_tree_check.leaf_index, note_hash_tree_check.prev_root, note_hash_tree_check.exists };
*
* Write usage (permutation):
* sel { note_hash, note_hash_next_available_leaf_index, prev_note_hash_tree_root,
Expand All @@ -38,6 +38,7 @@ namespace note_hash_tree_check;
pol commit write;
write * (1 - write) = 0;
pol READ = 1 - write;
pol commit exists;

pol commit note_hash;
pol commit leaf_index;
Expand Down Expand Up @@ -129,7 +130,11 @@ namespace note_hash_tree_check;

// ====== TREE CHECK ======
pol commit prev_leaf_value;
prev_leaf_value = (1 - write) * unique_note_hash;

// exists = prev_leaf_value == unique_note_hash
pol PREV_LEAF_VALUE_UNIQUE_NOTE_HASH_DIFF = prev_leaf_value - unique_note_hash;
pol commit prev_leaf_value_unique_note_hash_diff_inv;
sel * (PREV_LEAF_VALUE_UNIQUE_NOTE_HASH_DIFF * (exists * (1 - prev_leaf_value_unique_note_hash_diff_inv) + prev_leaf_value_unique_note_hash_diff_inv) - 1 + exists) = 0;

pol commit next_leaf_value;
// If reading, next_leaf_value is unconstrained
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#define NOTE_HASH_TREE_HEIGHT 40
#define PUBLIC_DATA_TREE_HEIGHT 40
#define NULLIFIER_TREE_HEIGHT 40
#define NOTE_HASH_TREE_LEAF_COUNT 0x10000000000
#define MAX_NOTE_HASHES_PER_TX 64
#define MAX_NULLIFIERS_PER_TX 64
#define MAX_ENQUEUED_CALLS_PER_TX 32
Expand Down Expand Up @@ -72,6 +73,7 @@
#define AVM_EXEC_OP_ID_DEBUGLOG 8192
#define AVM_EXEC_OP_ID_SLOAD 16384
#define AVM_EXEC_OP_ID_SSTORE 32768
#define AVM_EXEC_OP_ID_NOTEHASH_EXISTS 65536
#define AVM_EXEC_OP_ID_ALU_ADD 1
#define AVM_EXEC_OP_ID_ALU_SUB 2
#define AVM_EXEC_OP_ID_ALU_MUL 4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,12 @@ const std::unordered_map<ExecutionOpCode, ExecInstructionSpec> EXEC_INSTRUCTION_
.dyn_da = AVM_SSTORE_DYN_DA_GAS },
.dyn_gas_id = AVM_DYN_GAS_ID_SSTORE,
.register_info = RegisterInfo().add_inputs({ /*src*/ ValueTag::FF, /*slot*/ ValueTag::FF }) } },
{ ExecutionOpCode::NOTEHASHEXISTS,
{ .num_addresses = 3,
.gas_cost = { .opcode_gas = AVM_NOTEHASHEXISTS_BASE_L2_GAS, .base_da = 0, .dyn_l2 = 0, .dyn_da = 0 },
.register_info = RegisterInfo()
.add_inputs({ /*unique_note_hash*/ ValueTag::FF, /*leaf_index*/ ValueTag::U64 })
.add_output(/*exists*/) } },
{ ExecutionOpCode::GETCONTRACTINSTANCE,
{ .num_addresses = 2,
.gas_cost = { .opcode_gas = AVM_GETCONTRACTINSTANCE_BASE_L2_GAS, .base_da = 0, .dyn_l2 = 0, .dyn_da = 0 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@ using FF = AvmFlavorSettings::FF;
using C = Column;
using execution = bb::avm2::execution<FF>;

constexpr std::array<WireOpCode, 23> WIRE_OPCODES = {
constexpr std::array<WireOpCode, 24> WIRE_OPCODES = {
WireOpCode::GETENVVAR_16, WireOpCode::SET_8, WireOpCode::SET_16, WireOpCode::SET_32,
WireOpCode::SET_64, WireOpCode::SET_128, WireOpCode::SET_FF, WireOpCode::MOV_8,
WireOpCode::MOV_16, WireOpCode::JUMP_32, WireOpCode::JUMPI_32, WireOpCode::CALL,
WireOpCode::INTERNALCALL, WireOpCode::INTERNALRETURN, WireOpCode::RETURN, WireOpCode::SUCCESSCOPY,
WireOpCode::STATICCALL, WireOpCode::REVERT_8, WireOpCode::REVERT_16, WireOpCode::RETURNDATASIZE,
WireOpCode::DEBUGLOG, WireOpCode::SLOAD, WireOpCode::SSTORE,
WireOpCode::DEBUGLOG, WireOpCode::SLOAD, WireOpCode::SSTORE, WireOpCode::NOTEHASHEXISTS,
};

constexpr std::array<uint32_t, 23> OPERATION_IDS = {
constexpr std::array<uint32_t, 24> OPERATION_IDS = {
AVM_EXEC_OP_ID_GETENVVAR, AVM_EXEC_OP_ID_SET,
AVM_EXEC_OP_ID_SET, AVM_EXEC_OP_ID_SET,
AVM_EXEC_OP_ID_SET, AVM_EXEC_OP_ID_SET,
Expand All @@ -55,10 +55,10 @@ constexpr std::array<uint32_t, 23> OPERATION_IDS = {
AVM_EXEC_OP_ID_STATICCALL, AVM_EXEC_OP_ID_REVERT,
AVM_EXEC_OP_ID_REVERT, AVM_EXEC_OP_ID_RETURNDATASIZE,
AVM_EXEC_OP_ID_DEBUGLOG, AVM_EXEC_OP_ID_SLOAD,
AVM_EXEC_OP_ID_SSTORE,
AVM_EXEC_OP_ID_SSTORE, AVM_EXEC_OP_ID_NOTEHASH_EXISTS,
};

constexpr std::array<C, 23> SELECTOR_COLUMNS = {
constexpr std::array<C, 24> SELECTOR_COLUMNS = {
C::execution_sel_execute_get_env_var, C::execution_sel_execute_set,
C::execution_sel_execute_set, C::execution_sel_execute_set,
C::execution_sel_execute_set, C::execution_sel_execute_set,
Expand All @@ -70,7 +70,7 @@ constexpr std::array<C, 23> SELECTOR_COLUMNS = {
C::execution_sel_execute_static_call, C::execution_sel_execute_revert,
C::execution_sel_execute_revert, C::execution_sel_execute_returndata_size,
C::execution_sel_execute_debug_log, C::execution_sel_execute_sload,
C::execution_sel_execute_sstore,
C::execution_sel_execute_sstore, C::execution_sel_execute_notehash_exists,
};

// Ensure that WIRE_OPCODES contains all wire opcodes which have an execution opcode belonging
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ using C = Column;
using note_hash_tree_check = bb::avm2::note_hash_tree_check<FF>;
using RawPoseidon2 = crypto::Poseidon2<crypto::Poseidon2Bn254ScalarFieldParams>;

TEST(NoteHashTreeCheckConstrainingTests, PositiveRead)
TEST(NoteHashTreeCheckConstrainingTests, PositiveExists)
{
EventEmitter<Poseidon2HashEvent> hash_event_emitter;
EventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
Expand All @@ -76,8 +76,59 @@ TEST(NoteHashTreeCheckConstrainingTests, PositiveRead)
}
FF root = unconstrained_root_from_path(note_hash, leaf_index, sibling_path);

note_hash_tree_check_simulator.assert_read(
note_hash, leaf_index, sibling_path, AppendOnlyTreeSnapshot{ .root = root, .nextAvailableLeafIndex = 128 });
EXPECT_TRUE(note_hash_tree_check_simulator.note_hash_exists(
note_hash,
note_hash,
leaf_index,
sibling_path,
AppendOnlyTreeSnapshot{ .root = root, .nextAvailableLeafIndex = 128 }));

note_hash_tree_check_builder.process(note_hash_tree_check_event_emitter.dump_events(), trace);
merkle_check_builder.process(merkle_event_emitter.dump_events(), trace);

check_relation<note_hash_tree_check>(trace);
// Not checking all interactions due to the public inputs interaction, which needs to be checked in an e2e test
check_interaction<NoteHashTreeCheckTraceBuilder,
lookup_note_hash_tree_check_silo_poseidon2_settings,
lookup_note_hash_tree_check_read_first_nullifier_settings,
lookup_note_hash_tree_check_nonce_computation_poseidon2_settings,
lookup_note_hash_tree_check_unique_note_hash_poseidon2_settings,
lookup_note_hash_tree_check_merkle_check_settings>(trace);
}

TEST(NoteHashTreeCheckConstrainingTests, PositiveNotExists)
{
EventEmitter<Poseidon2HashEvent> hash_event_emitter;
EventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
Poseidon2 poseidon2(hash_event_emitter, perm_event_emitter);

EventEmitter<MerkleCheckEvent> merkle_event_emitter;
MerkleCheck merkle_check(poseidon2, merkle_event_emitter);

EventEmitter<simulation::NoteHashTreeCheckEvent> note_hash_tree_check_event_emitter;
NoteHashTreeCheck note_hash_tree_check_simulator(0, poseidon2, merkle_check, note_hash_tree_check_event_emitter);

TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
MerkleCheckTraceBuilder merkle_check_builder;
NoteHashTreeCheckTraceBuilder note_hash_tree_check_builder;

FF requested_note_hash = 42;
FF actual_leaf_value = 43;

uint64_t leaf_index = 30;
std::vector<FF> sibling_path;
sibling_path.reserve(NOTE_HASH_TREE_HEIGHT);
for (size_t i = 0; i < NOTE_HASH_TREE_HEIGHT; ++i) {
sibling_path.emplace_back(i);
}
FF root = unconstrained_root_from_path(actual_leaf_value, leaf_index, sibling_path);

EXPECT_FALSE(note_hash_tree_check_simulator.note_hash_exists(
requested_note_hash,
actual_leaf_value,
leaf_index,
sibling_path,
AppendOnlyTreeSnapshot{ .root = root, .nextAvailableLeafIndex = 128 }));

note_hash_tree_check_builder.process(note_hash_tree_check_event_emitter.dump_events(), trace);
merkle_check_builder.process(merkle_event_emitter.dump_events(), trace);
Expand Down
Loading
Loading