Skip to content

secp256k1: Implement 4x64 ModNScalar.#3731

Open
davecgh wants to merge 6 commits into
decred:masterfrom
davecgh:secp256k1_modnscalar_4x64
Open

secp256k1: Implement 4x64 ModNScalar.#3731
davecgh wants to merge 6 commits into
decred:masterfrom
davecgh:secp256k1_modnscalar_4x64

Conversation

@davecgh

@davecgh davecgh commented Jul 9, 2026

Copy link
Copy Markdown
Member

This requires #3730.

secp256k1: Implement 4x64 ModNScalar.

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 track and discard 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.

In order to further help ensure the implementation is correct, some additional test that exercise algebraic properties have been added in separate commits leading up to the main commit.

Finally, the tests have been updated 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)

@davecgh davecgh added this to the 2.2.0 milestone Jul 9, 2026
@davecgh davecgh force-pushed the secp256k1_modnscalar_4x64 branch from cd2b06c to 4284c3f Compare July 9, 2026 10:03
Comment thread dcrec/secp256k1/modnscalar.go Outdated
Comment thread dcrec/secp256k1/modnscalar.go Outdated
Comment thread dcrec/secp256k1/modnscalar.go Outdated
davecgh added 6 commits July 15, 2026 18:41
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 track
and discard 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 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 davecgh force-pushed the secp256k1_modnscalar_4x64 branch from 9a54281 to 7f2618f Compare July 15, 2026 23:41
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