Skip to content

perf(zkatdlog): reduce CPU time and allocations in CSP range-proof crypto - #1997

Open
adecaro wants to merge 1 commit into
mainfrom
perf-csp-msm-and-batch-inversion
Open

perf(zkatdlog): reduce CPU time and allocations in CSP range-proof crypto#1997
adecaro wants to merge 1 commit into
mainfrom
perf-csp-msm-and-batch-inversion

Conversation

@adecaro

@adecaro adecaro commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #1994

Summary

Optimizes the Compressed Sigma Protocol (CSP) range-proof package
(crypto/rp/csp) with no change to its public API, wire format, or verification
semantics:

  • crypto/rp/csp/msm.go (new): dispatches small, fixed-size multi-scalar
    multiplications to direct Mul/Mul2 instead of gnark-crypto's
    MultiScalarMul. CSP's folding rounds shrink every vector down to length 2
    and then 1 in their last two rounds, and several call sites always operate
    on exactly 2 generators/scalars; MultiScalarMul's goroutine fan-out (sized
    for runtime.NumCPU()*2) costs more than it saves at n<=2. Benchmarked on
    BLS12-381 (M1 Max): direct Mul is ~2.5x faster than MultiScalarMul at
    n=1, Mul2 ~25-29% faster at n=2; from n=3 the two are at parity or
    MultiScalarMul wins, so smallMSM only special-cases n=1/n=2 and falls
    through to MultiScalarMul otherwise (strictly non-regressive). Applied at
    all 6 production MultiScalarMul call sites in csp.go/rp.go.

  • crypto/math/native.go: adds NativeBatchInverseInto, a batch
    field-inversion helper that writes into caller-supplied buffers instead of
    allocating fresh ones on every call.

  • crypto/rp/csp/lagrange_native.go: interpolateNative's outer loop
    previously allocated 5 fresh same-size slices/scalars per iteration.
    Rewritten to hoist every per-x buffer and scalar scratch outside the outer
    loop via NativeBatchInverseInto, so each is allocated once per call
    instead of once per iteration.

  • crypto/rp/csp/transcript.go: Transcript.Absorb is called many times per
    proving/verification round; reuses a scratch buffer across calls instead of
    growing a fresh slice on every Absorb.

Benchmarked on BenchmarkBFProver (n=32/n=64, BLS12_381_BBS_GURVY, count=8,
benchstat): combined effect of the MSM dispatch and batch-inversion changes
is roughly -6% ns/op and -35-40% allocs/op (geomean), each individually
significant at p<0.01. No public API, wire format, or verification behavior
changed.

Test plan

  • make checks
  • make lint-auto-fix
  • make unit-tests
  • Targeted: go test ./token/core/zkatdlog/nogh/v1/crypto/rp/csp/... ./token/core/zkatdlog/nogh/v1/crypto/math/... ./token/core/zkatdlog/nogh/v1/crypto/rp/bulletproof/...
  • benchstat before/after on BenchmarkBFProver, count=8

…ypto

Optimizes the Compressed Sigma Protocol (CSP) range-proof package
(crypto/rp/csp) with no change to its public API, wire format, or
verification semantics:

- crypto/rp/csp/msm.go (new): dispatches small, fixed-size multi-scalar
  multiplications to direct Mul/Mul2 instead of gnark-crypto's
  MultiScalarMul. CSP's folding rounds shrink every vector down to
  length 2 and then 1 in their last two rounds, and several call sites
  always operate on exactly 2 generators/scalars; MultiScalarMul's
  goroutine fan-out (sized for runtime.NumCPU()*2) costs more than it
  saves at n<=2. Benchmarked on BLS12-381 (M1 Max): direct Mul is ~2.5x
  faster than MultiScalarMul at n=1, Mul2 ~25-29% faster at n=2; from
  n=3 the two are at parity or MultiScalarMul wins, so smallMSM only
  special-cases n=1/n=2 and falls through to MultiScalarMul otherwise
  (strictly non-regressive). Applied at all 6 production MultiScalarMul
  call sites in csp.go/rp.go.

- crypto/math/native.go: adds NativeBatchInverseInto, a batch
  field-inversion helper that writes into caller-supplied buffers
  instead of allocating fresh ones on every call.

- crypto/rp/csp/lagrange_native.go: interpolateNative's outer loop
  previously allocated 5 fresh same-size slices/scalars per iteration
  (partly via NativeBatchInverse's internal allocations, partly its own
  per-iteration scratch). Rewritten to hoist every per-x buffer and
  scalar scratch outside the outer loop via NativeBatchInverseInto, so
  each is allocated once per call instead of once per iteration.

- crypto/rp/csp/transcript.go: Transcript.Absorb is called many times
  per proving/verification round; reuses a scratch buffer across calls
  instead of growing a fresh slice on every Absorb.

Benchmarked on BenchmarkBFProver (n=32/n=64, BLS12_381_BBS_GURVY,
count=8, benchstat): combined effect of the MSM dispatch and batch-
inversion changes is roughly -6% ns/op and -35-40% allocs/op (geomean),
each individually significant at p<0.01. No public API, wire format, or
verification behavior changed.

Signed-off-by: Angelo De Caro <adc@zurich.ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CSP range-proof crypto: reduce allocations and CPU time in small MSMs and Lagrange interpolation

2 participants