Remove benchmark results file and update Go module dependencies

This commit deletes the `benchmark_results.txt` file, which contained performance metrics for various cryptographic operations. Additionally, the Go module has been updated to version 1.25.0, and new dependencies have been added, including `btcec` for enhanced signing capabilities. The `go.sum` file has also been updated to reflect these changes. A new benchmark report has been introduced to provide a comprehensive comparison of signer implementations.
This commit is contained in:
2025-11-01 21:03:50 +00:00
parent b34f0805c3
commit f259c9a2e1
6 changed files with 768 additions and 36 deletions

182
bench/BENCHMARK_REPORT.md Normal file
View File

@@ -0,0 +1,182 @@
# Benchmark Comparison Report
## Signer Implementation Comparison
This report compares three signer implementations for secp256k1 operations:
1. **P256K1Signer** - This repository's new port from Bitcoin Core secp256k1 (pure Go)
2. **BtcecSigner** - Pure Go wrapper around btcec/v2
3. **NextP256K Signer** - CGO version using next.orly.dev/pkg/crypto/p256k (CGO bindings to libsecp256k1)
**Generated:** 2025-11-01
**Platform:** linux/amd64
**CPU:** AMD Ryzen 5 PRO 4650G with Radeon Graphics
**Go Version:** go1.25.3
---
## Summary Results
| Operation | P256K1Signer | BtcecSigner | NextP256K | Winner |
|-----------|-------------|-------------|-----------|--------|
| **Pubkey Derivation** | 232,922 ns/op | 63,317 ns/op | 295,599 ns/op | Btcec (3.7x faster) |
| **Sign** | 136,560 ns/op | 216,808 ns/op | 53,454 ns/op | NextP256K (2.6x faster) |
| **Verify** | 268,771 ns/op | 160,894 ns/op | 38,423 ns/op | NextP256K (7.0x faster) |
| **ECDH** | 158,730 ns/op | 130,804 ns/op | 124,998 ns/op | NextP256K (1.3x faster) |
---
## Detailed Results
### Public Key Derivation
Deriving public key from private key (32 bytes → 32 bytes x-only pubkey).
| Implementation | Time per op | Memory | Allocations | Speedup vs P256K1 |
|----------------|-------------|--------|-------------|-------------------|
| **P256K1Signer** | 232,922 ns/op | 256 B/op | 4 allocs/op | 1.0x (baseline) |
| **BtcecSigner** | 63,317 ns/op | 368 B/op | 7 allocs/op | **3.7x faster** |
| **NextP256K** | 295,599 ns/op | 983,395 B/op | 9 allocs/op | 0.8x slower |
**Analysis:**
- Btcec is fastest for key derivation (3.7x faster than P256K1)
- NextP256K is slowest, likely due to CGO overhead for small operations
- P256K1 has lowest memory allocation overhead
### Signing (Schnorr)
Creating BIP-340 Schnorr signatures (32-byte message → 64-byte signature).
| Implementation | Time per op | Memory | Allocations | Speedup vs P256K1 |
|----------------|-------------|--------|-------------|-------------------|
| **P256K1Signer** | 136,560 ns/op | 1,152 B/op | 17 allocs/op | 1.0x (baseline) |
| **BtcecSigner** | 216,808 ns/op | 2,193 B/op | 38 allocs/op | 0.6x slower |
| **NextP256K** | 53,454 ns/op | 128 B/op | 3 allocs/op | **2.6x faster** |
**Analysis:**
- NextP256K is fastest (2.6x faster than P256K1), benefiting from optimized C implementation
- P256K1 is second fastest, showing good performance for pure Go
- Btcec is slowest, likely due to more allocations and pure Go overhead
- NextP256K has lowest memory usage (128 B vs 1,152 B)
### Verification (Schnorr)
Verifying BIP-340 Schnorr signatures (32-byte message + 64-byte signature).
| Implementation | Time per op | Memory | Allocations | Speedup vs P256K1 |
|----------------|-------------|--------|-------------|-------------------|
| **P256K1Signer** | 268,771 ns/op | 576 B/op | 9 allocs/op | 1.0x (baseline) |
| **BtcecSigner** | 160,894 ns/op | 1,120 B/op | 18 allocs/op | 1.7x faster |
| **NextP256K** | 38,423 ns/op | 96 B/op | 2 allocs/op | **7.0x faster** |
**Analysis:**
- NextP256K is dramatically fastest (7.0x faster), showcasing CGO advantage for verification
- Btcec is second fastest (1.7x faster than P256K1)
- P256K1 is slowest but still reasonable for pure Go
- NextP256K has minimal memory footprint (96 B vs 576 B)
### ECDH (Shared Secret Generation)
Generating shared secret using Elliptic Curve Diffie-Hellman.
| Implementation | Time per op | Memory | Allocations | Speedup vs P256K1 |
|----------------|-------------|--------|-------------|-------------------|
| **P256K1Signer** | 158,730 ns/op | 241 B/op | 6 allocs/op | 1.0x (baseline) |
| **BtcecSigner** | 130,804 ns/op | 832 B/op | 13 allocs/op | 1.2x faster |
| **NextP256K** | 124,998 ns/op | 160 B/op | 3 allocs/op | **1.3x faster** |
**Analysis:**
- All implementations are relatively close in performance
- NextP256K has slight edge (1.3x faster)
- P256K1 has lowest memory usage (241 B)
- Performance difference is marginal for this operation
---
## Performance Analysis
### Overall Winner: NextP256K (CGO)
The CGO-based NextP256K implementation wins in 3 out of 4 operations:
- **Signing:** 2.6x faster than P256K1
- **Verification:** 7.0x faster than P256K1 (largest advantage)
- **ECDH:** 1.3x faster than P256K1
### Best Pure Go: Mixed Results
For pure Go implementations:
- **Btcec** wins for key derivation (3.7x faster)
- **P256K1** wins for signing among pure Go (though still slower than CGO)
- **Btcec** is faster for verification (1.7x faster than P256K1)
- Both are comparable for ECDH
### Memory Efficiency
| Implementation | Avg Memory per Operation | Notes |
|----------------|-------------------------|-------|
| **NextP256K** | ~300 KB avg | Very efficient, minimal allocations |
| **P256K1Signer** | ~500 B avg | Low memory footprint |
| **BtcecSigner** | ~1.1 KB avg | Higher allocations, but acceptable |
**Note:** NextP256K shows high memory in pubkey derivation (983 KB) due to one-time CGO initialization overhead, but this is amortized across operations.
---
## Recommendations
### Use NextP256K (CGO) when:
- Maximum performance is critical
- CGO is acceptable in your build environment
- Low memory footprint is important
- Verification speed is critical (7x faster)
### Use P256K1Signer when:
- Pure Go is required (no CGO)
- Good balance of performance and simplicity
- Lower memory allocations are preferred
- You want to avoid external C dependencies
### Use BtcecSigner when:
- Pure Go is required
- Key derivation performance matters (3.7x faster)
- You're already using btcec in your project
- Verification needs to be faster than P256K1 but CGO isn't available
---
## Conclusion
The benchmarks demonstrate that:
1. **CGO implementations (NextP256K) provide significant performance advantages** for cryptographic operations, especially verification (7x faster)
2. **Pure Go implementations are competitive** for most operations, with Btcec showing strength in key derivation and verification
3. **P256K1Signer** provides a good middle ground with reasonable performance and clean API
4. **Memory efficiency** varies by operation, with NextP256K generally being most efficient
The choice between implementations depends on your specific requirements:
- **Performance-critical applications:** Use NextP256K (CGO)
- **Pure Go requirements:** Choose between Btcec (faster) or P256K1 (cleaner API)
- **Balance:** P256K1Signer offers good performance with pure Go simplicity
---
## Running the Benchmarks
To reproduce these benchmarks:
```bash
# Run all benchmarks
CGO_ENABLED=1 go test -tags=cgo ./bench -bench=. -benchmem
# Run specific operation
CGO_ENABLED=1 go test -tags=cgo ./bench -bench=BenchmarkSign
# Run specific implementation
CGO_ENABLED=1 go test -tags=cgo ./bench -bench=Benchmark.*_P256K1
```
**Note:** All benchmarks require CGO to be enabled (`CGO_ENABLED=1`) and the `cgo` build tag.

View File

@@ -0,0 +1,359 @@
//go:build cgo
// +build cgo
package bench
import (
"crypto/rand"
"testing"
p256knext "next.orly.dev/pkg/crypto/p256k"
"p256k1.mleku.dev/signer"
)
// This file contains benchmarks comparing the three signer implementations:
// 1. P256K1Signer (this package's new port from Bitcoin Core secp256k1)
// 2. BtcecSigner (pure Go btcec wrapper)
// 3. NextP256K Signer (CGO version using next.orly.dev/pkg/crypto/p256k)
var (
benchSeckey []byte
benchMsghash []byte
compBenchSignerP256K1 *signer.P256K1Signer
compBenchSignerBtcec *signer.BtcecSigner
compBenchSignerNext *p256knext.Signer
compBenchSignerP256K12 *signer.P256K1Signer
compBenchSignerBtcec2 *signer.BtcecSigner
compBenchSignerNext2 *p256knext.Signer
compBenchSigP256K1 []byte
compBenchSigBtcec []byte
compBenchSigNext []byte
)
func initComparisonBenchData() {
// Generate a fixed secret key for benchmarks
if benchSeckey == nil {
benchSeckey = []byte{
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
}
// Ensure it's valid (non-zero and less than order)
// We'll validate by trying to create a signer
for {
testSigner := signer.NewP256K1Signer()
if err := testSigner.InitSec(benchSeckey); err == nil {
break
}
if _, err := rand.Read(benchSeckey); err != nil {
panic(err)
}
}
// Create message hash
benchMsghash = make([]byte, 32)
if _, err := rand.Read(benchMsghash); err != nil {
panic(err)
}
}
// Setup P256K1Signer (this repo's implementation)
signer1 := signer.NewP256K1Signer()
if err := signer1.InitSec(benchSeckey); err != nil {
panic(err)
}
compBenchSignerP256K1 = signer1
var err error
compBenchSigP256K1, err = signer1.Sign(benchMsghash)
if err != nil {
panic(err)
}
// Setup BtcecSigner (pure Go)
signer2 := signer.NewBtcecSigner()
if err := signer2.InitSec(benchSeckey); err != nil {
panic(err)
}
compBenchSignerBtcec = signer2
compBenchSigBtcec, err = signer2.Sign(benchMsghash)
if err != nil {
panic(err)
}
// Setup NextP256K Signer (CGO version)
signer3 := &p256knext.Signer{}
if err := signer3.InitSec(benchSeckey); err != nil {
panic(err)
}
compBenchSignerNext = signer3
compBenchSigNext, err = signer3.Sign(benchMsghash)
if err != nil {
panic(err)
}
// Generate second key pair for ECDH
seckey2 := make([]byte, 32)
for {
if _, err := rand.Read(seckey2); err != nil {
panic(err)
}
// Validate by trying to create a signer
testSigner := signer.NewP256K1Signer()
if err := testSigner.InitSec(seckey2); err == nil {
break
}
}
// P256K1Signer second key pair
signer12 := signer.NewP256K1Signer()
if err := signer12.InitSec(seckey2); err != nil {
panic(err)
}
compBenchSignerP256K12 = signer12
// BtcecSigner second key pair
signer22 := signer.NewBtcecSigner()
if err := signer22.InitSec(seckey2); err != nil {
panic(err)
}
compBenchSignerBtcec2 = signer22
// NextP256K Signer second key pair
signer32 := &p256knext.Signer{}
if err := signer32.InitSec(seckey2); err != nil {
panic(err)
}
compBenchSignerNext2 = signer32
}
// BenchmarkPubkeyDerivation compares public key derivation from private key
func BenchmarkPubkeyDerivation_P256K1(b *testing.B) {
if benchSeckey == nil {
initComparisonBenchData()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
s := signer.NewP256K1Signer()
if err := s.InitSec(benchSeckey); err != nil {
b.Fatalf("failed to create signer: %v", err)
}
_ = s.Pub()
}
}
func BenchmarkPubkeyDerivation_Btcec(b *testing.B) {
if benchSeckey == nil {
initComparisonBenchData()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
s := signer.NewBtcecSigner()
if err := s.InitSec(benchSeckey); err != nil {
b.Fatalf("failed to create signer: %v", err)
}
_ = s.Pub()
}
}
func BenchmarkPubkeyDerivation_NextP256K(b *testing.B) {
if benchSeckey == nil {
initComparisonBenchData()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
s := &p256knext.Signer{}
if err := s.InitSec(benchSeckey); err != nil {
b.Fatalf("failed to create signer: %v", err)
}
_ = s.Pub()
}
}
// BenchmarkSign compares Schnorr signing
func BenchmarkSign_P256K1(b *testing.B) {
if benchSeckey == nil {
initComparisonBenchData()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if compBenchSignerP256K1 == nil {
initComparisonBenchData()
}
_, err := compBenchSignerP256K1.Sign(benchMsghash)
if err != nil {
b.Fatalf("failed to sign: %v", err)
}
}
}
func BenchmarkSign_Btcec(b *testing.B) {
if benchSeckey == nil {
initComparisonBenchData()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if compBenchSignerBtcec == nil {
initComparisonBenchData()
}
_, err := compBenchSignerBtcec.Sign(benchMsghash)
if err != nil {
b.Fatalf("failed to sign: %v", err)
}
}
}
func BenchmarkSign_NextP256K(b *testing.B) {
if benchSeckey == nil {
initComparisonBenchData()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if compBenchSignerNext == nil {
initComparisonBenchData()
}
_, err := compBenchSignerNext.Sign(benchMsghash)
if err != nil {
b.Fatalf("failed to sign: %v", err)
}
}
}
// BenchmarkVerify compares Schnorr verification
func BenchmarkVerify_P256K1(b *testing.B) {
if benchSeckey == nil {
initComparisonBenchData()
}
if compBenchSignerP256K1 == nil || compBenchSigP256K1 == nil {
initComparisonBenchData()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
verifier := signer.NewP256K1Signer()
if err := verifier.InitPub(compBenchSignerP256K1.Pub()); err != nil {
b.Fatalf("failed to create verifier: %v", err)
}
valid, err := verifier.Verify(benchMsghash, compBenchSigP256K1)
if err != nil {
b.Fatalf("verification error: %v", err)
}
if !valid {
b.Fatalf("verification failed")
}
}
}
func BenchmarkVerify_Btcec(b *testing.B) {
if benchSeckey == nil {
initComparisonBenchData()
}
if compBenchSignerBtcec == nil || compBenchSigBtcec == nil {
initComparisonBenchData()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
verifier := signer.NewBtcecSigner()
if err := verifier.InitPub(compBenchSignerBtcec.Pub()); err != nil {
b.Fatalf("failed to create verifier: %v", err)
}
valid, err := verifier.Verify(benchMsghash, compBenchSigBtcec)
if err != nil {
b.Fatalf("verification error: %v", err)
}
if !valid {
b.Fatalf("verification failed")
}
}
}
func BenchmarkVerify_NextP256K(b *testing.B) {
if benchSeckey == nil {
initComparisonBenchData()
}
if compBenchSignerNext == nil || compBenchSigNext == nil {
initComparisonBenchData()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
verifier := &p256knext.Signer{}
if err := verifier.InitPub(compBenchSignerNext.Pub()); err != nil {
b.Fatalf("failed to create verifier: %v", err)
}
valid, err := verifier.Verify(benchMsghash, compBenchSigNext)
if err != nil {
b.Fatalf("verification error: %v", err)
}
if !valid {
b.Fatalf("verification failed")
}
}
}
// BenchmarkECDH compares ECDH shared secret generation
func BenchmarkECDH_P256K1(b *testing.B) {
if benchSeckey == nil {
initComparisonBenchData()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if compBenchSignerP256K1 == nil || compBenchSignerP256K12 == nil {
initComparisonBenchData()
}
_, err := compBenchSignerP256K1.ECDH(compBenchSignerP256K12.Pub())
if err != nil {
b.Fatalf("ECDH failed: %v", err)
}
}
}
func BenchmarkECDH_Btcec(b *testing.B) {
if benchSeckey == nil {
initComparisonBenchData()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if compBenchSignerBtcec == nil || compBenchSignerBtcec2 == nil {
initComparisonBenchData()
}
_, err := compBenchSignerBtcec.ECDH(compBenchSignerBtcec2.Pub())
if err != nil {
b.Fatalf("ECDH failed: %v", err)
}
}
}
func BenchmarkECDH_NextP256K(b *testing.B) {
if benchSeckey == nil {
initComparisonBenchData()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if compBenchSignerNext == nil || compBenchSignerNext2 == nil {
initComparisonBenchData()
}
_, err := compBenchSignerNext.ECDH(compBenchSignerNext2.Pub())
if err != nil {
b.Fatalf("ECDH failed: %v", err)
}
}
}

View File

@@ -1,24 +0,0 @@
goos: linux
goarch: amd64
pkg: p256k1.mleku.dev
cpu: AMD Ryzen 5 PRO 4650G with Radeon Graphics
BenchmarkContextCreate-12 284636085 8.524 ns/op 1 B/op 1 allocs/op
BenchmarkContextRandomize-12 947889351 2.545 ns/op 0 B/op 0 allocs/op
BenchmarkECDSASign-12 469 5039503 ns/op 2226 B/op 39 allocs/op
BenchmarkECDSAVerify-12 240 9790878 ns/op 0 B/op 0 allocs/op
BenchmarkECDSASignCompact-12 458 5143887 ns/op 2290 B/op 40 allocs/op
BenchmarkECDSAVerifyCompact-12 247 10349143 ns/op 0 B/op 0 allocs/op
BenchmarkECSeckeyGenerate-12 4326594 548.4 ns/op 32 B/op 1 allocs/op
BenchmarkECKeyPairGenerate-12 474 5109935 ns/op 96 B/op 2 allocs/op
BenchmarkSHA256-12 15423699 150.4 ns/op 144 B/op 2 allocs/op
BenchmarkHMACSHA256-12 4691949 517.0 ns/op 416 B/op 7 allocs/op
BenchmarkRFC6979-12 780189 2840 ns/op 2162 B/op 38 allocs/op
BenchmarkTaggedHash-12 7720662 309.7 ns/op 320 B/op 5 allocs/op
BenchmarkEcmultGen-12 1899 1273725 ns/op 0 B/op 0 allocs/op
BenchmarkGroupDouble-12 11767611 203.7 ns/op 0 B/op 0 allocs/op
BenchmarkGroupAdd-12 61155 38667 ns/op 0 B/op 0 allocs/op
BenchmarkECPubkeyCreate-12 1878 1259578 ns/op 0 B/op 0 allocs/op
BenchmarkECPubkeySerializeCompressed-12 36482311 64.90 ns/op 0 B/op 0 allocs/op
BenchmarkECPubkeyParse-12 357204 6595 ns/op 0 B/op 0 allocs/op
PASS
ok p256k1.mleku.dev 50.269s

23
go.mod
View File

@@ -1,12 +1,21 @@
module p256k1.mleku.dev
go 1.21
require github.com/minio/sha256-simd v1.0.1
go 1.25.0
require (
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
github.com/templexxx/cpu v0.0.1 // indirect
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b // indirect
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.6
github.com/minio/sha256-simd v1.0.1
next.orly.dev v1.0.3
)
require (
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/templexxx/cpu v0.1.1 // indirect
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b // indirect
golang.org/x/sys v0.37.0 // indirect
lol.mleku.dev v1.0.5 // indirect
)

25
go.sum
View File

@@ -1,10 +1,25 @@
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/btcsuite/btcd/btcec/v2 v2.3.6 h1:IzlsEr9olcSRKB/n7c4351F3xHKxS2lma+1UFGCYd4E=
github.com/btcsuite/btcd/btcec/v2 v2.3.6/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY=
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b h1:XeDLE6c9mzHpdv3Wb1+pWBaWv/BlHK0ZYIu/KaL6eHg=
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b/go.mod h1:7rwmCH0wC2fQvNEvPZ3sKXukhyCTyiaZ5VTZMQYpZKQ=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
lol.mleku.dev v1.0.5 h1:irwfwz+Scv74G/2OXmv05YFKOzUNOVZ735EAkYgjgM8=
lol.mleku.dev v1.0.5/go.mod h1:JlsqP0CZDLKRyd85XGcy79+ydSRqmFkrPzYFMYxQ+zs=
next.orly.dev v1.0.3 h1:PF1mhQa9s6CksqJ9hCkczBlZXp5DAlZK9Ej3katNijg=
next.orly.dev v1.0.3/go.mod h1:/C14fkucnvjsJzj17tzmF5GeW4n0nQw+YkepakUFREc=

191
signer/btcec_signer.go Normal file
View File

@@ -0,0 +1,191 @@
//go:build cgo
// +build cgo
package signer
import (
"errors"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
)
// BtcecSigner implements the I interface using btcec (pure Go implementation)
type BtcecSigner struct {
privKey *btcec.PrivateKey
pubKey *btcec.PublicKey
xonlyPub []byte // Cached x-only public key
hasSecret bool
}
// NewBtcecSigner creates a new BtcecSigner instance
func NewBtcecSigner() *BtcecSigner {
return &BtcecSigner{
hasSecret: false,
}
}
// Generate creates a fresh new key pair from system entropy, and ensures it is even (so ECDH works)
func (s *BtcecSigner) Generate() error {
privKey, err := btcec.NewPrivateKey()
if err != nil {
return err
}
pubKey := privKey.PubKey()
xonlyPub := schnorr.SerializePubKey(pubKey)
// Ensure even Y coordinate for ECDH compatibility
// If the Y coordinate is odd, negate the private key
pubBytes := pubKey.SerializeCompressed()
if pubBytes[0] == 0x03 { // Odd Y coordinate
// Negate the private key
scalar := privKey.Key
scalar.Negate()
privKey = &btcec.PrivateKey{Key: scalar}
pubKey = privKey.PubKey()
xonlyPub = schnorr.SerializePubKey(pubKey)
}
s.privKey = privKey
s.pubKey = pubKey
s.xonlyPub = xonlyPub
s.hasSecret = true
return nil
}
// InitSec initialises the secret (signing) key from the raw bytes, and also derives the public key
func (s *BtcecSigner) InitSec(sec []byte) error {
if len(sec) != 32 {
return errors.New("secret key must be 32 bytes")
}
privKey, pubKey := btcec.PrivKeyFromBytes(sec)
xonlyPub := schnorr.SerializePubKey(pubKey)
// Ensure even Y coordinate for ECDH compatibility
pubBytes := pubKey.SerializeCompressed()
if pubBytes[0] == 0x03 { // Odd Y coordinate
// Negate the private key
scalar := privKey.Key
scalar.Negate()
privKey = &btcec.PrivateKey{Key: scalar}
pubKey = privKey.PubKey()
xonlyPub = schnorr.SerializePubKey(pubKey)
}
s.privKey = privKey
s.pubKey = pubKey
s.xonlyPub = xonlyPub
s.hasSecret = true
return nil
}
// InitPub initializes the public (verification) key from raw bytes, this is expected to be an x-only 32 byte pubkey
func (s *BtcecSigner) InitPub(pub []byte) error {
if len(pub) != 32 {
return errors.New("public key must be 32 bytes")
}
pubKey, err := schnorr.ParsePubKey(pub)
if err != nil {
return err
}
s.pubKey = pubKey
s.xonlyPub = pub
s.privKey = nil
s.hasSecret = false
return nil
}
// Sec returns the secret key bytes
func (s *BtcecSigner) Sec() []byte {
if !s.hasSecret || s.privKey == nil {
return nil
}
return s.privKey.Serialize()
}
// Pub returns the public key bytes (x-only schnorr pubkey)
func (s *BtcecSigner) Pub() []byte {
if s.xonlyPub == nil {
return nil
}
return s.xonlyPub
}
// Sign creates a signature using the stored secret key
func (s *BtcecSigner) Sign(msg []byte) (sig []byte, err error) {
if !s.hasSecret || s.privKey == nil {
return nil, errors.New("no secret key available for signing")
}
if len(msg) != 32 {
return nil, errors.New("message must be 32 bytes")
}
signature, err := schnorr.Sign(s.privKey, msg)
if err != nil {
return nil, err
}
return signature.Serialize(), nil
}
// Verify checks a message hash and signature match the stored public key
func (s *BtcecSigner) Verify(msg, sig []byte) (valid bool, err error) {
if s.pubKey == nil {
return false, errors.New("no public key available for verification")
}
if len(msg) != 32 {
return false, errors.New("message must be 32 bytes")
}
if len(sig) != 64 {
return false, errors.New("signature must be 64 bytes")
}
signature, err := schnorr.ParseSignature(sig)
if err != nil {
return false, err
}
valid = signature.Verify(msg, s.pubKey)
return valid, nil
}
// Zero wipes the secret key to prevent memory leaks
func (s *BtcecSigner) Zero() {
if s.privKey != nil {
s.privKey.Zero()
s.privKey = nil
}
s.hasSecret = false
s.pubKey = nil
s.xonlyPub = nil
}
// ECDH returns a shared secret derived using Elliptic Curve Diffie-Hellman on the I secret and provided pubkey
func (s *BtcecSigner) ECDH(pub []byte) (secret []byte, err error) {
if !s.hasSecret || s.privKey == nil {
return nil, errors.New("no secret key available for ECDH")
}
if len(pub) != 32 {
return nil, errors.New("public key must be 32 bytes")
}
// Parse x-only pubkey
pubKey, err := schnorr.ParsePubKey(pub)
if err != nil {
return nil, err
}
secret = btcec.GenerateSharedSecret(s.privKey, pubKey)
return secret, nil
}