Skip to content

feat: batch field inversions in the Solidity Honk verifier#24080

Open
0xedwen wants to merge 1 commit into
AztecProtocol:nextfrom
0xedwen:perf/honk-sol-batch-invert
Open

feat: batch field inversions in the Solidity Honk verifier#24080
0xedwen wants to merge 1 commit into
AztecProtocol:nextfrom
0xedwen:perf/honk-sol-batch-invert

Conversation

@0xedwen

@0xedwen 0xedwen commented Jun 15, 2026

Copy link
Copy Markdown

Replaces clusters of individual .invert() calls (each hitting the MODEXP precompile) with Montgomery batch inversion—one MODEXP per batch, with linear mulmod operations for the remaining work. Includes several additional optimizations in the same code paths.

Changes

FrLib.batchInvert()

Introduces a new library function implementing the standard prefix-product batch inversion technique.

  • Uses a single MODEXP call to invert an entire array.
  • Reverts with InvertOfZero if any element is zero, matching the behavior of individual .invert() calls.

Sumcheck barycentric evaluation

Each round now:

  • Collects all denominators and batch-inverts them once.
  • Computes (roundChallenge - i) a single time per iteration and reuses it for both the numerator product and denominator.

Gemini folding

  • All logSize denominators are precomputed and batch-inverted before entering the fold loop.
  • Denominators are independent of the running accumulator, making them suitable for batching.

Shplonk denominators

  • Collects all denominators upfront:
    • 2 * LOG_N + 1 values in non-ZK mode.
    • 2 * LOG_N + 2 values in ZK mode (including the Libra denominator).
  • Performs a single batch inversion instead of individual inversions during scalar construction.

ZK-specific optimizations

Additional improvements for the ZK verifier:

  • Replaces 256⁻¹ mod p with a precomputed constant.
  • Computes geminiR^256 using 8 squarings instead of pow().
  • Batch-inverts all 256 checkEvalsConsistency denominators.

These changes account for most of the observed ZK gas reduction.

MSM seeding

Since shplonkQ always has scalar 1:

  • The assembly MSM loop now seeds the accumulator directly.
  • Avoids an unnecessary ecMul.
  • On-curve validation remains enforced by the first ecAdd in the loop.

Results

Median verify() gas usage via forge test --gas-report:

Verifier log_n Baseline Optimized Δ %
Add2Honk 5 877,987 829,127 −48,860 −5.6%
Add2HonkZK 5 1,785,462 1,458,842 −326,620 −18.3%
BlakeHonk 15 1,411,841 1,267,685 −144,156 −10.2%
BlakeHonkZK 15 2,400,105 1,965,564 −434,541 −18.1%
EcdsaHonk 16 1,468,213 1,314,333 −153,880 −10.5%
EcdsaHonkZK 16 2,464,484 2,018,976 −445,508 −18.1%
RecursiveHonk 20 1,692,908 1,499,709 −193,199 −11.4%
RecursiveHonkZK 20 2,721,626 2,231,521 −490,105 −18.0%
BlakeOpt / BlakeOptZK 570,264 / 686,231 identical 0 0%

Summary

  • Non-ZK: ~10% gas reduction, increasing with log_n.
  • ZK: ~18% gas reduction across all circuit sizes, driven primarily by batching the checkEvalsConsistency denominators (256 MODEXP calls → 1).
  • The hand-optimized assembly verifier is unchanged.

Tests

  • Added unit and fuzz tests for batchInvert in FrLib.t.sol.
  • Added test_OffCurve_ShplonkQ to verify that MSM seeding does not weaken off-curve validation for shplonkQ.
  • Mirrored all changes into honk_contract.hpp and honk_zk_contract.hpp via copy_to_cpp.sh.

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