Repository navigation
Cheaper loop steps and MSM windows: −6% cost, −1.3 kB - #7
Merged
Merged
Conversation
Every node that passes an environment on costs its width, so the step functions now get only what they read: - the 25 values computed in the setup are split into those a step function reads (12 for every step, 7 more for a committed one) and those only the additions' arguments and the end read; the loop passes the second group around the function call; - z^5, z^7 and r^3, read only at the end, are kept beside the loop instead of in its state, and -B and -pi^2(B) are computed where they are used. An MSM window projects its 8 bits from the scalar as one chunk and splits them with a shared function, BITS8: 57 bytes of code per window instead of 107. The Horner steps of a(t) add the coefficient after the reduction instead of as a product with 1. Regtest spend: 68.1 kWU -> 66.8 kWU; cost 65.5 M -> 61.5 M mWU. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mjthatch
force-pushed
the
research/torus
branch
from
October 15, 2026 11:27
08f6fc0 to
e33f483
Compare
mjthatch
force-pushed
the
research/cheaper-steps
branch
from
October 15, 2026 11:27
ad89659 to
099dd7e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on the torus PR.
Summary
The step functions now get only the values they read, and the MSM windows share their bit-splitting code. Nothing about what is checked changes.
The spend's weight is still set by its bytes. So the cost saving shows up as slack, room that a future change can spend to save bytes. The bytes come from the MSM change.
How it works
Changes measured one after another on the whole verifier (
tests/test_groth16.py):The last three rows change the program by −3 B together.
Why the environment matters. Two Simplicity costs depend on width:
idencosts 100 plus the width of its input;compcosts 100 plus the width of the value it passes on.So every node that hands the environment along pays for all of it. Before, a step function's environment held the whole loop state, including all 30 run values. These are values computed once in the setup: the powers of z, the points at z, a_c(z) ± z, r, r² and r³. That is 7,680 bits, paid for at every node that passes the environment on, in every one of the 88 steps.
The run values now come in three groups:
RUN_ANY(12)RUN_COMMIT(7)RUN_REST(6)How each group reaches the steps:
((core, h), ((RUN_ANY, RUN_COMMIT), RUN_REST)).RUN_ANYas one projection. A committed step gets both of the first two groups, also as one projection.RUN_RESTback beside the result. That costs apairand a projection per step, about 3.6 B each time, which is the +315 B.Some values are not carried through the loop at all:
MSM windows. Each window used to extract its 8 bits one at a time from the scalar, each bit through its own projection path, and then pair them into the case tree's input. Now:
BITS8, a function defined once that turns a byte into the case tree's input(b₇, (b₆, … (b₀, 1))).A window now has 57 bytes of code of its own instead of 107, which is 1.6 kB over the 32 windows. The case tree, the selected entry and the 8 pruned branches per window are unchanged.
a(t) by Horner's rule. A term a_k·1 inside a dot product costs two multiplication jets and two additions. Added after the reduction instead, it costs one addition. Where this applies:
EVAL: a_k < 2²⁵⁴, so the running value stays below 3.1 p, well under p_s (5.3 p).Where it doesn't:
Where it stands
Most of what is left is data: key constants, table lookups, and the values the identity needs. Here is how I checked whether the 4.7 M slack can buy bytes:
Changes
groth/pairing.py:RUN_ANY,RUN_COMMIT,RUN_REST;ENDgets z⁵, z⁷, r³;DERIVED(−YB, ω·XB) is computed instep_value;state_shapeandrun_shapegive each step function its own shape;rd_plus;EVAL.groth/curve.py:bits_fn(BITS8);select_pointprojects an aligned chunk when the width is a power of two.examples/multiplier/(new regtest spend).Testing
hand's.tools/regtest.sh:9e3770ac…: 66,775 WU, vsize 16,694;Assertion failed inside jet);116f7d63…(the example files): 66,779 WU;cd7a7455….🤖 Generated with Claude Code