diff --git a/barretenberg/cpp/pil/vm2/opcodes/get_contract_instance.pil b/barretenberg/cpp/pil/vm2/opcodes/get_contract_instance.pil index f2becd91207f..38098cde12ae 100644 --- a/barretenberg/cpp/pil/vm2/opcodes/get_contract_instance.pil +++ b/barretenberg/cpp/pil/vm2/opcodes/get_contract_instance.pil @@ -42,7 +42,7 @@ include "../bytecode/contract_instance_retrieval.pil"; * - is_init_hash * +-------+----------------------+---------------+-------------+---------------+ * | Row | is_valid_member_enum | is_deployer | is_class_id | is_init_hash | - * | (clk) | | | | | + * | (idx) | | | | | * +-------+----------------------+---------------+-------------+---------------+ * | 0 | 1 | 1 | 0 | 0 | * | 1 | 1 | 0 | 1 | 0 | diff --git a/barretenberg/cpp/pil/vm2/opcodes/get_env_var.pil b/barretenberg/cpp/pil/vm2/opcodes/get_env_var.pil index 6d774c7e3d25..316d8b82b490 100644 --- a/barretenberg/cpp/pil/vm2/opcodes/get_env_var.pil +++ b/barretenberg/cpp/pil/vm2/opcodes/get_env_var.pil @@ -46,7 +46,7 @@ include "../public_inputs.pil"; * columns in this gadget: * +-------+-----------------+-------------------+---------------------+--------------------+-------------------+------------+-----------+------------------+---------------+-----------------+--------------+----------------+-----------+ * | Row | Env Variable | invalid_envvar_ | sel_envvar_pi_col0 | sel_envvar_pi_col1 | envvar_pi_row_idx | is_address | is_sender | is_transactionfee| is_feeperl2gas| is_isstaticcall | is_l2gasleft | is_dagasleft | out_tag | - * | (clk) | | enum | lookup | lookup | | | | | | | | | | + * | (idx) | | enum | lookup | lookup | | | | | | | | | | * +-------+-----------------+-------------------+---------------------+--------------------+-------------------+------------+-----------+------------------+---------------+-----------------+--------------+----------------+-----------+ * | 0 | address | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | FF | * | 1 | sender | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | FF | @@ -81,7 +81,7 @@ namespace execution; // this is a virtual gadget that shares rows with the execu pol commit is_isstaticcall; pol commit is_l2gasleft; pol commit is_dagasleft; - // This lookup must be valid for all clks up to max u8 (255) + // This lookup must be valid for all idx values up to max u8 (255) // to allow error handling for any invalid enum that fits in a u8 immediate. // So, use the `precomputed.sel_range_8` as selector into the precomputed table. #[PRECOMPUTED_INFO] diff --git a/barretenberg/cpp/pil/vm2/precomputed.pil b/barretenberg/cpp/pil/vm2/precomputed.pil index 10dbbeeda318..01d6ee3524ad 100644 --- a/barretenberg/cpp/pil/vm2/precomputed.pil +++ b/barretenberg/cpp/pil/vm2/precomputed.pil @@ -1,7 +1,93 @@ +/* + * Precomputed Subtrace + * + * This subtrace defines all constant (precomputed) columns used across the AVM. + * As the name suggests, the columns are known before execution and do not change per transaction/program. + * They are computed once and shared across all instances of VM execution. + * + * Note: As precomputed values are committed to as part of the AVM verification key (VK), any changes to these columns or their + * values will result in changes to the AVM VK. + * + * USAGE: All precomputed columns are consumed via lookups: other subtraces look up attributes at a given index. + * The general pattern is: + * + * caller_sel { key, val1, val2, ... } + * in + * precomputed. { precomputed.idx, precomputed., precomputed., ... }; + * + * Example (from alu.pil) — range-check shift_lo_bits to [0, 255] and retrieve power_of_2: + * + * sel_shift_ops_no_overflow { shift_lo_bits, two_pow_shift_lo_bits } + * in + * precomputed.sel_range_8 { precomputed.idx, precomputed.power_of_2 }; + * + * Valid selectors determining the row ranges: + * - sel_range_8: rows [0, 2^8) — 8-bit range checks and power-of-2 lookups + * - sel_range_16: rows [0, 2^16) — 16-bit range checks + * - sel_sha256_compression: SHA-256 round constants + * - sel_tag_parameters: memory tag metadata (byte length, max bits, max value) + * - sel_to_radix_p_limb_counts: safe limb counts for radix decomposition + * - sel_p_decomposition: field modulus p limb decompositions + * - sel_exec_spec: execution instruction spec (gas costs, subtrace selectors, etc.) + * - sel_addressing_gas: addressing-mode bitmask to gas cost + * - sel_phase: transaction execution phase properties + * - sel_keccak: keccak round constants + * + * The columns are organized into several logical groups: + * + * 1. General-purpose columns: row index, zero column, first-row selector. + * + * 2. Range check selectors: boolean selectors defining valid 8-bit and 16-bit ranges. + * + * 3. Bitwise lookup tables: precomputed AND/OR/XOR results for all pairs of 8-bit inputs. + * + * 4. Power of 2 table: 2^idx for idx in [0, 255]. + * + * 5. SHA-256 round constants. + * + * 6. Memory tag parameters: maps each MemoryTag enum (FF, U1, U8, ..., U128) to its + * byte length, max bits, and max value. + * + * 7. Wire instruction spec (WIRE_INSTRUCTION_SPEC): maps a WireOpCode to operand + * decomposition selectors, execution opcode, instruction size, and tag metadata. + * + * 8. Radix decomposition tables: safe limb counts and decompositions of the field + * modulus p for the to_radix gadget. + * + * 9. Execution instruction spec (EXECUTION_INSTRUCTION_SPEC): maps an execution opcode + * to gas costs, register memory-access flags, tag-check info, subtrace/gadget selectors, + * and address-operand flags. + * + * 10. Addressing gas table: maps addressing-mode bitmask to the addressing gas cost. + * + * 11. Phase table: encodes transaction execution phases (setup, app-logic, teardown, etc.) + * and their properties for the transaction trace. + * + * 12. Keccak round constants. + * + * 13. GETENVVAR opcode columns: maps environment variable enum values to public input + * lookup metadata and type tags. + * + * 14. GETCONTRACTINSTANCE opcode columns: maps contract instance member enum values + * to their selectors. + */ // General/shared precomputed columns. namespace precomputed; -// From 0 and incrementing up to the size of the circuit (2^21). +// ===== Section 1: General-purpose columns ===== +// Basic index and selector columns used throughout the AVM. +// +// Example trace: +// idx | zero | first_row +// ------+------+---------- +// 0 | 0 | 1 +// 1 | 0 | 0 +// 2 | 0 | 0 +// ... | 0 | 0 +// 65535 | 0 | 0 + + +// From 0 and incrementing up to PRECOMPUTED_TRACE_SIZE (2^16). pol constant idx; // A column of zeroes @@ -10,8 +96,40 @@ pol constant zero; // 1 only at row 0. pol constant first_row; +// ===== Section 2: Range check selectors ===== +// Boolean selectors that define valid ranges for lookup tables. +// The actual range-checked value is the idx column. +// +// Example trace: +// idx | sel_range_8 | sel_range_16 +// ------+-------------+------------- +// 0 | 1 | 1 +// ... | 1 | 1 +// 255 | 1 | 1 +// 256 | 0 | 1 +// ... | 0 | 1 +// 65535 | 0 | 1 +pol constant sel_range_8; // 1 in the first 2^8 rows [0, 2^8) +pol constant sel_range_16; // 1 in the first 2^16 rows [0, 2^16) + +// ===== Section 3: Bitwise lookup tables ===== +// Precomputed AND/OR/XOR results for all pairs of 8-bit inputs (256x256 = 65536 rows). +// Row index = (input_a << 8) | input_b, so all 65536 pairs are covered. +// +// Example trace: +// idx | sel_range_16 | input_a | input_b | output_and | output_or | output_xor +// ------+--------------+---------+---------+------------+-----------+----------- +// 0 | 1 | 0 | 0 | 0 | 0 | 0 +// 1 | 1 | 0 | 1 | 0 | 1 | 1 +// ... | 1 | ... | ... | ... | ... | ... +// 255 | 1 | 0 | 255 | 0 | 255 | 255 +// 256 | 1 | 1 | 0 | 0 | 1 | 1 +// 257 | 1 | 1 | 1 | 1 | 1 | 0 +// 258 | 1 | 1 | 2 | 0 | 3 | 3 +// ... | 1 | ... | ... | ... | ... | ... +// 65535 | 1 | 255 | 255 | 255 | 255 | 0 + // AND/OR/XOR of all 8-bit numbers. -// Note: think if we can avoid the selector. // We will select this table with sel_range_16. pol constant bitwise_input_a; // column of all 8-bit numbers. pol constant bitwise_input_b; // column of all 8-bit numbers. @@ -19,31 +137,63 @@ pol constant bitwise_output_and; // output = a AND b. pol constant bitwise_output_or; // output = a OR b. pol constant bitwise_output_xor; // output = a XOR b. -// Boolean selectors for 8-bit and 16-bit range checks. -// We reuse idx for the actual values. -pol constant sel_range_8; // 1 in the first 2^8 rows [0, 2^8) -pol constant sel_range_16; // 1 in the first 2^16 rows [0, 2^16) +// ===== Section 4: Power of 2 table ===== +// Lookup table for 2^idx, populated for idx in [0, 255]. Note the [0, 255] property is guaranteed by the +// use of sel_range_8 as the dst selector (since idx actually goes from [0, 65536]). +// Care must be taken when using this table to ensure the idx being looked up is range checked. +// +// Note that 2^254 and 2^255 is larger than the field so the values stored in the +// 2^254 and 2^255 modulo p respectively. +// This could be mitigated by using a specific index for the powers of 2 table rather than reusing idx +// +// Example trace: +// idx | power_of_2 +// ----+----------- +// 0 | 1 +// 1 | 2 +// ... | ... +// 7 | 128 +// 8 | 256 +// ... | ... +// 255 | 2^255 // All the powers of 2 from 0 to 255 // For a given row, the exponent is idx (value = 2^idx) // Populated for the first 256 rows [0, 255] pol constant power_of_2; +// ===== Section 5: SHA-256 round constants ===== +// The 64 round constants used in SHA-256 compression, indexed by round number. +// +// Example trace: +// idx | sel_sha256_compression | sha256_compression_round_constant +// ----+------------------------+---------------------------------- +// 0 | 1 | 0x428a2f98 +// 1 | 1 | 0x71374491 +// ... | ... | ... +// 63 | 1 | 0xc67178f2 + // SHA256 Round Params Lookup pol constant sel_sha256_compression; pol constant sha256_compression_round_constant; -// A mapping between a MemoryTag value and their respective parameters: -// {FF:0, U1: 1, U8: 1, U16: 2, ... , U128: 16} -// | tag | byte_length | max_bits | max_value | -// FF | 0 | 32 | 0 | p - 1 | -// u1 | 1 | 1 | 1 | 2^1 - 1 | -// u8 | 2 | 1 | 8 | 2^8 - 1 | -// u16 | 3 | 2 | 16 | 2^16 - 1 | -// u32 | 4 | 4 | 32 | 2^32 - 1 | -// u64 | 5 | 8 | 64 | 2^64 - 1 | -// u128 | 6 | 16 | 128 | 2^128 - 1 | -// The enum values of MemoryTag are present in column idx. +// ===== Section 6: Memory tag parameters ===== +// Maps each MemoryTag enum value to its byte length, max bits, and max value. +// Used by memory operations to enforce type constraints. +// +// Example trace (idx encodes the MemoryTag enum): +// idx | sel_tag_parameters | tag_byte_length | tag_max_bits | tag_max_value | sel_mem_tag_out_of_range +// ----+--------------------+-----------------+--------------+---------------+------------------------ +// 0 | 1 | 32 | 0 | p - 1 | 0 (FF) +// 1 | 1 | 1 | 1 | 1 | 0 (U1) +// 2 | 1 | 1 | 8 | 255 | 0 (U8) +// 3 | 1 | 2 | 16 | 2^16 -1 | 0 (U16) +// 4 | 1 | 4 | 32 | 2^32 -1 | 0 (U32) +// 5 | 1 | 8 | 64 | 2^64 -1 | 0 (U64) +// 6 | 1 | 16 | 128 | 2^128 -1 | 0 (U128) +// 7 | 0 | 0 | 0 | 0 | 1 (out of range) +// ... | 0 | 0 | 0 | 0 | 1 +// 255 | 0 | 0 | 0 | 0 | 1 pol constant sel_tag_parameters; // Toggle row with idx == FF,U1,U8,U16,...,U128 pol constant tag_byte_length; pol constant tag_max_bits; @@ -52,16 +202,31 @@ pol constant tag_max_value; // Toggled at every row where idx is a byte and outside of memory tag pol constant sel_mem_tag_out_of_range; -// WIRE INSTRUCTION SPEC table +// ===== Section 7: Wire instruction spec (WIRE_INSTRUCTION_SPEC) ===== // The WIRE_INSTRUCTION_SPEC maps a WireOpCode to different values related to the instruction format such as: // - array of decomposition selectors: sel_op_dc_XX // - corresponding execution opcode: exec_opcode // - instruction size (in bytes): instr_size // - Selector on whether the instruction has a tag: sel_has_tag // - Selector on whether operand op2 is a tag: sel_tag_is_op2 - +// +// Used by the instruction fetching subtrace to decode raw bytecode. // Selectors for operands decomposition into bytes (required by instr_fetching.pil) // This table is populated by a map generated by a cpp test defined in op_decomposition.test.cpp. +// +// Example trace (idx encodes the WireOpCode; sel_op_dc uses array notation): +// +// idx | sel_op_dc[0..16] | exec_opcode | instr_size | sel_has_tag | sel_tag_is_op2 | opcode_out_of_range +// -----------------+-------------------------------------+------------------+------------+-------------+----------------+-------------------- +// 0 (ADD_8) | [0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0] | 0 (ADD) | 5 | 0 | 0 | 0 +// 1 (ADD_16) | [0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0] | 0 (ADD) | 8 | 0 | 0 | 0 +// 2 (SUB_8) | [0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0] | 1 (SUB) | 5 | 0 | 0 | 0 +// ... | ... | ... | ... | ... | ... | ... +// 67 (TORADIXBE) | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] | 45 (TORADIXBE) | 13 | 0 | 0 | 0 +// 68 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] | 0 | 0 | 0 | 0 | 1 (invalid opcode) +// ... | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] | 0 | 0 | 0 | 0 | 1 +// 256 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] | 0 | 0 | 0 | 0 | 0 (beyond byte range) + pol constant sel_op_dc_0; pol constant sel_op_dc_1; pol constant sel_op_dc_2; @@ -90,6 +255,33 @@ pol constant sel_tag_is_op2; // (sel_tag_is_op2 == 0 && sel_has_tag == 1) ==> op // Toggled only up to idx = 255. (within range specified by sel_range_8) pol constant opcode_out_of_range; +// ===== Section 8: Radix decomposition tables ===== +// Lookup tables for the to_radix gadget: safe limb counts per radix, and +// the full limb-by-limb decomposition of the field modulus p for each radix. +// +// Example trace for limb counts (idx encodes the radix): +// num_limbs_for_p: the number of limbs to represent the field modulus (p) given a certain base (radix) +// safe_limbs: `num_limbs_for_p - 1`, the last limb is "unsafe" as it must be compared against the last decomposed limb to check for overflows. +// +// idx | sel_to_radix_p_limb_counts | to_radix_safe_limbs | to_radix_num_limbs_for_p +// ----+----------------------------+---------------------+------------------------- +// 0 | 0 | 0 | 0 (invalid radix choice) +// 1 | 0 | 0 | 0 (invalid radix choice) +// 2 | 1 | 253 | 254 (radix 2) +// 10 | 1 | 76 | 77 (radix 10) +// 256 | 1 | 31 | 32 (radix 256) +// +// Example trace for p decomposition (rows are contiguous, radix-2 first then radix-3, etc.): +// row | sel_p_decomposition | p_decomposition_radix | p_decomposition_limb_index | p_decomposition_limb +// ----+---------------------+-----------------------+----------------------------+--------------------- +// 0 | 1 | 2 | 0 | 1 (p bit 0: LSB=1, p is odd) +// 1 | 1 | 2 | 1 | 0 (p bit 1) +// ... | 1 | 2 | ... | ... +// 253 | 1 | 2 | 253 | 0 (p bit 253: MSB of p in base-2) +// 254 | 1 | 3 | 0 | 1 (p limb 0 in base-3) +// 255 | 1 | 3 | 1 | 0 (p limb 1 in base-3) +// ... | ... | ... | ... | ... + // Used for getting the number of safe limbs for a given radix. // The selector is on for 1 < idx <= 256 pol constant sel_to_radix_p_limb_counts; @@ -104,9 +296,21 @@ pol constant p_decomposition_radix; pol constant p_decomposition_limb_index; pol constant p_decomposition_limb; -// EXECUTION INSTRUCTION SPEC table -// Maps an execution opcode value to useful information used during execution -// exec_opcode is idx +// ===== Section 9: Execution instruction spec (EXECUTION_INSTRUCTION_SPEC) ===== +// Each valid row encodes: gas costs (L2 opcode, base DA, dynamic L2, dynamic DA), +// per-register flags (memory access, read/write, tag check, expected tag) for 6 registers, +// subtrace/gadget dispatch IDs, dynamic gas ID, and address-operand flags for 7 operands. +// Used by the execution subtrace to dispatch and constrain instruction behavior. +// +// Example trace (idx encodes the ExecutionOpCode; array columns use [a,b,...] notation): +// +// idx | sel_exec_spec | opcode_gas | base_da | dyn_l2 | dyn_da | mem_op[0..5] | rw[0..5] | subtrace_id | sub_op_id | dyn_gas_id | is_addr[0..6] +// -----------------+---------------+------------+---------+--------+--------+--------------------+--------------------+-------------+-----------+------------+---------------------- +// 0 (ADD) | 1 | 12 | 0 | 0 | 0 | [1,1,1,0,0,0] | [0,0,1,0,0,0] | 1 | 1 | 0 | [1,1,1,0,0,0,0] +// 1 (SUB) | 1 | 12 | 0 | 0 | 0 | [1,1,1,0,0,0] | [0,0,1,0,0,0] | 1 | 2 | 0 | [1,1,1,0,0,0,0] +// ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... +// 45 (TORADIXBE) | 1 | 24 | 0 | 3 | 0 | [1,1,1,1,0,0] | [0,0,0,0,0,0] | 13 | 0 | 4 | [1,1,1,1,1,0,0] + pol constant sel_exec_spec; // Gas Costs pol constant exec_opcode_opcode_gas; @@ -127,11 +331,54 @@ pol constant subtrace_operation_id; // NOTE: this could be encoded in bits in 1 column and reconstructed. pol constant sel_op_is_address[7]; -// Addressing gas -// Indirect is idx +// ===== Section 10: Addressing gas table ===== +// Maps an addressing-mode bitmask (encoded in idx) to the total addressing gas cost. +// Used by the addressing subtrace to look up gas overhead from indirection and relative addressing. +// +// idx encodes the indirect/relative operand bitmask: each operand i uses 2 bits, +// bit[2i] = indirect, bit[2i+1] = relative. Only the first AVM_MAX_OPERANDS (7) are considered. +// Example encoding for idx = 11 (0b1011): +// operand: | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +// bits: | R I | R I | R I | R I | R I | R I | R I | +// values: | 0 0 | 0 0 | 0 0 | 0 0 | 0 0 | 1 0 | 1 1 | +// => 1 indirect + 2 relative => gas = 3 + 1*3 + 2*3 = 12 +// +// Example trace: +// idx | sel_addressing_gas | addressing_gas +// ------+--------------------+--------------- +// 0 | 1 | 0 (no indirect/relative operands) +// 1 | 1 | 3 (operand 0 indirect) +// 3 | 1 | 9 (operand 0 indirect + relative) +// ... | 1 | ... +// 4095 | 1 | 39 (operands 0-5 indirect + relative) +// ... | 1 | ... +// 65535 | 1 | 45 (all 7 operands indirect + relative) + pol constant sel_addressing_gas; pol constant addressing_gas; +// ===== Section 11: Phase table ===== +// Encodes transaction execution phases (setup, app-logic, teardown, etc.) and their +// properties: which tree operations are allowed, public-input offsets, revert behavior. +// Used by the transaction trace to enforce phase-based constraints. +// +// Example trace (idx encodes the TransactionPhase enum; abbreviated column names): +// idx | sel_phase | is_call | is_teardown | is_collect_fee | is_tree_padding | is_cleanup | is_revertible | nr_null | nr_note | nr_l2l1 | r_null | r_note | r_l2l1 | next_on_revert +// ----+-----------+---------+-------------+----------------+-----------------+------------+---------------+---------+---------+---------+--------+--------+--------+--------------- +// 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 (NR_NULLIFIER_INSERTION) +// 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 (NR_NOTE_INSERTION) +// 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 (NR_L2_TO_L1_MESSAGE) +// 3 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (SETUP) +// 4 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 8 (R_NULLIFIER_INSERTION) +// 5 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 8 (R_NOTE_INSERTION) +// 6 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 8 (R_L2_TO_L1_MESSAGE) +// 7 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 8 (APP_LOGIC) +// 8 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 9 (TEARDOWN) +// 9 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (COLLECT_GAS_FEES) +// 10 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (TREE_PADDING) +// 11 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (CLEANUP) +// 12 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (beyond phase range) + // Phase Table for Tx Trace pol constant sel_phase; // Selector for phase table // phase_value is encoded in idx, e.g.: NR_PRIVATE_INSERTS, SETUP, R_PRIVATE_INSERTS, APPLOGIC, TEARDOWN @@ -151,14 +398,32 @@ pol constant sel_revertible_append_nullifier; pol constant sel_revertible_append_l2_l1_msg; pol constant next_phase_on_revert; +// ===== Section 12: Keccak round constants ===== +// The 24 round constants used in the Keccak-f[1600] permutation. +// +// Example trace (note: starts at row 1, not row 0): +// idx | sel_keccak | keccak_round_constant +// ----+------------+---------------------- +// 0 | 0 | 0 +// 1 | 1 | 0x0000000000000001 +// 2 | 1 | 0x0000000000008082 +// 3 | 1 | 0x800000000000808a +// ... | 1 | ... +// 24 | 1 | 0x8000000080008008 +// 25 | 0 | 0 + // Keccak round constants // Store the 24 round constants with indices ranging from 1 to 24. // Warning: Not a standard 0-based array! pol constant sel_keccak; pol constant keccak_round_constant; -// GETENVVAR opcode precomputed columns +// ===== Section 13: GETENVVAR opcode columns ===== +// Maps environment variable enum values to public-input lookup metadata and type tags. +// Used by the GETENVVAR opcode to resolve environment reads at proving time. +// // see opcodes/get_env_var.pil for ascii table + pol constant invalid_envvar_enum; pol constant sel_envvar_pi_lookup_col0; pol constant sel_envvar_pi_lookup_col1; @@ -171,8 +436,13 @@ pol constant is_l2gasleft; pol constant is_dagasleft; pol constant out_tag; -// GETCONTRACTINSTANCE opcode precomputed columns +// ===== Section 14: GETCONTRACTINSTANCE opcode columns ===== +// Maps contract instance member enum values to selectors indicating which field +// (deployer, class_id, init_hash) is being accessed. +// Used by the GETCONTRACTINSTANCE opcode. +// // see opcodes/get_contract_instance.pil for ascii table + pol constant is_valid_member_enum; pol constant is_deployer; pol constant is_class_id; diff --git a/barretenberg/cpp/src/barretenberg/vm2/tracegen/precomputed_trace.cpp b/barretenberg/cpp/src/barretenberg/vm2/tracegen/precomputed_trace.cpp index b1b27a7072f6..b6526eab5617 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/tracegen/precomputed_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/tracegen/precomputed_trace.cpp @@ -1,7 +1,6 @@ #include "barretenberg/vm2/tracegen/precomputed_trace.hpp" #include -#include #include #include @@ -9,7 +8,7 @@ #include "barretenberg/vm2/common/aztec_types.hpp" #include "barretenberg/vm2/common/gas.hpp" #include "barretenberg/vm2/common/instruction_spec.hpp" -#include "barretenberg/vm2/common/memory_types.hpp" +#include "barretenberg/vm2/common/opcodes.hpp" #include "barretenberg/vm2/common/tagged_value.hpp" #include "barretenberg/vm2/common/to_radix.hpp" #include "barretenberg/vm2/simulation/gadgets/keccakf1600.hpp" @@ -22,11 +21,20 @@ namespace bb::avm2::tracegen { using C = Column; +/** + * @brief Populate miscellaneous precomputed columns: first_row selector and idx (row index). + * @details `precomputed_first_row` is 1 only at row 0. `precomputed_idx` holds the row index (0..num_rows-1) + * and is used as the key for most precomputed lookups. `precomputed_zero` is intentionally left unset + * (the trace container defaults to 0). + */ void PrecomputedTraceBuilder::process_misc(TraceContainer& trace, const uint32_t num_rows) { // First row. trace.set(C::precomputed_first_row, 0, 1); + // Note: precomputed_zero is intentionally not populated. + // The trace container defaults unset values to FF(0), which is the desired value. + // Idx trace.reserve_column(C::precomputed_idx, num_rows); for (uint32_t i = 0; i < num_rows; i++) { @@ -34,6 +42,11 @@ void PrecomputedTraceBuilder::process_misc(TraceContainer& trace, const uint32_t } } +/** + * @brief Populate the 8-bit bitwise lookup table (AND, OR, XOR). + * @details Generates 256×256 = 65536 rows covering all pairs of 8-bit inputs (a, b). + * Row index is derived as (a << 8) | b. + */ void PrecomputedTraceBuilder::process_bitwise(TraceContainer& trace) { // 256 per input (a and b), and 3 different bitwise ops @@ -63,10 +76,9 @@ void PrecomputedTraceBuilder::process_bitwise(TraceContainer& trace) } /** - * Generate a selector column that activates the first 2^8 (256) rows. - * We can enforce that a value X is <= 8 bits via a lookup that checks - * whether the selector (sel_range_8) is high at the corresponding - * idx's row (X==idx). + * @brief Generate a selector column that activates the first 2^8 (256) rows. + * @details Enables 8-bit range checks: a value X is in [0, 255] iff sel_range_8 is 1 + * at the row where idx == X. */ void PrecomputedTraceBuilder::process_sel_range_8(TraceContainer& trace) { @@ -80,10 +92,9 @@ void PrecomputedTraceBuilder::process_sel_range_8(TraceContainer& trace) } /** - * Generate a selector column that activates the first 2^16 rows. - * We can enforce that a value X is <= 16 bits via a lookup that checks - * whether the selector (sel_range_16) is high at the corresponding - * idx's row (X==idx). + * @brief Generate a selector column that activates the first 2^16 (65536) rows. + * @details Enables 16-bit range checks: a value X is in [0, 65535] iff sel_range_16 is 1 + * at the row where idx == X. */ void PrecomputedTraceBuilder::process_sel_range_16(TraceContainer& trace) { @@ -97,11 +108,12 @@ void PrecomputedTraceBuilder::process_sel_range_16(TraceContainer& trace) } /** - * Generate a column where each row is a power of 2 (2^idx). - * Populate the first 256 rows. + * @brief Generate a column where row i holds 2^i, for i in [0, 255], for the values of 254 and 255 the values are + * modulo reduced by the field modulus (i.e. 2^254 and 2^255 mod p) since the trace operates over a finite field. */ void PrecomputedTraceBuilder::process_power_of_2(TraceContainer& trace) { + // This corresponds to the fact that sel_range_8 is used as the dst selector for lookups involving powers of 2. constexpr auto num_rows = 1 << 8; // 2^8 = 256 trace.reserve_column(C::precomputed_power_of_2, num_rows); for (uint32_t i = 0; i < num_rows; i++) { @@ -109,6 +121,10 @@ void PrecomputedTraceBuilder::process_power_of_2(TraceContainer& trace) } } +/** + * @brief Populate the 64 SHA-256 round constants (K_0 .. K_63) and their selector. + * The sel_sha256_compression selector is set high on these rows. + */ void PrecomputedTraceBuilder::process_sha256_round_constants(TraceContainer& trace) { constexpr std::array round_constants{ @@ -131,6 +147,10 @@ void PrecomputedTraceBuilder::process_sha256_round_constants(TraceContainer& tra } } +/** + * @brief Populate the memory tag parameters table (byte length, max bits, max value per tag). + * @details One row per MemoryTag variant (FF=0, U1=1, U8=2, ..., U128=6). + */ void PrecomputedTraceBuilder::process_tag_parameters(TraceContainer& trace) { using bb::avm2::MemoryTag; @@ -148,6 +168,13 @@ void PrecomputedTraceBuilder::process_tag_parameters(TraceContainer& trace) } } +/** + * @brief Populate the wire-level instruction specification table. + * @details One row per WireOpCode (0..67). Each row holds the operand decomposition selectors + * (sel_op_dc_0..17), the corresponding ExecutionOpCode, instruction size in bytes, and tag + * operand metadata. Rows beyond the last valid opcode are flagged with opcode_out_of_range. + * Used by the instruction-fetch subtrace to decode bytecode. + */ void PrecomputedTraceBuilder::process_wire_instruction_spec(TraceContainer& trace) { const std::array sel_op_dc_columns = { @@ -195,6 +222,13 @@ void PrecomputedTraceBuilder::process_wire_instruction_spec(TraceContainer& trac } } +/** + * @brief Populate the execution-level instruction specification table. + * @details One row per ExecutionOpCode (0..45). Each row holds gas costs (opcode L2, base DA, + * dynamic L2, dynamic DA), register info (mem_op, rw, tag_check, expected_tag for each of 6 + * registers), operand-is-address flags, and subtrace dispatch info (subtrace_id, + * subtrace_operation_id, dyn_gas_id). Used by the execution and gas subtraces. + */ void PrecomputedTraceBuilder::process_exec_instruction_spec(TraceContainer& trace) { constexpr std::array MEM_OP_REG_COLUMNS = { @@ -236,7 +270,7 @@ void PrecomputedTraceBuilder::process_exec_instruction_spec(TraceContainer& trac } }); // Register information. - const auto& register_info = get_exec_instruction_spec().at(exec_opcode).register_info; + const auto& register_info = exec_instruction_spec.register_info; for (size_t i = 0; i < AVM_MAX_REGISTERS; i++) { trace.set(MEM_OP_REG_COLUMNS.at(i), static_cast(exec_opcode), register_info.is_active(i) ? 1 : 0); trace.set(RW_COLUMNS.at(i), static_cast(exec_opcode), register_info.is_write(i) ? 1 : 0); @@ -264,6 +298,12 @@ void PrecomputedTraceBuilder::process_exec_instruction_spec(TraceContainer& trac } } +/** + * @brief Populate the TORADIXBE safe-limbs table (one row per radix 0..255). + * @details For each radix r, stores the number of limbs needed to represent the BN254 scalar + * field modulus p in base r, and the "safe limbs" count (num_limbs - 1). Radices 0 and 1 have + * no valid decomposition and default to 0. + */ void PrecomputedTraceBuilder::process_to_radix_safe_limbs(TraceContainer& trace) { const auto& p_limbs_per_radix = get_p_limbs_per_radix(); @@ -282,6 +322,13 @@ void PrecomputedTraceBuilder::process_to_radix_safe_limbs(TraceContainer& trace) } } +/** + * @brief Populate the TORADIXBE p-decomposition table. + * @details Stores the limb-by-limb decomposition of the BN254 field modulus p in every radix + * r ∈ [2, 255]. Rows are laid out sequentially: all limbs for radix 2, then radix 3, etc. + * Each row records (radix, limb_index, limb_value). Used to verify that TORADIXBE output + * does not exceed p. + */ void PrecomputedTraceBuilder::process_to_radix_p_decompositions(TraceContainer& trace) { const auto& p_limbs_per_radix = get_p_limbs_per_radix(); @@ -299,6 +346,11 @@ void PrecomputedTraceBuilder::process_to_radix_p_decompositions(TraceContainer& } } +/** + * @brief Populate the memory tag out-of-range selector. + * @details Flags rows where idx > MemoryTag::MAX (i.e., idx ∈ [7, 255]) with + * sel_mem_tag_out_of_range = 1. Used to detect invalid memory tags via lookup. + */ void PrecomputedTraceBuilder::process_memory_tag_range(TraceContainer& trace) { constexpr uint32_t num_rows = 1 << 8; // 256 @@ -308,6 +360,11 @@ void PrecomputedTraceBuilder::process_memory_tag_range(TraceContainer& trace) } } +/** + * @brief Populate the addressing-mode gas lookup table (65536 rows). + * @details For every possible 16-bit addressing mode bitfield, precomputes the L2 gas cost + * of address resolution (indirect dereferences + relative offsets). + */ void PrecomputedTraceBuilder::process_addressing_gas(TraceContainer& trace) { constexpr uint32_t num_rows = 1 << 16; // 65536 @@ -320,10 +377,15 @@ void PrecomputedTraceBuilder::process_addressing_gas(TraceContainer& trace) } } +/** + * @brief Populate the transaction phase specification table. + * @details One row per TransactionPhase (0..11). Each row holds boolean flags + * (is_public_call_request, is_teardown, is_revertible, etc.), public-input read offsets, + * side-effect append selectors, and the next_phase_on_revert value. Used by the tx subtrace + * via the #[READ_PHASE_SPEC] lookup. + */ void PrecomputedTraceBuilder::process_phase_table(TraceContainer& trace) { - using C = Column; - for (const auto& [phase_value, spec] : get_tx_phase_spec_map()) { const uint32_t row = static_cast(phase_value); @@ -351,6 +413,10 @@ void PrecomputedTraceBuilder::process_phase_table(TraceContainer& trace) } } +/** + * @brief Populate the 24 Keccak-f[1600] round constants and their selector. + * Row 0 is intentionally left empty (round constants are 1-indexed in the Keccak subtrace). + */ void PrecomputedTraceBuilder::process_keccak_round_constants(TraceContainer& trace) { uint32_t row = 1; @@ -365,23 +431,22 @@ void PrecomputedTraceBuilder::process_keccak_round_constants(TraceContainer& tra } /** + * @brief Populate the GETENVVAR lookup table. + * @details One row per EnvironmentVariable enum value (0..11). Each row maps the variable to + * its public-input column/row indices, type selectors (is_address, is_sender, etc.), and + * output tag. Rows beyond the valid enum range are flagged with invalid_envvar_enum = 1. * See `opcodes/get_env_var.pil` for an ascii version of this table. */ void PrecomputedTraceBuilder::process_get_env_var_table(TraceContainer& trace) { constexpr uint32_t NUM_ROWS = 1 << 8; - // Start by flagging `invalid_envvar_enum` as 1 for all rows. - // "valid" rows will be reset manually to 0 below. - for (uint32_t i = 0; i < NUM_ROWS; i++) { - trace.set(C::precomputed_invalid_envvar_enum, i, 1); - } + constexpr uint8_t NUM_VALID_ENV_VARS = static_cast(EnvironmentVariable::MAX) + 1; - for (uint8_t enum_value = 0; enum_value <= static_cast(EnvironmentVariable::MAX); enum_value++) { + for (uint8_t enum_value = 0; enum_value < NUM_VALID_ENV_VARS; enum_value++) { const auto& envvar_spec = GetEnvVarSpec::get_table(enum_value); trace.set(static_cast(enum_value), { { - { C::precomputed_invalid_envvar_enum, 0 }, // Reset the invalid enum flag for valid rows { C::precomputed_sel_envvar_pi_lookup_col0, envvar_spec.envvar_pi_lookup_col0 }, { C::precomputed_sel_envvar_pi_lookup_col1, envvar_spec.envvar_pi_lookup_col1 }, { C::precomputed_envvar_pi_row_idx, envvar_spec.envvar_pi_row_idx }, @@ -394,10 +459,19 @@ void PrecomputedTraceBuilder::process_get_env_var_table(TraceContainer& trace) { C::precomputed_out_tag, envvar_spec.out_tag }, } }); } + + // Flag invalid enum values (those beyond the valid range) as out-of-range. + // Valid rows default to 0 (the trace container's default). + for (uint32_t i = NUM_VALID_ENV_VARS; i < NUM_ROWS; i++) { + trace.set(C::precomputed_invalid_envvar_enum, i, 1); + } } /** - * See `opcodes/get_contract_instance.pil` for an ascii version of this table. + * @brief Populate the GETCONTRACTINSTANCE lookup table. + * @details One row per ContractInstanceMember enum value (DEPLOYER=0, CLASS_ID=1, INIT_HASH=2). + * Each row holds a validity flag and one-hot member selectors. See + * `opcodes/get_contract_instance.pil` for an ascii version of this table. */ void PrecomputedTraceBuilder::process_get_contract_instance_table(TraceContainer& trace) { diff --git a/barretenberg/cpp/src/barretenberg/vm2/tracegen/precomputed_trace.hpp b/barretenberg/cpp/src/barretenberg/vm2/tracegen/precomputed_trace.hpp index 3cc45adc0e75..dba1e4574a1e 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/tracegen/precomputed_trace.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/tracegen/precomputed_trace.hpp @@ -1,8 +1,6 @@ #pragma once -#include "barretenberg/vm2/common/avm_io.hpp" #include "barretenberg/vm2/common/constants.hpp" -#include "barretenberg/vm2/common/opcodes.hpp" #include "barretenberg/vm2/tracegen/trace_container.hpp" namespace bb::avm2::tracegen {