chore!: masking at the top of the trace#22334
Open
iakovenkos wants to merge 53 commits intomerge-train/barretenbergfrom
Open
chore!: masking at the top of the trace#22334iakovenkos wants to merge 53 commits intomerge-train/barretenbergfrom
iakovenkos wants to merge 53 commits intomerge-train/barretenbergfrom
Conversation
…ay from RDP
With top-of-trace masking, the row-disabling polynomial 1 - ∏_{i≥2}(1-u_i)
is circuit-size independent. The verifier evaluates it over ALL D challenges
without needing log_circuit_size or padding_indicator_array.
Prover: ZK virtual rounds now use compute_virtual_contribution * (1-L) + libra
instead of sending zero univariates. Libra univariates cover all D rounds.
Verifier: apply_zk_corrections evaluates RDP from challenge vector size.
compute_padding_indicator_array returns all-1s (unified path).
…i/eccvm-lagrange-last-integer
…i/eccvm-lagrange-last-integer
notnotraju
reviewed
Apr 14, 2026
notnotraju
reviewed
Apr 14, 2026
notnotraju
reviewed
Apr 14, 2026
|
|
||
| /** | ||
| * @brief Verify ECC op wires are zero in the disabled region (not masked). | ||
| * @details Unlike witness wires, ecc_op_wires are not masked (ZK for ops comes from random ops |
Contributor
There was a problem hiding this comment.
random question (out of scope): is there advantage to doing this (i.e., to forcing ZK for ops to come from merge?)
Contributor
|
In Similarly, in |
…i/eccvm-lagrange-last-integer
iakovenkos
commented
Apr 14, 2026
| static constexpr size_t VIRTUAL_LOG_N = MEGA_AVM_LOG_N; | ||
|
|
||
| // Gives ecc_op_wire polynomials 2 leading zeros, matching the Translator's shiftability layout. | ||
| static constexpr size_t TRACE_OFFSET = 1; |
Contributor
Author
There was a problem hiding this comment.
@ledwards2225 still feels a bit hacky but it allows to
- remove no ops from op queue/ mega and translator builders
- remove shifts from the eco op wire relation
iakovenkos
commented
Apr 14, 2026
| CommitmentKey pcs_commitment_key; | ||
|
|
||
| // Offset for L and R: matches the circuit's ecc_op_wire layout (TRACE_OFFSET + NUM_ZERO_ROWS). | ||
| static constexpr size_t FULL_SHIFT = MegaExecutionTraceBlocks::TRACE_OFFSET + NUM_ZERO_ROWS; |
Contributor
Author
There was a problem hiding this comment.
I don't like this constant but couldn't find a more elegant solution
critesjosh
pushed a commit
that referenced
this pull request
Apr 14, 2026
…22461) ## Summary Add `lagrange_first * transcript_accumulator_not_empty = 0` subrelation to `ECCVMTranscriptRelation`. This is a prerequisite for #22334 (masking at top of ECCVM circuit). The audit in #22442 identified that when `lagrange_first` moves to row k (away from the PCS-enforced zero row), `transcript_accumulator_not_empty` is the only shiftable column where a malicious prover can potentially set a non-zero value at the `lagrange_first` row without any existing relation catching it. Setting it to 1 disables `INFINITY_ACC_X/Y`, allowing arbitrary accumulator coordinates to be injected. ## Changes - New subrelation `ACCUMULATOR_NOT_EMPTY_INIT` in `ecc_transcript_relation.hpp` (degree 2) - Gate count updates (+174 gates from the new subrelation): - `ECCVM_RECURSIVE_VERIFIER_GATE_COUNT`: 224336 → 224510 - `CHONK_RECURSION_GATES`: 1491593 → 1491767 ## Test plan - [x] `eccvm_tests` — all 44 tests pass - [x] `stdlib_eccvm_verifier_tests` — `SingleRecursiveVerification` passes with updated gate count - [x] `dsl_tests` — `GateCountChonkRecursion` passes with updated gate count Co-authored-by: notnotraju <raju@aztec-labs.com>
notnotraju
reviewed
Apr 16, 2026
| MegaExecutionTraceBlocks() = default; | ||
|
|
||
| void compute_offsets() | ||
| void compute_offsets(size_t trace_offset = TRACE_OFFSET) |
Contributor
There was a problem hiding this comment.
at l. 305 in this file (meta_execution_trace.hpp), stale comment
notnotraju
reviewed
Apr 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Motivation
Previously, ZK masking used tail masking: random values were stored in separate
MaskingTailDatapolynomials at the end of the trace. This forced all PCS code to bedyadic_sizedependent — the batched polynomialA₀, Gemini folds, and Shplonk quotient all had to be allocated and operated atdyadic_sizebecause the masking tails extended to the last rows. The row-disabling polynomial was also circuit-size dependent (1 - L_{n-1} - ... - L_{n-4}), requiring apadding_indicator_arrayto handle padded sumcheck rounds.What Changed
Top-of-trace masking: Random masking values are written directly into witness polynomials at rows
{1, 2, 3}during allocation. The row-disabling polynomial disables the first 4 rows (0–3) instead of the last 4.lagrange_firstmoves from row0to rowTRACE_OFFSET (= 4).Key Benefits
dyadic_size: With masking at low indices, committed polynomials only extend tomax_end_index()(the actual trace extent), notdyadic_size. Gemini fold polynomials already trackactual_sizefromA_0.end_index(). This enables future non-dyadic PCS proving where sparse circuits avoid paying for the full power-of-2 padding.1 - ∏_{i≥2}(1-u_i)depends only on sumcheck challenges, not circuit size. Thepadding_indicator_arrayis eliminated.MaskingTailData(218 lines),padding_indicator_array(93+134 lines), and all tail-batching logic. Net-707lines across 103 files.padding_indicator_arrayparameters. This simplifies native, recursive, and AVM verifier flows alike.Merge Protocol and ECC Op Wire Changes
Moving
lagrange_firstfrom row0to rowTRACE_OFFSET (= 4)shifts the entire execution trace down by 4 rows. In Mega, theecc_opblock now starts attrace_offset() = TRACE_OFFSET + 1 = 5, andecc_op_wiredata sits one row before at rowTRACE_OFFSET = 4(to satisfy theecc_op_wire[row] == w_shift[row]constraint).The merge protocol must match this layout: it prepends
TRACE_OFFSETzeros to its table polynomials (L,R, andMin PREPEND mode) so that the prover's Shplonk quotient is consistent with theecc_op_wirecommitments held by the verifier. Previously, these polynomials started at row0; now they start at rowTRACE_OFFSETto align with the shifted circuit layout.The Translator receives its op queue data from Mega's
ecc_op_wires. Since the merge protocol handles the offset alignment in chonk, standard Mega → Translator flows work correctly. However, the two-layer AVM flow (AVM recursive verifier inside a Mega circuit) required addingTRACE_OFFSET = 0to the standalone AVM flavor. TheMegaAVMflavor inherits Mega'sTRACE_OFFSET = 4, soecc_op_wiresin the outer Mega circuit are offset. The inner AVM has no disabled region (TRACE_OFFSET = 0), but since it runs inside a Mega wrapper that handles the merge protocol, the op queue data alignment stays consistent across the boundary.Files Changed (103 files, +1484/-2191)
Deleted
sumcheck/masking_tail_data.hpp— tail masking infrastructurestdlib/primitives/padding_indicator_array/— virtual-round indicator for old row-disablingAdded
ultra_honk/zk_boundary.test.cpp— tests for masking layout,ecc_opalignment, row-disablingCore changes
prover_instance.cpp): Polynomials allocated withadd_masking()at rows{1,2,3};lagrange_firstatTRACE_OFFSET; trace blocks start atTRACE_OFFSET + 1oink_prover.cpp): Removed all tail references; commitments use base polynomials directlyultra_prover.cpp): CRS sized tomax_end_index()instead ofdyadic_size()for ZKeccvm_flavor.hpp): Polynomial allocation with trace offset; Lagrange polys shiftedeccvm_prover.cpp): Removed tail batching andextend_with_tailcopies for translation polyssumcheck.hpp): Unified ZK/non-ZK virtual round paths; removedpadding_indicator_arrayfrom verifierrow_disabling_polynomial.hpp): Simplified to1 - ∏_{i≥2}(1-u_i)gemini_impl.hpp): Fold polynomials trackactual_sizefromA_0.end_index()shplemini.hpp): Removedpadding_indicator_arrayfromcompute_batch_opening_claimvm2/constraining/): Removedpadding_indicator_arrayfrom native and recursive AVM verifiers; addedTRACE_OFFSET = 0to AVM flavormerge_prover.cpp/hpp,merge_verifier.cpp/hpp): PrependsTRACE_OFFSETzeros to table polynomials; verifier accounts for offset in commitment checksgp_start/start_indexparametersdatabus.hpp):DEFAULT_VALUE = 0(point-at-infinity commitment matches default)Known Issues / Follow-ups