Summary
I'd like to add an alternative 4x64-bit representation of the secp256k1 base
field element (FieldVal64) alongside the current 10x26-bit FieldVal,
selectable at build time. It comes with assembly fast paths for amd64
(BMI2/MULX + ADX) and arm64 (UMULH/MUL), and a portable Go fallback for
all other arches and purego builds. The existing default is unchanged.
I'm opening this before submitting a PR to get maintainer input on the
overall direction.
Motivation
On 64-bit hardware a tightly packed 256-bit representation with a Crandall
reduction performs fewer limb multiplications than the base-2^26 schedule.
In local benchmarks (ECDSA sign/verify, ECDH, pubkey parse/serialize) the
64-bit backend is consistently faster, including under emulation. I'll post
a benchstat comparison in a follow-up comment.
Proposed design
FieldVal64: four little-endian uint64 limbs, fully reduced after each op,
matching the existing FieldVal API surface.
- Constant-time throughout: no secret-dependent branches or memory accesses.
The final conditional subtraction uses CMOVcc (amd64) / CSEL (arm64) and
arithmetic masking in the portable path.
- Portable building blocks (512-bit multiply/square + Crandall reduce) used on
all platforms.
field64Mul / field64Square dispatch:
- arm64: dedicated asm
- amd64: MULX/ADX asm, runtime-gated on CPU support, portable fallback
- everything else / purego: portable Go
FieldVal type-aliased to FieldVal26 (default) or FieldVal64
(-tags fieldval64), so curve/ECDSA/Schnorr all run against the chosen type.
Testing
- Differential tests against a math/big oracle and against the portable path
(asm-vs-Go cross-check), plus edge cases and randomized inputs.
Open questions for maintainers
- Is a build-tag-selectable alternative backend acceptable, or would you
prefer a single implementation (e.g. eventually making 64-bit the default
on 64-bit targets)?
- CPU feature detection currently uses
golang.org/x/sys/cpu. This module
only depends on blake256 today; would you rather I avoid the new dependency
and detect BMI2/ADX inline?
- Any preference on how this is split for review?
Happy to adjust the approach based on your guidance before sending the PR.
Summary
I'd like to add an alternative 4x64-bit representation of the secp256k1 base
field element (
FieldVal64) alongside the current 10x26-bitFieldVal,selectable at build time. It comes with assembly fast paths for amd64
(BMI2/MULX + ADX) and arm64 (UMULH/MUL), and a portable Go fallback for
all other arches and
puregobuilds. The existing default is unchanged.I'm opening this before submitting a PR to get maintainer input on the
overall direction.
Motivation
On 64-bit hardware a tightly packed 256-bit representation with a Crandall
reduction performs fewer limb multiplications than the base-2^26 schedule.
In local benchmarks (ECDSA sign/verify, ECDH, pubkey parse/serialize) the
64-bit backend is consistently faster, including under emulation. I'll post
a benchstat comparison in a follow-up comment.
Proposed design
FieldVal64: four little-endian uint64 limbs, fully reduced after each op,matching the existing
FieldValAPI surface.The final conditional subtraction uses CMOVcc (amd64) / CSEL (arm64) and
arithmetic masking in the portable path.
all platforms.
field64Mul/field64Squaredispatch:FieldValtype-aliased toFieldVal26(default) orFieldVal64(
-tags fieldval64), so curve/ECDSA/Schnorr all run against the chosen type.Testing
(asm-vs-Go cross-check), plus edge cases and randomized inputs.
Open questions for maintainers
prefer a single implementation (e.g. eventually making 64-bit the default
on 64-bit targets)?
golang.org/x/sys/cpu. This moduleonly depends on blake256 today; would you rather I avoid the new dependency
and detect BMI2/ADX inline?
Happy to adjust the approach based on your guidance before sending the PR.