Skip to content

fix(algebra): return the point itself when folding a single point - #1842

Open
0xShadowX wants to merge 3 commits into
Consensys-Incorporated:masterfrom
0xShadowX:fix/msm-folding-single-point
Open

0xShadowX wants to merge 3 commits into
Consensys-Incorporated:masterfrom
0xShadowX:fix/msm-folding-single-point

Conversation

@0xShadowX

@0xShadowX 0xShadowX commented Sep 27, 2026 •

Copy link
Copy Markdown

MultiScalarMul with algopts.WithFoldingScalarMul() returns P0 + [gamma]P0 instead of P0 when it is called with a single point:

res, _ := cr.MultiScalarMul([]*AffinePoint[T]{p0}, []*emulated.Element[S]{gamma}, algopts.WithFoldingScalarMul())
// expected: p0 (gamma^0 * p0), got: p0 + gamma*p0

The folding branch computes sum gamma^i * P[i] Horner-style: it starts with [gamma]P[n-1], folds P[n-2]..P[1] in the loop, then adds P[0]. For n == 1 the loop body never runs, so P[0] is both the starting point and the final addend. For n >= 2 the result is correct.

The same code is in four places, all fixed the same way (early return of the single point):

  • std/algebra/emulated/sw_emulated (Curve.MultiScalarMul)
  • std/algebra/emulated/sw_bls12381 (G2.MultiScalarMul)
  • std/algebra/native/sw_bls12377 (Curve.MultiScalarMul)
  • std/algebra/native/sw_grumpkin (Curve.MultiScalarMul)

Tests:

go test -count=1 -run TestMultiScalarMulFoldedSinglePoint ./std/algebra/emulated/sw_emulated/ ./std/algebra/emulated/sw_bls12381/ ./std/algebra/native/sw_bls12377/ ./std/algebra/native/sw_grumpkin/
go test -short -run 'MultiScalar|Folded|MSM' ./std/algebra/...
go test -short ./internal/stats/ ./std/commitments/kzg/

TestMultiScalarMulFoldedSinglePoint is added in each package and fails on master in all four; existing MSM tests and stats are unchanged.

  • regression test in each affected package
  • no generated files touched

Note

Medium Risk
Correctness fix in folded multi-scalar multiplication used in ZK circuits; wrong results could affect proofs that call MSM with one point, but the change is a small edge-case guard with per-package regression tests.

Overview
Fixes MultiScalarMul with algopts.WithFoldingScalarMul() when there is only one point: the Horner-style folding path used to return P₀ + [γ]P₀ instead of γ⁰·P₀ = P₀, because the same point was both the initial [γ]P accumulator and the final addend.

The folding branch now early-returns p[0] when len(p) == 1 in all four curve wrappers: emulated sw_emulated and sw_bls12381 G2, and native sw_bls12377 and sw_grumpkin.

TestMultiScalarMulFoldedSinglePoint is added in each package to lock in the single-point behavior via test.IsSolved.

Reviewed by Cursor Bugbot for commit be283bf. Bugbot is set up for automated code reviews on this repo. Configure here.

The folding branch starts with gamma*P[n-1], folds P[n-2]..P[1] in the
loop and adds P[0] at the end. With one point the loop doesn't run and the
result is P[0] + gamma*P[0] instead of P[0]. Return P[0] directly in that
case, in the four implementations that support folding.
@0xShadowX
0xShadowX requested a review from a team as a code owner September 27, 2026 22:31
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.

1 participant