Skip to content

Batch randomizers fix - #103

Merged
davxy merged 8 commits into
masterfrom
davxy/batch-randomizers
Oct 23, 2026
Merged

davxy merged 8 commits into
masterfrom
davxy/batch-randomizers

Conversation

@davxy

@davxy davxy commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Fixes paritytech/eng_findings#43.

BatchVerifier derived the KZG randomizer of each proof from that proof alone, so a prover who controls the whole batch could pick invalid proofs whose randomized errors cancel (Wagner k-sum, see paritytech/eng_findsings#43).

Now the batch transcript absorbs the entropy of every pushed proof and all randomizers are drawn from the final state at verify, so each depends on the whole batch.

Bumps both crates to 0.0.11.

@davxy
davxy requested review from drskalman and swasilyev October 22, 2026 11:45
@davxy
davxy marked this pull request as ready for review October 22, 2026 11:48
@davxy
davxy requested a review from burdges October 22, 2026 12:13
self.acc_scalars[0] -= claim.value * r;
self.kzg_proofs.push(claim.proof);
self.randomizers.push(r);
self.randomizers.push(r2);

@burdges burdges Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It pushed two randomizers before but now only pushes one? What else changed in the logic?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Was r2 the weak randomizer for the batch before?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

no both r1 and r2 were weak. It was just that accumulator was dealing with evalutating at zeta and zeta * omega separately (so we had r1 as random coefficient of the evalution at zeta and r2 for zeta * omega. Now @davxy has written generic code to deal with just as a generic point, so the function is called twice.

@burdges

burdges commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Alright the core bug fix in w3f-ring-proof/src/multi_ring_batch_verifier.rs‎ looks okay to me. self.transcript is the transcript of the batch verifier, independent of the transcripts for each individual proof. It looks fine to me, although I've not yet pulled up the Openings::new logic side by side with the original, so I'll get back to this after lunch.

@drskalman drskalman left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Mostly opiniated nits but I think the namings are genuinely confusing.

A less intrusive fix would have been that each r depends on all r submitted before, would have been a two line change, I know @burdges thinks it is not secure but vanilla Wagner doesn't apply to it I think at least.

Comment thread w3f-plonk-common/src/kzg_acc.rs Outdated
// at the cost of linear accumulator size.

// `sum(coeffs[i] * commitments[i])` opens to `value` at `at`.
struct OpeningClaim<E: Pairing> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: name is misleading, because this is an Opening, not a Claim: ClaimOpening, PointOpening?

Comment thread w3f-plonk-common/src/kzg_acc.rs Outdated

/// The two KZG opening claims of a Plonk proof, flattened for the final MSMs.
/// The batch randomizer is not applied yet.
pub struct Openings<E: Pairing> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: This is hard code two point openings, I think the name should indicate that (like the similar object we have for generic Pcs).

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.

We've similar structs at

pub struct PcsOpeningAt2Points<F: PrimeField, C: Commitment<F>> {
(or also
pub struct PcsOpeningAt2Points<F: PrimeField> {
), but that can be refactored later.

Comment thread w3f-plonk-common/src/kzg_acc.rs Outdated
.zip(challenges.nus.iter())
.map(|(y, r)| y * r)
.sum();
pub fn accumulate(&mut self, openings: &Openings<E>, r: E::ScalarField) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Isn't it better that user force to hand in the transcript/fs object to the accumulator so they don't mess up with the randmoness per proof somehow (I think we had that bug before that we submited that r didn't depened on the proof). Or if you don't want to do that, maybe leave a comment here on what r is acceptable so poeple won't abuse it in future again.

Comment thread w3f-plonk-common/src/kzg_acc.rs Outdated
.map(|(y, r)| y * r)
.sum();
pub fn accumulate(&mut self, openings: &Openings<E>, r: E::ScalarField) {
self.accumulate_claim(&openings.at_zeta, r);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

same nit here maybe accoumulate_opening_point ?

@davxy
davxy merged commit 1afd867 into master Oct 23, 2026
7 checks passed
@davxy
davxy deleted the davxy/batch-randomizers branch October 23, 2026 14:01
@davxy

davxy commented Oct 23, 2026

Copy link
Copy Markdown
Member Author

Thanks for the reviews. I've merged it as-is since it's needed ASAP, good enough for now, and I'm actually OOO sick 🤒. We can always refactor and improve it later.

@burdges

burdges commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

A less intrusive fix would have been that each r depends on all r submitted before, would have been a two line change, I know @burdges thinks it is not secure but vanilla Wagner doesn't apply to it I think at least.

It impacts how big you choose r. We typically have only 128 bit r when doing this, because that's a bit faster in the MSM. Here, the r have 256 bits, so you could get away with more. We do not afaik pay any significant price for those 256 bit r here, since we've stacked on a few layers of randomizers, but that seems kinda unique to plonk or similar RO snarks.

I'd probably have done this using some more generic code, which would've used 128 bit r because that'd make a difference in other protocols. In principle, you could accept an external Fiat-Shamir seed, like the block hash. We do not expose that for the runtime however, so that'd be a much bigger change. Another thing you can do is accumulate the hash of all the tx, place that into the finished batched type that you serialize and put into the block, and then verify it when checking the block. This risks some complexity when folks want to do bad stuff to blocks, like remove tx later or whatever.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants