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
64 changes: 37 additions & 27 deletions barretenberg/cpp/pil/vm2/bytecode/bc_retrieval.pil
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ include "class_id_derivation.pil";

include "../constants_gen.pil";
include "../precomputed.pil";
include "../trees/retrieved_bytecodes_tree_check.pil";
include "../trees/indexed_tree_check.pil";

/**
* This subtrace constrains everything related to "retrieving" a bytecode given an address. It is responsible for proving success or failure of retrieval for a bytecode id
* and does not fetch the bytes themselves. In practice this means we:
* - Silo the address:
* - siloed_nullifier = H(DOM_SEP__SILOED_NULLIFIER, deployer_protocol_contract_address, address)
* - Enforced by lookup into contract_instance_retrieval.pil (#[CONTRACT_INSTANCE_RETRIEVAL]), which looks up nullifier_check.pil (#[DEPLOYMENT_NULLIFIER_READ]).
* - Enforced by lookup into contract_instance_retrieval.pil (#[CONTRACT_INSTANCE_RETRIEVAL]), which looks up indexed_tree_check.pil (#[DEPLOYMENT_NULLIFIER_READ]).
* - Check if the nullifier exists.
* - Enforced in above lookup (contract_instance_retrieval.pil (#[CONTRACT_INSTANCE_RETRIEVAL]) -> nullifier_check.pil (#[DEPLOYMENT_NULLIFIER_READ])).
* - Enforced in above lookup (contract_instance_retrieval.pil (#[CONTRACT_INSTANCE_RETRIEVAL]) -> indexed_tree_check.pil (#[DEPLOYMENT_NULLIFIER_READ])).
* - Derive the address.
* - Enforced in above lookup (contract_instance_retrieval.pil (#[CONTRACT_INSTANCE_RETRIEVAL]) -> address_derivation.pil (#[ADDRESS_DERIVATION])).
* - Note: for this trace, we only 'care' about the contract instance member current_class_id (not salt, deployer_addr, init_hash), which is validated in
Expand Down Expand Up @@ -63,10 +63,10 @@ include "../trees/retrieved_bytecodes_tree_check.pil";
* TRACE SHAPE: This subtrace retrieves one bytecode instance per row, anchored by the bytecode_id column.
*
* INTERACTIONS:
* execution.pil --> bc_retrieval.pil --> contract_instance_retrieval.pil --> nullifier_check.pil
* execution.pil --> bc_retrieval.pil --> contract_instance_retrieval.pil --> indexed_tree_check.pil
* --> address_derivation.pil
* --> update_check.pil
* --> retrieved_bytecodes_tree_check.pil
* --> indexed_tree_check.pil
* --> class_id_derivation.pil
* --> instr_fetching.pil --> bc_decomposition.pil <-> bc_hashing.pil
* --> precomputed.pil
Expand All @@ -81,9 +81,9 @@ include "../trees/retrieved_bytecodes_tree_check.pil";
* (#[CONTRACT_INSTANCE_RETRIEVAL]). Additionally links the state tree roots and enforces that the class id is zero if the
* instance does not exist. This lookup is crucial since we defer the nullifier, address, and update checks to the contract
* instance retrieval trace.
* - retrieved_bytecodes_tree_check.pil: If an instance exists, to constrain whether it is a new class id not yet accessed by this tx (#[IS_NEW_CLASS_CHECK]).
* If there is no error, to insert the class id into the retrieved bytecodes tree (#[RETRIEVED_BYTECODES_INSERTION]).
* These cases must form separate lookups to ensure we do not add to the tree when we have hit the bytecode limit.
* - indexed_tree_check.pil: If an instance exists, to constrain whether it is a new class id not yet accessed by this tx (#[IS_NEW_CLASS_CHECK]).
* If there is no error, to insert the class id into the retrieved bytecodes tree (#[RETRIEVED_BYTECODES_INSERTION]).
* These cases must form separate lookups to ensure we do not add to the tree when we have hit the bytecode limit.
* - class_id_derivation.pil: If there is no error, to constrain correctness of the class id against the contract class member columns of this trace
* (#[CLASS_ID_DERIVATION]). In terms of this trace's columns, that is:
* current_class_id = Poseidon2(DOM_SEP__CONTRACT_CLASS_ID, artifact_hash, private_functions_root, bytecode_id),
Expand Down Expand Up @@ -168,7 +168,7 @@ sel {
// Switching on the error column forces the class members to be zero.

// Whether we have retrieved the bytecode for this class before (if not, is_new_class == 1 and we insert the bytecode into the tree).
pol commit is_new_class; // @boolean (by lookup into retrieved_bytecodes_tree_check when instance_exists == 1; constrained to be 0 when instance_exists == 0)
pol commit is_new_class; // @boolean (by lookup into indexed_tree_check when instance_exists == 1; constrained to be 0 when instance_exists == 0)

// Whether we have retrieved the maximum number of different bytecodes for this tx.
pol commit no_remaining_bytecodes; // @boolean
Expand Down Expand Up @@ -233,36 +233,46 @@ should_retrieve {
// Bytecode Tree Read/Write
///////////////////////////////

// Lookup constant support: Can be removed when we support constants in lookups.
pol commit retrieved_bytecodes_tree_height;
sel * (retrieved_bytecodes_tree_height - constants.AVM_RETRIEVED_BYTECODES_TREE_HEIGHT) = 0;

// This constrains that is_new_class is on iff the bytecode does not exist in the transient tree (i.e. this
// bytecode has not been used in this tx).
// Note: instance_exists can be on for inactive rows, but this lookup below not create side effects, so
// it is safe to gate by instance_exists rather than sel && instance_exists.
#[IS_NEW_CLASS_CHECK]
instance_exists {
current_class_id,
is_new_class,
prev_retrieved_bytecodes_tree_root
} in retrieved_bytecodes_tree_check.sel {
retrieved_bytecodes_tree_check.class_id,
retrieved_bytecodes_tree_check.leaf_not_exists,
retrieved_bytecodes_tree_check.root
is_new_class, // not_exists
current_class_id, // value
prev_retrieved_bytecodes_tree_root,
retrieved_bytecodes_tree_height,
precomputed.zero // sel_silo = 0 (no siloing)
} in indexed_tree_check.sel {
indexed_tree_check.not_exists,
indexed_tree_check.value,
indexed_tree_check.root,
indexed_tree_check.tree_height,
indexed_tree_check.sel_silo
};

// This constrains that if we have retrieved bytecode for a new class, we update the tree correctly.
#[RETRIEVED_BYTECODES_INSERTION]
should_retrieve {
current_class_id,
should_retrieve, /*=1*/
current_class_id, // value
prev_retrieved_bytecodes_tree_root,
prev_retrieved_bytecodes_tree_size,
next_retrieved_bytecodes_tree_root,
next_retrieved_bytecodes_tree_size
} in retrieved_bytecodes_tree_check.sel {
retrieved_bytecodes_tree_check.class_id,
retrieved_bytecodes_tree_check.write,
retrieved_bytecodes_tree_check.root,
retrieved_bytecodes_tree_check.tree_size_before_write,
retrieved_bytecodes_tree_check.write_root,
retrieved_bytecodes_tree_check.tree_size_after_write
prev_retrieved_bytecodes_tree_size,
next_retrieved_bytecodes_tree_size,
retrieved_bytecodes_tree_height,
precomputed.zero // sel_silo = 0 (no siloing)
} in indexed_tree_check.write {
indexed_tree_check.value,
indexed_tree_check.root,
indexed_tree_check.write_root,
indexed_tree_check.tree_size_before_write,
indexed_tree_check.tree_size_after_write,
indexed_tree_check.tree_height,
indexed_tree_check.sel_silo
};

Original file line number Diff line number Diff line change
Expand Up @@ -129,20 +129,32 @@ namespace contract_instance_retrieval;
// Protocol contracts do not have an address nullifier in the nullifier tree.
should_check_nullifier = sel * (1 - is_protocol_contract);

// Lookup constant support: Can be removed when we support constants in lookups.
pol commit nullifier_tree_height;
should_check_nullifier * (nullifier_tree_height - constants.NULLIFIER_TREE_HEIGHT) = 0;

// Lookup constant support: Can be removed when we support constants in lookups.
pol commit siloing_separator;
should_check_nullifier * (siloing_separator - constants.DOM_SEP__SILOED_NULLIFIER) = 0;

// Nullifier existence check (deployment nullifier read)
#[DEPLOYMENT_NULLIFIER_READ]
should_check_nullifier {
exists, // does the contract address nullifier exist? gates later lookups....
address, // the deployment nullifier
nullifier_tree_root,
deployer_protocol_contract_address,
sel // 1 (yes silo)
} in nullifier_check.sel {
nullifier_check.exists,
nullifier_check.nullifier,
nullifier_check.root,
nullifier_check.address,
nullifier_check.sel_silo
nullifier_tree_height,
sel, // 1 (yes silo)
siloing_separator,
deployer_protocol_contract_address
} in indexed_tree_check.sel {
indexed_tree_check.exists,
indexed_tree_check.value,
indexed_tree_check.root,
indexed_tree_check.tree_height,
indexed_tree_check.sel_silo,
indexed_tree_check.siloing_separator,
indexed_tree_check.address
};

// For protocol contracts we retrieve the derived address from the protocol contract trace, else use the input address
Expand Down
10 changes: 4 additions & 6 deletions barretenberg/cpp/pil/vm2/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,9 @@ Subtraces that handle the different trees and squashing logic. Note: See [State
- [L1ToL2MessageTreeCheck](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/l1_to_l2_message_tree_check.pil): Merkle proof for L1→L2 message tree reads.
- [MerkleCheck](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/merkle_check.pil): Generic Merkle read/write; one path node per row, Poseidon2 hashes.
- [NoteHashTreeCheck](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/note_hash_tree_check.pil): Note hash tree membership and insertions.
- [NullifierCheck](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/nullifier_check.pil): Nullifier tree membership and insertions.
- [IndexedTreeCheck](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/indexed_tree_check.pil): Generic indexed tree membership and insertions (nullifier tree, retrieved bytecodes tree, written public data slots tree, etc.). Callers provide tree height and optional siloing / public inputs parameters.
- [PublicDataCheck](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/public_data_check.pil): Public data tree read/write with siloed slots; uses public_data_squash.
- [PublicDataSquash](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/public_data_squash.pil): Squashes public data writes by leaf slot for public inputs.
- [RetrievedBytecodesTreeCheck](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/retrieved_bytecodes_tree_check.pil): Transient tree of retrieved class IDs per transaction.
- [WrittenPublicDataSlotsTreeCheck](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/written_public_data_slots_tree_check.pil): Transient tree of written (contract, slot) for gas and squash.

**Gadgets**

Expand Down Expand Up @@ -437,19 +435,19 @@ flowchart TB

PI[Public Inputs]
NHT[NoteHashTreeCheck]
NC[NullifierCheck]
ITC[IndexedTreeCheck]
PDC[PublicDataCheck]

TXctx -->|read start/end tree roots and sizes| PI
TX -->|NOTE_HASH_APPEND: leaf, prev/next root and size, discard| NHT
TX -->|NULLIFIER_APPEND: nullifier, prev/next root and size, discard, exists| NC
TX -->|NULLIFIER_APPEND: nullifier, prev/next root and size, discard, exists| ITC
TX -->|BALANCE_READ: fee balance at slot| PDC
TX <-->|BALANCE_UPDATE: fee deduction write| PDC
```

- **TxContext** ([tx_context.pil](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/tx_context.pil)) owns the `prev_*` and `next_*` tree root/size columns for note hash, nullifier, public data, written public data slots, L1→L2 message, and retrieved bytecodes trees. It **reads** initial state from the public inputs at `start_tx` and end state at `is_cleanup` (lookups into [public_inputs.pil](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/public_inputs.pil)). It constrains **continuity** (e.g. `next_note_hash_tree_root` → `prev_note_hash_tree_root'` on the next row) and **immutability** when the current phase does not mutate a given tree. On revert, it **restores** tree state to the snapshot at the end of the setup phase (RESTORE_STATE_ON_REVERT).

- **Note hash and nullifier insertions (from private):** In the non-revertible and revertible insertion phases, Tx matches each insertion row to the corresponding tree-check subtrace. **NOTE_HASH_APPEND** is a **lookup** from `should_note_hash_append` into [note_hash_tree_check.write](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/note_hash_tree_check.pil): Tx supplies leaf value, prev root/size, next root, and discard; the tree check proves the Merkle update. **NULLIFIER_APPEND** is a **lookup** from `should_nullifier_append` into [nullifier_check.write](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/nullifier_check.pil): Tx supplies the nullifier, prev/next root and size, `discard`, `nullifier_index` (used for public input indexing), and `sel_silo` with `address` for optional siloing; the gadget returns `exists` (whether the nullifier was already present, which can force a revert) and `write_root` (unchanged if `exists = 1`, updated otherwise). Internally the gadget distinguishes a successful write (`sel_insert = write ∧ ¬exists`) from a failing write (`exists = 1`) — on a failing write no Merkle update occurs and the root is passed through unchanged.
- **Note hash and nullifier insertions (from private):** In the non-revertible and revertible insertion phases, Tx matches each insertion row to the corresponding tree-check subtrace. **NOTE_HASH_APPEND** is a **lookup** from `should_note_hash_append` into [note_hash_tree_check.write](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/note_hash_tree_check.pil): Tx supplies leaf value, prev root/size, next root, and discard; the tree check proves the Merkle update. **NULLIFIER_APPEND** is a **lookup** from `should_nullifier_append` into [indexed_tree_check.write](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/indexed_tree_check.pil): Tx supplies the nullifier, prev/next root and size, `discard`, `public_inputs_index` (used for public input indexing), `tree_height`, and `sel_silo` with `address` for optional siloing; the gadget returns `exists` (whether the nullifier was already present, which can force a revert) and `write_root` (unchanged if `exists = 1`, updated otherwise). Internally the gadget distinguishes a successful write (`sel_insert = write ∧ ¬exists`) from a failing write (`exists = 1`) — on a failing write no Merkle update occurs and the root is passed through unchanged.

- **Fee payment (public data tree):** In the collect-fee phase, Tx derives the fee-payer balance slot (via [Poseidon2](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/poseidon2_hash.pil)), then **looks up** [public_data_check.sel](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/trees/public_data_check.pil) (**BALANCE_READ**) to bind the current balance at that slot and root. It enforces fee ≤ balance (via [ff_gt](https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/ff_gt.pil)) and then **permutes** with **public_data_check.protocol_write** (**BALANCE_UPDATE**) to prove the fee deduction write (new balance, root/size update, discard).

Expand Down
39 changes: 28 additions & 11 deletions barretenberg/cpp/pil/vm2/execution.pil
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,9 @@ include "execution/gas.pil";
include "execution/registers.pil";

include "trees/merkle_check.pil";
include "trees/nullifier_check.pil";
include "trees/indexed_tree_check.pil";
include "trees/public_data_check.pil";
include "trees/written_public_data_slots_tree_check.pil";
include "trees/l1_to_l2_message_tree_check.pil";
include "trees/retrieved_bytecodes_tree_check.pil";

include "bytecode/address_derivation.pil";
include "bytecode/bc_decomposition.pil";
Expand Down Expand Up @@ -517,21 +515,36 @@ sel_gas_to_radix * ((/*num_limbs=*/register[2] - num_p_limbs) * sel_use_num_limb
//////////////////////////////////////////
// SSTORE, Dynamic DA Gas Calculation
//////////////////////////////////////////

// Lookup constant support: Can be removed when we support constants in lookups.
pol commit written_slots_tree_height;
sel_gas_sstore * (written_slots_tree_height - constants.AVM_WRITTEN_PUBLIC_DATA_SLOTS_TREE_HEIGHT) = 0;

// Lookup constant support: Can be removed when we support constants in lookups.
pol commit written_slots_tree_siloing_separator;
sel_gas_sstore * (written_slots_tree_siloing_separator - constants.DOM_SEP__PUBLIC_LEAF_SLOT) = 0;

// We can probably unconditionally write to the written_public_data_slots_tree here
// to avoid writing later in opcode execution, since the root would be reverted if it errors before
// the opcode execution. However, this feels like an early optimization
// since the simulator would need to write at the gas step which is a bit weird.
#[CHECK_WRITTEN_STORAGE_SLOT]
sel_gas_sstore {
contract_address,
register[1], // slot
dynamic_da_gas_factor,
prev_written_public_data_slots_tree_root
} in written_public_data_slots_tree_check.sel {
written_public_data_slots_tree_check.address,
written_public_data_slots_tree_check.slot,
written_public_data_slots_tree_check.leaf_not_exists,
written_public_data_slots_tree_check.root
register[1], // value
prev_written_public_data_slots_tree_root,
written_slots_tree_height,
sel_gas_sstore, // sel_silo = 1
written_slots_tree_siloing_separator,
contract_address
} in indexed_tree_check.sel {
indexed_tree_check.not_exists,
indexed_tree_check.value,
indexed_tree_check.root,
indexed_tree_check.tree_height,
indexed_tree_check.sel_silo,
indexed_tree_check.siloing_separator,
indexed_tree_check.address
};

///////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -775,6 +788,10 @@ sel_execute_returndata_size * (mem_tag_reg[0] - constants.MEM_TAG_U32) = 0; // W
#[RETRIEVED_BYTECODES_TREE_SIZE_NOT_CHANGED]
(1 - sel_first_row_in_context) * (prev_retrieved_bytecodes_tree_size - retrieved_bytecodes_tree_size) = 0;

// Constant height set in nullifier_exists.pil and emit_nullifier.pil
// Lookup constant support: Can be removed when we support constants in lookups.
pol commit nullifier_tree_height;

// Whether the opcode logic failed.
pol commit sel_opcode_error; // @boolean
sel_opcode_error * (1 - sel_opcode_error) = 0;
Expand Down
Loading
Loading