Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Note hash management in the AVM #10666

Merged
merged 8 commits into from
Dec 13, 2024
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
37 changes: 24 additions & 13 deletions barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "barretenberg/vm/avm/generated/verifier.hpp"
#include "barretenberg/vm/avm/trace/common.hpp"
#include "barretenberg/vm/avm/trace/deserialization.hpp"
#include "barretenberg/vm/avm/trace/gadgets/merkle_tree.hpp"
#include "barretenberg/vm/avm/trace/helper.hpp"
#include "barretenberg/vm/avm/trace/instructions.hpp"
#include "barretenberg/vm/avm/trace/kernel_trace.hpp"
Expand Down Expand Up @@ -336,23 +337,33 @@ std::vector<Row> Execution::gen_trace(AvmPublicInputs const& public_inputs,
if (phase == TxExecutionPhase::SETUP) {
vinfo("Inserting non-revertible side effects from private before SETUP phase. Checkpointing trees.");
// Temporary spot for private non-revertible insertion
std::vector<FF> siloed_nullifiers;
siloed_nullifiers.insert(
siloed_nullifiers.end(),
public_inputs.previous_non_revertible_accumulated_data.nullifiers.begin(),
public_inputs.previous_non_revertible_accumulated_data.nullifiers.begin() +
public_inputs.previous_non_revertible_accumulated_data_array_lengths.nullifiers);
trace_builder.insert_private_state(siloed_nullifiers, {});
auto siloed_nullifiers =
std::vector<FF>(public_inputs.previous_non_revertible_accumulated_data.nullifiers.begin(),
public_inputs.previous_non_revertible_accumulated_data.nullifiers.begin() +
public_inputs.previous_non_revertible_accumulated_data_array_lengths.nullifiers);

auto unique_note_hashes =
std::vector<FF>(public_inputs.previous_non_revertible_accumulated_data.note_hashes.begin(),
public_inputs.previous_non_revertible_accumulated_data.note_hashes.begin() +
public_inputs.previous_non_revertible_accumulated_data_array_lengths.note_hashes);

trace_builder.insert_private_state(siloed_nullifiers, unique_note_hashes);

trace_builder.checkpoint_non_revertible_state();
} else if (phase == TxExecutionPhase::APP_LOGIC) {
vinfo("Inserting revertible side effects from private before APP_LOGIC phase");
// Temporary spot for private revertible insertion
std::vector<FF> siloed_nullifiers;
siloed_nullifiers.insert(siloed_nullifiers.end(),
public_inputs.previous_revertible_accumulated_data.nullifiers.begin(),
public_inputs.previous_revertible_accumulated_data.nullifiers.begin() +
public_inputs.previous_revertible_accumulated_data_array_lengths.nullifiers);
trace_builder.insert_private_state(siloed_nullifiers, {});
auto siloed_nullifiers =
std::vector<FF>(public_inputs.previous_revertible_accumulated_data.nullifiers.begin(),
public_inputs.previous_revertible_accumulated_data.nullifiers.begin() +
public_inputs.previous_revertible_accumulated_data_array_lengths.nullifiers);

auto siloed_note_hashes =
std::vector<FF>(public_inputs.previous_revertible_accumulated_data.note_hashes.begin(),
public_inputs.previous_revertible_accumulated_data.note_hashes.begin() +
public_inputs.previous_revertible_accumulated_data_array_lengths.note_hashes);

trace_builder.insert_private_revertible_state(siloed_nullifiers, siloed_note_hashes);
}

vinfo("Beginning execution of phase ", to_name(phase), " (", public_call_requests.size(), " enqueued calls).");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ FF AvmMerkleTreeTraceBuilder::unconstrained_silo_note_hash(FF contract_address,
return Poseidon2::hash({ GENERATOR_INDEX__SILOED_NOTE_HASH, contract_address, note_hash });
}

FF AvmMerkleTreeTraceBuilder::unconstrained_compute_note_hash_nonce(FF tx_hash, FF note_index_in_tx)
{
return Poseidon2::hash({ GENERATOR_INDEX__NOTE_HASH_NONCE, tx_hash, note_index_in_tx });
}

FF AvmMerkleTreeTraceBuilder::unconstrained_compute_unique_note_hash(FF nonce, FF siloed_note_hash)
{
return Poseidon2::hash({ GENERATOR_INDEX__UNIQUE_NOTE_HASH, nonce, siloed_note_hash });
}

FF AvmMerkleTreeTraceBuilder::unconstrained_silo_nullifier(FF contract_address, FF nullifier)
{
return Poseidon2::hash({ GENERATOR_INDEX__OUTER_NULLIFIER, contract_address, nullifier });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ class AvmMerkleTreeTraceBuilder {
static FF unconstrained_hash_public_data_preimage(const PublicDataTreeLeafPreimage& preimage);

static FF unconstrained_silo_note_hash(FF contract_address, FF note_hash);
static FF unconstrained_compute_note_hash_nonce(FF tx_hash, FF note_index_in_tx);
static FF unconstrained_compute_unique_note_hash(FF nonce, FF siloed_note_hash);
static FF unconstrained_silo_nullifier(FF contract_address, FF nullifier);
static FF unconstrained_compute_public_tree_leaf_slot(FF contract_address, FF leaf_index);

Expand Down
44 changes: 38 additions & 6 deletions barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,14 @@ std::vector<uint8_t> AvmTraceBuilder::get_bytecode(const FF contract_address, bo
throw std::runtime_error("Bytecode not found");
}

uint32_t AvmTraceBuilder::get_inserted_note_hashes_count()
{
return merkle_tree_trace_builder.get_tree_snapshots().note_hash_tree.size -
public_inputs.start_tree_snapshots.note_hash_tree.size;
}

void AvmTraceBuilder::insert_private_state(const std::vector<FF>& siloed_nullifiers,
[[maybe_unused]] const std::vector<FF>& siloed_note_hashes)
const std::vector<FF>& unique_note_hashes)
{
for (const auto& siloed_nullifier : siloed_nullifiers) {
auto hint = execution_hints.nullifier_write_hints[nullifier_write_counter++];
Expand All @@ -225,6 +231,28 @@ void AvmTraceBuilder::insert_private_state(const std::vector<FF>& siloed_nullifi
siloed_nullifier,
hint.insertion_path);
}

for (const auto& unique_note_hash : unique_note_hashes) {
auto hint = execution_hints.note_hash_write_hints[note_hash_write_counter++];
merkle_tree_trace_builder.perform_note_hash_append(0, unique_note_hash, hint.sibling_path);
}
}

void AvmTraceBuilder::insert_private_revertible_state(const std::vector<FF>& siloed_nullifiers,
const std::vector<FF>& siloed_note_hashes)
{
// Revertibles come only siloed from private, so we need to make them unique here
std::vector<FF> unique_note_hashes;
unique_note_hashes.reserve(siloed_note_hashes.size());

for (size_t i = 0; i < siloed_note_hashes.size(); i++) {
size_t note_index_in_tx = i + get_inserted_note_hashes_count();
FF nonce = AvmMerkleTreeTraceBuilder::unconstrained_compute_note_hash_nonce(get_tx_hash(), note_index_in_tx);
unique_note_hashes.push_back(
AvmMerkleTreeTraceBuilder::unconstrained_compute_unique_note_hash(nonce, siloed_note_hashes.at(i)));
}

insert_private_state(siloed_nullifiers, unique_note_hashes);
}

void AvmTraceBuilder::pay_fee()
Expand Down Expand Up @@ -2770,8 +2798,8 @@ AvmError AvmTraceBuilder::op_note_hash_exists(uint8_t indirect,
AvmError AvmTraceBuilder::op_emit_note_hash(uint8_t indirect, uint32_t note_hash_offset)
{
auto const clk = static_cast<uint32_t>(main_trace.size()) + 1;

if (note_hash_write_counter >= MAX_NOTE_HASHES_PER_TX) {
uint32_t inserted_note_hashes_count = get_inserted_note_hashes_count();
if (inserted_note_hashes_count >= MAX_NOTE_HASHES_PER_TX) {
AvmError error = AvmError::SIDE_EFFECT_LIMIT_REACHED;
auto row = Row{
.main_clk = clk,
Expand All @@ -2791,16 +2819,20 @@ AvmError AvmTraceBuilder::op_emit_note_hash(uint8_t indirect, uint32_t note_hash
row.main_op_err = FF(static_cast<uint32_t>(!is_ok(error)));

AppendTreeHint note_hash_write_hint = execution_hints.note_hash_write_hints.at(note_hash_write_counter++);
auto siloed_note_hash = AvmMerkleTreeTraceBuilder::unconstrained_silo_note_hash(
FF siloed_note_hash = AvmMerkleTreeTraceBuilder::unconstrained_silo_note_hash(
current_public_call_request.contract_address, row.main_ia);
ASSERT(row.main_ia == note_hash_write_hint.leaf_value);
FF nonce =
AvmMerkleTreeTraceBuilder::unconstrained_compute_note_hash_nonce(get_tx_hash(), inserted_note_hashes_count);
FF unique_note_hash = AvmMerkleTreeTraceBuilder::unconstrained_compute_unique_note_hash(nonce, siloed_note_hash);

ASSERT(unique_note_hash == note_hash_write_hint.leaf_value);
// We first check that the index is currently empty
bool insert_index_is_empty = merkle_tree_trace_builder.perform_note_hash_read(
clk, FF::zero(), note_hash_write_hint.leaf_index, note_hash_write_hint.sibling_path);
ASSERT(insert_index_is_empty);

// Update the root with the new leaf that is appended
merkle_tree_trace_builder.perform_note_hash_append(clk, siloed_note_hash, note_hash_write_hint.sibling_path);
merkle_tree_trace_builder.perform_note_hash_append(clk, unique_note_hash, note_hash_write_hint.sibling_path);

// Constrain gas cost
gas_trace_builder.constrain_gas(clk, OpCode::EMITNOTEHASH);
Expand Down
6 changes: 5 additions & 1 deletion barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ class AvmTraceBuilder {
void rollback_to_non_revertible_checkpoint();
std::vector<uint8_t> get_bytecode(const FF contract_address, bool check_membership = false);
std::unordered_set<FF> bytecode_membership_cache;
void insert_private_state(const std::vector<FF>& siloed_nullifiers, const std::vector<FF>& siloed_note_hashes);
void insert_private_state(const std::vector<FF>& siloed_nullifiers, const std::vector<FF>& unique_note_hashes);
void insert_private_revertible_state(const std::vector<FF>& siloed_nullifiers,
const std::vector<FF>& siloed_note_hashes);
void pay_fee();

// These are used for testing only.
Expand Down Expand Up @@ -359,6 +361,8 @@ class AvmTraceBuilder {
AvmMemoryTag write_tag,
IntermRegister reg,
AvmMemTraceBuilder::MemOpOwner mem_op_owner = AvmMemTraceBuilder::MAIN);
uint32_t get_inserted_note_hashes_count();
FF get_tx_hash() const { return public_inputs.previous_non_revertible_accumulated_data.nullifiers[0]; }

// TODO: remove these once everything is constrained.
AvmMemoryTag unconstrained_get_memory_tag(AddressWithMode addr);
Expand Down
6 changes: 3 additions & 3 deletions yarn-project/circuits.js/src/hash/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ export function siloNoteHash(contract: AztecAddress, uniqueNoteHash: Fr): Fr {
* Computes a unique note hash.
* @dev Includes a nonce which contains data that guarantees the resulting note hash will be unique.
* @param nonce - A nonce (typically derived from tx hash and note hash index in the tx).
* @param noteHash - A note hash.
* @param siloedNoteHash - A note hash.
* @returns A unique note hash.
*/
export function computeUniqueNoteHash(nonce: Fr, noteHash: Fr): Fr {
return poseidon2HashWithSeparator([nonce, noteHash], GeneratorIndex.UNIQUE_NOTE_HASH);
export function computeUniqueNoteHash(nonce: Fr, siloedNoteHash: Fr): Fr {
return poseidon2HashWithSeparator([nonce, siloedNoteHash], GeneratorIndex.UNIQUE_NOTE_HASH);
}

/**
Expand Down
17 changes: 15 additions & 2 deletions yarn-project/simulator/src/avm/avm_simulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import {
SerializableContractInstance,
} from '@aztec/circuits.js';
import { Grumpkin } from '@aztec/circuits.js/barretenberg';
import { computePublicDataTreeLeafSlot, computeVarArgsHash, siloNullifier } from '@aztec/circuits.js/hash';
import {
computeNoteHashNonce,
computePublicDataTreeLeafSlot,
computeUniqueNoteHash,
computeVarArgsHash,
siloNoteHash,
siloNullifier,
} from '@aztec/circuits.js/hash';
import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing';
import { FunctionSelector } from '@aztec/foundation/abi';
import { AztecAddress } from '@aztec/foundation/aztec-address';
Expand Down Expand Up @@ -66,6 +73,7 @@ import {
mockGetContractClass,
mockGetContractInstance,
mockL1ToL2MessageExists,
mockNoteHashCount,
mockNoteHashExists,
mockNullifierExists,
mockStorageRead,
Expand Down Expand Up @@ -155,6 +163,7 @@ describe('AVM simulator: transpiled Noir contracts', () => {

const trace = mock<PublicSideEffectTraceInterface>();
const nestedTrace = mock<PublicSideEffectTraceInterface>();
mockNoteHashCount(trace, 0);
mockTraceFork(trace, nestedTrace);
const ephemeralTrees = await AvmEphemeralForest.create(worldStateDB.getMerkleInterface());
const persistableState = initPersistableStateManager({ worldStateDB, trace, merkleTrees: ephemeralTrees });
Expand Down Expand Up @@ -621,13 +630,17 @@ describe('AVM simulator: transpiled Noir contracts', () => {
const calldata = [value0];
const context = createContext(calldata);
const bytecode = getAvmTestContractBytecode('new_note_hash');
mockNoteHashCount(trace, 0);

const results = await new AvmSimulator(context).executeBytecode(bytecode);
expect(results.reverted).toBe(false);
expect(results.output).toEqual([]);

expect(trace.traceNewNoteHash).toHaveBeenCalledTimes(1);
expect(trace.traceNewNoteHash).toHaveBeenCalledWith(expect.objectContaining(address), /*noteHash=*/ value0);
const siloedNotehash = siloNoteHash(address, value0);
const nonce = computeNoteHashNonce(Fr.fromBuffer(context.persistableState.txHash.toBuffer()), 0);
const uniqueNoteHash = computeUniqueNoteHash(nonce, siloedNotehash);
expect(trace.traceNewNoteHash).toHaveBeenCalledWith(uniqueNoteHash);
});

it('Should append a new nullifier correctly', async () => {
Expand Down
4 changes: 3 additions & 1 deletion yarn-project/simulator/src/avm/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isNoirCallStackUnresolved } from '@aztec/circuit-types';
import { TxHash, isNoirCallStackUnresolved } from '@aztec/circuit-types';
import { GasFees, GlobalVariables, MAX_L2_GAS_PER_TX_PUBLIC_PORTION } from '@aztec/circuits.js';
import { type FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi';
import { AztecAddress } from '@aztec/foundation/aztec-address';
Expand Down Expand Up @@ -45,6 +45,7 @@ export function initPersistableStateManager(overrides?: {
nullifiers?: NullifierManager;
doMerkleOperations?: boolean;
merkleTrees?: AvmEphemeralForest;
txHash?: TxHash;
}): AvmPersistableStateManager {
const worldStateDB = overrides?.worldStateDB || mock<WorldStateDB>();
return new AvmPersistableStateManager(
Expand All @@ -54,6 +55,7 @@ export function initPersistableStateManager(overrides?: {
overrides?.nullifiers || new NullifierManager(worldStateDB),
overrides?.doMerkleOperations || false,
overrides?.merkleTrees || mock<AvmEphemeralForest>(),
overrides?.txHash || new TxHash(new Fr(27).toBuffer()),
);
}

Expand Down
9 changes: 7 additions & 2 deletions yarn-project/simulator/src/avm/journal/journal.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AztecAddress, SerializableContractInstance, computePublicBytecodeCommitment } from '@aztec/circuits.js';
import { siloNullifier } from '@aztec/circuits.js/hash';
import { computeNoteHashNonce, computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash';
import { makeContractClassPublic } from '@aztec/circuits.js/testing';
import { Fr } from '@aztec/foundation/fields';

Expand All @@ -13,6 +13,7 @@ import {
mockGetContractClass,
mockGetContractInstance,
mockL1ToL2MessageExists,
mockNoteHashCount,
mockNoteHashExists,
mockNullifierExists,
mockStorageRead,
Expand Down Expand Up @@ -80,9 +81,13 @@ describe('journal', () => {
});

it('writeNoteHash works', () => {
mockNoteHashCount(trace, 1);
persistableState.writeNoteHash(address, utxo);
expect(trace.traceNewNoteHash).toHaveBeenCalledTimes(1);
expect(trace.traceNewNoteHash).toHaveBeenCalledWith(expect.objectContaining(address), /*noteHash=*/ utxo);
const siloedNotehash = siloNoteHash(address, utxo);
const nonce = computeNoteHashNonce(Fr.fromBuffer(persistableState.txHash.toBuffer()), 1);
const uniqueNoteHash = computeUniqueNoteHash(nonce, siloedNotehash);
expect(trace.traceNewNoteHash).toHaveBeenCalledWith(uniqueNoteHash);
});

it('checkNullifierExists works for missing nullifiers', async () => {
Expand Down
Loading
Loading