Skip to content

chore!: masking at the top of the trace#22334

Open
iakovenkos wants to merge 53 commits intomerge-train/barretenbergfrom
si/eccvm-lagrange-last-integer
Open

chore!: masking at the top of the trace#22334
iakovenkos wants to merge 53 commits intomerge-train/barretenbergfrom
si/eccvm-lagrange-last-integer

Conversation

@iakovenkos
Copy link
Copy Markdown
Contributor

@iakovenkos iakovenkos commented Apr 6, 2026

Summary

Motivation

Previously, ZK masking used tail masking: random values were stored in separate MaskingTailData polynomials at the end of the trace. This forced all PCS code to be dyadic_size dependent — the batched polynomial A₀, Gemini folds, and Shplonk quotient all had to be allocated and operated at dyadic_size because 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 a padding_indicator_array to 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_first moves from row 0 to row TRACE_OFFSET (= 4).

Key Benefits

  • PCS decoupled from dyadic_size: With masking at low indices, committed polynomials only extend to max_end_index() (the actual trace extent), not dyadic_size. Gemini fold polynomials already track actual_size from A_0.end_index(). This enables future non-dyadic PCS proving where sparse circuits avoid paying for the full power-of-2 padding.
  • Circuit-size-independent row-disabling: The polynomial 1 - ∏_{i≥2}(1-u_i) depends only on sumcheck challenges, not circuit size. The padding_indicator_array is eliminated.
  • Simpler code: Removed MaskingTailData (218 lines), padding_indicator_array (93+134 lines), and all tail-batching logic. Net -707 lines across 103 files.
  • Unified verifier paths: Sumcheck and Shplemini verifiers no longer take padding_indicator_array parameters. This simplifies native, recursive, and AVM verifier flows alike.

Merge Protocol and ECC Op Wire Changes

Moving lagrange_first from row 0 to row TRACE_OFFSET (= 4) shifts the entire execution trace down by 4 rows. In Mega, the ecc_op block now starts at trace_offset() = TRACE_OFFSET + 1 = 5, and ecc_op_wire data sits one row before at row TRACE_OFFSET = 4 (to satisfy the ecc_op_wire[row] == w_shift[row] constraint).

The merge protocol must match this layout: it prepends TRACE_OFFSET zeros to its table polynomials (L, R, and M in PREPEND mode) so that the prover's Shplonk quotient is consistent with the ecc_op_wire commitments held by the verifier. Previously, these polynomials started at row 0; now they start at row TRACE_OFFSET to 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 adding TRACE_OFFSET = 0 to the standalone AVM flavor. The MegaAVM flavor inherits Mega's TRACE_OFFSET = 4, so ecc_op_wires in 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 infrastructure
  • stdlib/primitives/padding_indicator_array/ — virtual-round indicator for old row-disabling

Added

  • ultra_honk/zk_boundary.test.cpp — tests for masking layout, ecc_op alignment, row-disabling

Core changes

  • Prover instance (prover_instance.cpp): Polynomials allocated with add_masking() at rows {1,2,3}; lagrange_first at TRACE_OFFSET; trace blocks start at TRACE_OFFSET + 1
  • Oink prover (oink_prover.cpp): Removed all tail references; commitments use base polynomials directly
  • Ultra prover (ultra_prover.cpp): CRS sized to max_end_index() instead of dyadic_size() for ZK
  • ECCVM flavor (eccvm_flavor.hpp): Polynomial allocation with trace offset; Lagrange polys shifted
  • ECCVM prover (eccvm_prover.cpp): Removed tail batching and extend_with_tail copies for translation polys
  • Sumcheck (sumcheck.hpp): Unified ZK/non-ZK virtual round paths; removed padding_indicator_array from verifier
  • Row-disabling (row_disabling_polynomial.hpp): Simplified to 1 - ∏_{i≥2}(1-u_i)
  • Gemini (gemini_impl.hpp): Fold polynomials track actual_size from A_0.end_index()
  • Shplemini (shplemini.hpp): Removed padding_indicator_array from compute_batch_opening_claim
  • AVM (vm2/constraining/): Removed padding_indicator_array from native and recursive AVM verifiers; added TRACE_OFFSET = 0 to AVM flavor
  • Merge protocol (merge_prover.cpp/hpp, merge_verifier.cpp/hpp): Prepends TRACE_OFFSET zeros to table polynomials; verifier accounts for offset in commitment checks
  • Grand product / log-derivative: Start after disabled region via gp_start / start_index parameters
  • Databus (databus.hpp): DEFAULT_VALUE = 0 (point-at-infinity commitment matches default)

Known Issues / Follow-ups

  • The zk ultra prover efficiency needs to be investigated.

…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).
@iakovenkos iakovenkos self-assigned this Apr 9, 2026
@iakovenkos iakovenkos requested a review from suyash67 April 14, 2026 10:23
Comment thread barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp
Comment thread barretenberg/cpp/src/barretenberg/ultra_honk/README.md

/**
* @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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

random question (out of scope): is there advantage to doing this (i.e., to forcing ZK for ops to come from merge?)

@notnotraju
Copy link
Copy Markdown
Contributor

In constants.hpp, some of the comments are outdated precisely in view of this PR, see above and below NUM_MASKED_ROWS

Similarly, in eccvm_prover.cpp, I think the comments on masking (started with "This check gets trickier when the witness wires in ECCVM are masked") are stale.

@iakovenkos iakovenkos removed the ci-full Run all master checks. label Apr 14, 2026
@iakovenkos iakovenkos changed the title chore: masking at the top of the trace chore!: masking at the top of the trace 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;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ledwards2225 still feels a bit hacky but it allows to

  1. remove no ops from op queue/ mega and translator builders
  2. remove shifts from the eco op wire relation

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;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@iakovenkos iakovenkos added ci-full Run all master checks. ci-docs and removed ci-docs labels Apr 14, 2026
@iakovenkos iakovenkos removed the ci-full Run all master checks. label Apr 15, 2026
@iakovenkos iakovenkos added the ci-full Run all master checks. label Apr 15, 2026
@notnotraju notnotraju self-requested a review April 16, 2026 07:30
MegaExecutionTraceBlocks() = default;

void compute_offsets()
void compute_offsets(size_t trace_offset = TRACE_OFFSET)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at l. 305 in this file (meta_execution_trace.hpp), stale comment

@fcarreiro fcarreiro removed their request for review April 16, 2026 10:51
Comment thread barretenberg/cpp/src/barretenberg/constants.hpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-full Run all master checks.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants