Skip to content

chore: use free witness for offset generator in batch_mul#21040

Merged
suyash67 merged 5 commits into
merge-train/barretenbergfrom
sb/off-generator
Mar 10, 2026
Merged

chore: use free witness for offset generator in batch_mul#21040
suyash67 merged 5 commits into
merge-train/barretenbergfrom
sb/off-generator

Conversation

@suyash67

@suyash67 suyash67 commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

resolves AztecProtocol/barretenberg#1585

tldr: we make the offset generator a free witness while performing batch_mul

Current Way of Masking Points

While performing batch_mul we create ROM tables of linear combinations of the group elements. If we have an MSM of the form $[(s_1, s_2, \dots, s_n), (G_1, G_2, \dots, G_n)]$ then the ROM tables are of the form:

Index Element
0 $G_1 + G_2 + \dots + G_5 + G_6$
1 $G_1 + G_2 + \dots + G_5 - G_6$
2 $G_1 + G_2 + \dots - G_5 - G_6$
$\vdots$ $\vdots$
$2^6 - 1$ $- G_1 - G_2 - \dots - G_5 - G_6$

To avoid any entries of the ROM table to be a point at infinity, we add a multiple of an offset generator:

$G'_i := G_i + 2^{i-1}\delta \cdot G_{\textsf{offset}} \quad \forall i \in [0, 6).$

and then we create a ROM table with $(G'_1, G'_2, \dots, G'_6)$. Here, $\delta$ is a verifier-sent challenge so the prover cannot know it beforehand.

Proposed Masking of Points

Instead of sampling a challenge $\delta$, we make the offset generator $G_{\textsf{offset}}$ a free witness in the circuit, so the masked generators become:

$G'_i := G_i + 2^{i-1}\cdot G_{\textsf{offset}} \quad \forall i \in [0, 6).$

We only constrain $G_{\textsf{offset}}$ to be a valid point on the curve. This approach should also be safe because if a malicious prover tries to exploit the fact that he can freely choose $G_{\textsf{offset}}$ the circuit will be unsatisfiable. An honest prover must choose a random point on the curve as $G_{\textsf{offset}}$.

@suyash67 suyash67 added ci-full Run all master checks. ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure labels Mar 3, 2026
// ========================================

inline constexpr size_t ROOT_ROLLUP_GATE_COUNT = 12998771;
inline constexpr size_t ROOT_ROLLUP_GATE_COUNT = 12933470;

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.

The gate count reduction is because we do not sample the extra challenge $\delta$ and we do not compute the scalar multiplication $(\delta \cdot G_{\textsf{offset}})$.

// Sample a random curve point as the offset generator (free witness).
// The prover provides this point: the circuit only constrains it to lie on the curve.
C* builder = validate_context<C>(validate_context<C>(_points), validate_context<C>(_scalars));
const typename G::affine_element native_offset_generator = typename G::affine_element(G::element::random_element());

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.

This is the main change. Previously we did the following:

$G_{\textsf{masking}} = \delta \cdot G_{\textsf{offset}}$

Now we make $G_{\textsf{masking}}$ a free witness and it must be a valid curve point.

@ledwards2225 ledwards2225 left a comment

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.

Makes sense to me. Nice that it amounts to cleanup in a few places

// choose it freely because it must satisfy the batched opening equation.
if constexpr (Curve::is_stdlib_type) {
const auto challenge_tag = masking_challenge.get_origin_tag();
const auto challenge_tag = batch_opening_claim.evaluation_point.get_origin_tag();

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.

maybe I just don't remember the tag stuff well, but confused by this line - I'd expect us to just not have this challenge_tag now that we don't have the challenge? Why is it set to this evaluation_point.get_origin_tag?

@ledwards2225 ledwards2225 Mar 6, 2026

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.

A little while ago we updated the tag mechanism to alert us to combinations like A*\alpha + B, where e.g. A, B are commitments, \alpha a hash that accounts only for A. (This for example would have caught the bug we had in the PG verifier where we converted a bunch of size 2 MSMs into one larger one without proper fiat shamir). But this can give false positives since sometimes B is constrained by some other aspect of the protocol, as is the case here. Here W acts like B (really its more like A + B*\alpha) but it's constrained by the pairing to be correct so the prover isn't free to tamper with it. Setting the tag of W to match that of the challenge z is just the way we tell the tool not to complain. This is what the comment // OriginTag false positive was trying to explain but it could be clearer

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've added this in the comment, thanks for explaining Luke!

@suyash67 suyash67 merged commit 7276f34 into merge-train/barretenberg Mar 10, 2026
10 checks passed
@suyash67 suyash67 deleted the sb/off-generator branch March 10, 2026 16:03
github-merge-queue Bot pushed a commit that referenced this pull request Mar 11, 2026
BEGIN_COMMIT_OVERRIDE
chore: use free witness for offset generator in `batch_mul` (#21040)
chore: Stdlib curves audit (#21177)
END_COMMIT_OVERRIDE
AztecBot added a commit that referenced this pull request Mar 11, 2026
…pected manifest

The KZG opening protocol no longer sends a masking_challenge (removed in
#21040). The ProverManifestConsistency test's expected manifest was not
updated, causing a mismatch in round 26.
iakovenkos pushed a commit that referenced this pull request Mar 11, 2026
…pected manifest (#21371)

## Summary
The `BatchedHonkTranslatorTests.ProverManifestConsistency` test was
failing because its hardcoded expected manifest included a
`KZG:masking_challenge` entry in round 26 (the KZG opening round), but
the KZG protocol no longer sends this challenge (removed in #21040).

## Fix
Removed the stale `m.add_challenge(round, "KZG:masking_challenge")` from
`build_expected_batched_manifest()` and updated the corresponding
comment.

## Test plan
- [x] `BatchedHonkTranslatorTests.ProverManifestConsistency` passes
- [x] All 4 `batched_honk_translator_tests` pass locally

ClaudeBox log: https://claudebox.work/s/a26a429f5a7f3d3d?run=1
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. ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants