This report compares three signer implementations for secp256k1 operations:
Generated: 2025-11-29 (Updated after GLV endomorphism optimization) Platform: linux/amd64 CPU: AMD Ryzen 5 PRO 4650G with Radeon Graphics Go Version: go1.25.3
Key Optimizations:
- GLV scalar splitting reduces 256-bit to two 128-bit multiplications - Strauss algorithm with wNAF (windowed Non-Adjacent Form) representation - Precomputed tables for generator G and λ*G (32 entries each) - EcmultGenGLV: 2.7x faster than reference (122 → 45 µs) - Scalar multiplication: 17% faster with GLV + Strauss (121 → 101 µs)
- Precomputed TaggedHash prefixes for common BIP-340 tags - Eliminated unnecessary copies in field element operations - Pre-allocated group elements to reduce allocations
| Operation | P256K1Signer (Pure Go) | LibSecp256k1 (C) | Winner |
|---|---|---|---|
| Pubkey Derivation | 56 µs | 22 µs | LibSecp (2.5x faster) |
| Sign | 58 µs | 41 µs | LibSecp (1.4x faster) |
| Verify | 182 µs | 47 µs | LibSecp (3.9x faster) |
| ECDH | 119 µs | N/A | P256K1 |
| Operation | Time | Description |
|---|---|---|
| EcmultGenGLV | 45 µs | GLV-optimized generator multiplication |
| EcmultGenSimple | 68 µs | Precomputed table (no GLV) |
| EcmultGenConstRef | 122 µs | Reference implementation |
| EcmultStraussWNAFGLV | 101 µs | GLV + Strauss for arbitrary point |
| EcmultConst | 122 µs | Constant-time binary method |
The GLV (Gallant-Lambert-Vanstone) endomorphism exploits secp256k1's special structure where:
| Metric | Before GLV | After GLV | Improvement |
|---|---|---|---|
| Generator mult (EcmultGen) | 122 µs | 45 µs | 2.7x faster |
| Arbitrary point mult | 122 µs | 101 µs | 17% faster |
| Scalar split overhead | N/A | 0.2 µs | Negligible |
Deriving public key from private key (32 bytes → 32 bytes x-only pubkey).
| Implementation | Time per op | Notes |
|---|---|---|
| P256K1Signer | 56 µs | Pure Go with GLV optimization |
| LibSecp256k1 | 22 µs | Native C library via purego |
Creating BIP-340 Schnorr signatures (32-byte message → 64-byte signature).
| Implementation | Time per op | Notes |
|---|---|---|
| P256K1Signer | 58 µs | Pure Go with GLV |
| LibSecp256k1 | 41 µs | Native C library |
Verifying BIP-340 Schnorr signatures (32-byte message + 64-byte signature).
| Implementation | Time per op | Notes |
|---|---|---|
| P256K1Signer | 182 µs | Pure Go with GLV |
| LibSecp256k1 | 47 µs | Native C library (3.9x faster) |
Generating shared secret using Elliptic Curve Diffie-Hellman.
| Implementation | Time per op | Notes |
|---|---|---|
| P256K1Signer | 119 µs | Pure Go with GLV |
The native libsecp256k1 library maintains significant advantages due to:
However, the pure Go implementation with GLV is now competitive for many use cases.
The GLV endomorphism provides the most benefit for generator multiplication (used in signing):
Use LibSecp256k1 when:
Use P256K1Signer when:
The GLV endomorphism optimization significantly improves secp256k1 performance in pure Go:
While the native C library remains faster (especially for verification), the pure Go implementation is now much more competitive for signing operations where generator multiplication dominates.
To reproduce these benchmarks:
# Run all benchmarks
go test ./... -bench=. -benchmem -benchtime=2s
# Run specific scalar multiplication benchmarks
go test -bench='BenchmarkEcmultGen|BenchmarkEcmultStraussWNAFGLV' -benchtime=2s
# Run comparison benchmarks
go test ./bench -bench=. -benchtime=2s