secp256k1: Faster scalar mult via width-5 wNAF.#3740
Open
davecgh wants to merge 12 commits into
Open
Conversation
This corrects the referenced names in the proof in the comments of field64Reuce512 and tightens the bounds. While the bounds that were commented were sufficient to prove it is safe to discard the carry, using a tighter bound provides an even stronger proof.
This updates the README.md and doc.go files to include some important caveats about the constant time and memory clearing behavior. Specifically, while the behavior has been manually verified by carefully reviewing the generated assembly as of the most recent version of Go, it is impossible to guarantee the compiler will not change in the future.
This adds a new test helper for some algebraic properties of scalar negation and calls it from the test that handles specific edge cases as well as the randomized test.
This adds a new test helper for some algebraic properties of modular multiplicative inverses of scalars and calls it from the test that handles specific edge cases as well as the randomized test.
This adds a new test helper for some algebraic properties of the scalar half order check and calls it from the test that handles specific edge cases as well as the randomized test.
This significantly optimizes the ModNScalar implementation by rewriting it to use saturated 4x64 arithmetic similar to the new 4x64 field arithmetic. The new implementation retains all of the important properties such as being constant time and only using temporary buffers on the stack that are cleared. Of particular interest is the new modular reduction implementation. It moves away from the 96-bit accumulate design and instead manually tracks and discards carries inline which provides a major speed advantage. Due to the complexity and potential mistakes in the arithmetic, all of the intermediate bounds and carry assumptions documented that the code relies upon have been formally verified to be correct. The formal proof is in a separate commit. The implementation is primarily targeted at 64-bit since it is by far the most common architecture in modern use, however, as the included benchmarks below show, it is faster for almost all operations on 32-bit architectures as well. The few operations where it is a bit slower on 32-bit architectures are not used anywhere near as frequently as the ones that get 10-30% better performance, so the end result is the overall implementation is notably faster on 32 bit too. For 64-bit architectures, every operation is significantly faster. For example, all of the primary operations that get heavy use get ~73-94% better performance. Finally, the tests have been updated to use values specifically crafted to exercise the new limbs versus the old values that were crafted for the old limbs. Comparison for 32-bit platforms: $ benchstat beforescalar_x86.txt afterscalar_x86.txt name old time/op new time/op delta ----------------------------------------------------------------------------------- ModNScalar 22.9ns ± 4% 23.2ns ± 5% ~ (p=0.184 n=10+10) ModNScalarZero 1.36ns ± 3% 6.59ns ± 6% +385.95% (p=0.000 n=9+9) ModNScalarIsZero 0.32ns ± 4% 0.34ns ±14% ~ (p=0.143 n=10+10) ModNScalarEquals 3.69ns ± 4% 0.30ns ± 7% -91.78% (p=0.000 n=10+10) ModNScalarAdd 25.1ns ± 6% 22.5ns ± 5% -10.40% (p=0.000 n=9+10) ModNScalarMul 257ns ± 5% 223ns ± 5% -13.07% (p=0.000 n=10+10) ModNScalarSquare 267ns ± 5% 172ns ± 6% -35.56% (p=0.000 n=10+10) ModNScalarNegate 12.8ns ± 9% 14.5ns ± 2% +13.39% (p=0.000 n=10+10) ModNScalarInverse 686ns ± 7% 623ns ± 3% -9.24% (p=0.000 n=10+9) ModNScalarIsOverHalfOrder 8.92ns ± 6% 6.55ns ± 3% -26.59% (p=0.000 n=10+10) Comparison for 64-bit platforms: $ benchstat beforescalar_x64.txt afterscalar_x64.txt name old time/op new time/op delta ModNScalar 12.1ns ± 1% 3.5ns ± 4% -71.25% (p=0.000 n=7+10) ModNScalarZero 0.64ns ± 9% 0.61ns ± 5% -4.08% (p=0.043 n=10+10) ModNScalarIsZero 0.31ns ± 8% 0.31ns ± 6% ~ (p=0.000 n=10+10) ModNScalarEquals 2.63ns ± 5% 0.33ns ± 9% -87.47% (p=0.000 n=10+10) ModNScalarAdd 15.1ns ± 4% 4.1ns ± 4% -73.25% (p=0.000 n=10+10) ModNScalarMul 214ns ± 6% 29ns ± 6% -86.30% (p=0.000 n=10+10) ModNScalarSquare 215ns ± 4% 23ns ± 6% -89.39% (p=0.000 n=10+10) ModNScalarNegate 5.78ns ± 4% 2.99ns ±10% -48.33% (p=0.000 n=10+10) ModNScalarInverse 452ns ± 4% 408ns ± 6% -9.89% (p=0.000 n=10+10) ModNScalarIsOverHalfOrder 5.52ns ± 6% 0.29ns ± 5% -94.66% (p=0.000 n=10+9)
This adds a formal verification proof of the scalar reduction arithmetic in the new 4x64 scalar implementation and updates the proofs README.md accordingly.. It includes a formal proof for the following: - 512-bit modular reduction over the group order with saturated 64-bit limbs
davecgh
commented
Jul 20, 2026
With the ultimate goal of optimizing scalar multiplication by reducing the average number of point additions, this commit adds code to convert a balanced scalar representative into width-5 windowed non-adjacent form (wNAF). The current scalar multiplication implementation uses ordinary non-adjacent form (NAF), which corresponds to a window width of 2. This commit introduces only the width-5 wNAF recoder and does not yet integrate it into scalar multiplication. Future commits will add comprehensive tests and integrate the new implementation.
This adds comprehensive tests for the new wNAF implementation to help verify correctness. The tests assert the resulting encoding satisfies the required mathematical properties and reconstructs the original value for both deterministic edge cases and randomized balanced scalar representatives. The deterministic edge cases exercise enough small values to exhaustively test every residue modulo 2^w multiple times, and include additional edge cases such as carries introduced by the recoding, maximum values, and alternating bit patterns.
This updates the non-constant-time scalar multiplication implementation to use the new width-5 wNAF recoding. In particular, because wNAF requires precomputed odd multiples that were not needed by the previous implementation, this introduces a new dedicated precomputed table type along with a new helper to construct the required multiples. The previous implementation only needed to swap between two precomputed points depending on the scalar signs. However, since the new wNAF implementation uses larger precomputed tables, it instead tracks whether each decomposed scalar was negated and applies the corresponding sign adjustment to the appropriate half of each table. The following benchmark shows a before and after comparison of scalar multiplication: name old time/op new time/op delta --------------------------------------------------------------------------- ScalarMultNonConst 94.4µs ± 1% 84.3µs ± 1% -10.75% (p=0.000 n=10+10)
This removes the old ordinary NAF implementation that is no longer used now that the implementation has been updated to use a new width-5 wNAF recoder instead. It also removes the associated tests and benchmark accordingly.
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.
This requires #3731.
This improves the performance of the non-constant-time scalar multiplication implementation via width-5 windowed non-adjacent form (wNAF) recoding.
It introduces an optimized width-5 recoder together with precomputed odd-multiple tables for both P and φ(P) which allows the existing endomorphism-based simultaneous multiplication to replace the previous width-2 NAF representation with the sparser width-5 wNAF representation.
On average, width-5 wNAF reduces the density of non-zero digits from approximately one in three to one in six which significantly reduces the number of point additions required during scalar multiplication at the cost of a small amount of additional precomputation.
Comprehensive tests are included to verify the mathematical properties of the resulting encodings, reconstruct the original values, and cover both deterministic edge cases and randomized balanced scalars. A benchmark for the recoder is also added.
Finally, it removes the old ordinary width-2 NAF code and associated tests and benchmark since it is no longer used.
It is implemented via a series of commits to ease the review process. See the individual commit descriptions for more details.
The following benchmark shows a before and after comparison: