crypto_bench_test.go raw
1 //go:build !js && !wasm && !tinygo && !wasm32
2
3 package bench
4
5 import (
6 "crypto/rand"
7 "fmt"
8 "testing"
9
10 "github.com/btcsuite/btcd/btcec/v2"
11 "github.com/btcsuite/btcd/btcec/v2/ecdsa"
12 "github.com/btcsuite/btcd/btcec/v2/schnorr"
13
14 "next.orly.dev/pkg/p256k1"
15 )
16
17 // Benchmark comparing pure Go implementation vs libsecp256k1 C library
18 // for ECDSA and Schnorr operations on AMD64
19
20 var (
21 cryptoBenchSeckey []byte
22 cryptoBenchMsghash []byte
23 cryptoBenchPubkey *p256k1.PublicKey
24 cryptoBenchXonly *p256k1.XOnlyPubkey
25 cryptoBenchKeypair *p256k1.KeyPair
26
27 // Pre-computed signatures
28 cryptoBenchSchnorrSig [64]byte
29 cryptoBenchECDSASig p256k1.ECDSASignature
30
31 // libsecp256k1
32 cryptoBenchLibSecp *p256k1.LibSecp256k1
33 )
34
35 func initCryptoBenchData(b *testing.B) {
36 if cryptoBenchSeckey != nil {
37 return
38 }
39
40 // Generate a valid secret key
41 cryptoBenchSeckey = make([]byte, 32)
42 for {
43 if _, err := rand.Read(cryptoBenchSeckey); err != nil {
44 b.Fatal(err)
45 }
46 // Validate by creating a keypair
47 kp, err := p256k1.KeyPairCreate(cryptoBenchSeckey)
48 if err == nil {
49 cryptoBenchKeypair = kp
50 break
51 }
52 }
53
54 // Get public keys
55 cryptoBenchPubkey = cryptoBenchKeypair.Pubkey()
56 xonly, err := cryptoBenchKeypair.XOnlyPubkey()
57 if err != nil {
58 b.Fatal(err)
59 }
60 cryptoBenchXonly = xonly
61
62 // Generate message hash
63 cryptoBenchMsghash = make([]byte, 32)
64 if _, err := rand.Read(cryptoBenchMsghash); err != nil {
65 b.Fatal(err)
66 }
67
68 // Pre-compute Schnorr signature
69 if err := p256k1.SchnorrSign(cryptoBenchSchnorrSig[:], cryptoBenchMsghash, cryptoBenchKeypair, nil); err != nil {
70 b.Fatal(err)
71 }
72
73 // Pre-compute ECDSA signature
74 if err := p256k1.ECDSASign(&cryptoBenchECDSASig, cryptoBenchMsghash, cryptoBenchSeckey); err != nil {
75 b.Fatal(err)
76 }
77
78 // Try to load libsecp256k1
79 cryptoBenchLibSecp, _ = p256k1.GetLibSecp256k1()
80 }
81
82 // =============================================================================
83 // Pure Go - Schnorr
84 // =============================================================================
85
86 func BenchmarkPureGo_Schnorr_PubkeyDerivation(b *testing.B) {
87 initCryptoBenchData(b)
88 var sig [64]byte
89 _ = sig
90
91 b.ResetTimer()
92 b.ReportAllocs()
93 for i := 0; i < b.N; i++ {
94 kp, err := p256k1.KeyPairCreate(cryptoBenchSeckey)
95 if err != nil {
96 b.Fatal(err)
97 }
98 _, err = kp.XOnlyPubkey()
99 if err != nil {
100 b.Fatal(err)
101 }
102 }
103 }
104
105 func BenchmarkPureGo_Schnorr_Sign(b *testing.B) {
106 initCryptoBenchData(b)
107 var sig [64]byte
108
109 b.ResetTimer()
110 b.ReportAllocs()
111 for i := 0; i < b.N; i++ {
112 if err := p256k1.SchnorrSign(sig[:], cryptoBenchMsghash, cryptoBenchKeypair, nil); err != nil {
113 b.Fatal(err)
114 }
115 }
116 }
117
118 func BenchmarkPureGo_Schnorr_Verify(b *testing.B) {
119 initCryptoBenchData(b)
120
121 b.ResetTimer()
122 b.ReportAllocs()
123 for i := 0; i < b.N; i++ {
124 if !p256k1.SchnorrVerify(cryptoBenchSchnorrSig[:], cryptoBenchMsghash, cryptoBenchXonly) {
125 b.Fatal("verification failed")
126 }
127 }
128 }
129
130 // =============================================================================
131 // Pure Go - ECDSA
132 // =============================================================================
133
134 func BenchmarkPureGo_ECDSA_PubkeyDerivation(b *testing.B) {
135 initCryptoBenchData(b)
136 var pubkey p256k1.PublicKey
137
138 b.ResetTimer()
139 b.ReportAllocs()
140 for i := 0; i < b.N; i++ {
141 if err := p256k1.ECPubkeyCreate(&pubkey, cryptoBenchSeckey); err != nil {
142 b.Fatal(err)
143 }
144 }
145 }
146
147 func BenchmarkPureGo_ECDSA_Sign(b *testing.B) {
148 initCryptoBenchData(b)
149 var sig p256k1.ECDSASignature
150
151 b.ResetTimer()
152 b.ReportAllocs()
153 for i := 0; i < b.N; i++ {
154 if err := p256k1.ECDSASign(&sig, cryptoBenchMsghash, cryptoBenchSeckey); err != nil {
155 b.Fatal(err)
156 }
157 }
158 }
159
160 func BenchmarkPureGo_ECDSA_Verify(b *testing.B) {
161 initCryptoBenchData(b)
162
163 b.ResetTimer()
164 b.ReportAllocs()
165 for i := 0; i < b.N; i++ {
166 if !p256k1.ECDSAVerify(&cryptoBenchECDSASig, cryptoBenchMsghash, cryptoBenchPubkey) {
167 b.Fatal("verification failed")
168 }
169 }
170 }
171
172 // =============================================================================
173 // libsecp256k1 (C library via purego) - Schnorr
174 // =============================================================================
175
176 func BenchmarkLibSecp_Schnorr_PubkeyDerivation(b *testing.B) {
177 initCryptoBenchData(b)
178
179 if cryptoBenchLibSecp == nil || !cryptoBenchLibSecp.IsLoaded() {
180 b.Skip("libsecp256k1.so not available")
181 }
182
183 // libsecp256k1 derives x-only pubkey via compressed pubkey
184 b.ResetTimer()
185 b.ReportAllocs()
186 for i := 0; i < b.N; i++ {
187 pubkey, _, err := cryptoBenchLibSecp.CreatePubkeyCompressed(cryptoBenchSeckey)
188 if err != nil {
189 b.Fatal(err)
190 }
191 // x-only is just the x coordinate (bytes 1-32 of compressed)
192 _ = pubkey[1:33]
193 }
194 }
195
196 func BenchmarkLibSecp_Schnorr_Sign(b *testing.B) {
197 initCryptoBenchData(b)
198
199 if cryptoBenchLibSecp == nil || !cryptoBenchLibSecp.IsLoaded() {
200 b.Skip("libsecp256k1.so not available")
201 }
202
203 b.ResetTimer()
204 b.ReportAllocs()
205 for i := 0; i < b.N; i++ {
206 _, err := cryptoBenchLibSecp.SchnorrSign(cryptoBenchMsghash, cryptoBenchSeckey)
207 if err != nil {
208 b.Fatal(err)
209 }
210 }
211 }
212
213 func BenchmarkLibSecp_Schnorr_Verify(b *testing.B) {
214 initCryptoBenchData(b)
215
216 if cryptoBenchLibSecp == nil || !cryptoBenchLibSecp.IsLoaded() {
217 b.Skip("libsecp256k1.so not available")
218 }
219
220 // Get signature from libsecp for fair comparison
221 sig, err := cryptoBenchLibSecp.SchnorrSign(cryptoBenchMsghash, cryptoBenchSeckey)
222 if err != nil {
223 b.Fatal(err)
224 }
225 // Get x-only pubkey (first 32 bytes after prefix of compressed pubkey)
226 compressedPubkey, _, err := cryptoBenchLibSecp.CreatePubkeyCompressed(cryptoBenchSeckey)
227 if err != nil {
228 b.Fatal(err)
229 }
230 pubkey := compressedPubkey[1:33] // x-only is just x coordinate
231
232 b.ResetTimer()
233 b.ReportAllocs()
234 for i := 0; i < b.N; i++ {
235 if !cryptoBenchLibSecp.SchnorrVerify(sig, cryptoBenchMsghash, pubkey) {
236 b.Fatal("verification failed")
237 }
238 }
239 }
240
241 // =============================================================================
242 // libsecp256k1 (C library via purego) - ECDSA
243 // =============================================================================
244
245 func BenchmarkLibSecp_ECDSA_PubkeyDerivation(b *testing.B) {
246 initCryptoBenchData(b)
247
248 if cryptoBenchLibSecp == nil || !cryptoBenchLibSecp.IsLoaded() {
249 b.Skip("libsecp256k1.so not available")
250 }
251
252 b.ResetTimer()
253 b.ReportAllocs()
254 for i := 0; i < b.N; i++ {
255 _, err := cryptoBenchLibSecp.CreatePubkey(cryptoBenchSeckey)
256 if err != nil {
257 b.Fatal(err)
258 }
259 }
260 }
261
262 func BenchmarkLibSecp_ECDSA_Sign(b *testing.B) {
263 initCryptoBenchData(b)
264
265 if cryptoBenchLibSecp == nil || !cryptoBenchLibSecp.IsLoaded() {
266 b.Skip("libsecp256k1.so not available")
267 }
268
269 b.ResetTimer()
270 b.ReportAllocs()
271 for i := 0; i < b.N; i++ {
272 _, err := cryptoBenchLibSecp.ECDSASign(cryptoBenchMsghash, cryptoBenchSeckey)
273 if err != nil {
274 b.Fatal(err)
275 }
276 }
277 }
278
279 func BenchmarkLibSecp_ECDSA_Verify(b *testing.B) {
280 initCryptoBenchData(b)
281
282 if cryptoBenchLibSecp == nil || !cryptoBenchLibSecp.IsLoaded() {
283 b.Skip("libsecp256k1.so not available")
284 }
285
286 // Get signature and pubkey from libsecp for fair comparison
287 sig, err := cryptoBenchLibSecp.ECDSASign(cryptoBenchMsghash, cryptoBenchSeckey)
288 if err != nil {
289 b.Fatal(err)
290 }
291 // ECDSA needs compressed pubkey (33 bytes), not x-only
292 pubkey, _, err := cryptoBenchLibSecp.CreatePubkeyCompressed(cryptoBenchSeckey)
293 if err != nil {
294 b.Fatal(err)
295 }
296
297 b.ResetTimer()
298 b.ReportAllocs()
299 for i := 0; i < b.N; i++ {
300 if !cryptoBenchLibSecp.ECDSAVerify(sig, cryptoBenchMsghash, pubkey) {
301 b.Fatal("verification failed")
302 }
303 }
304 }
305
306 // =============================================================================
307 // btcec (decred's secp256k1 in pure Go) - Schnorr
308 // =============================================================================
309
310 var (
311 cryptoBtcecPrivKey *btcec.PrivateKey
312 cryptoBtcecPubKey *btcec.PublicKey
313 cryptoBtcecSchnorrSig *schnorr.Signature
314 cryptoBtcecECDSASig *ecdsa.Signature
315 )
316
317 func initBtcecData(b *testing.B) {
318 if cryptoBtcecPrivKey != nil {
319 return
320 }
321 initCryptoBenchData(b)
322
323 var err error
324 cryptoBtcecPrivKey, cryptoBtcecPubKey = btcec.PrivKeyFromBytes(cryptoBenchSeckey)
325 if cryptoBtcecPrivKey == nil {
326 b.Fatal("failed to create btcec private key")
327 }
328
329 cryptoBtcecSchnorrSig, err = schnorr.Sign(cryptoBtcecPrivKey, cryptoBenchMsghash)
330 if err != nil {
331 b.Fatal(err)
332 }
333
334 cryptoBtcecECDSASig = ecdsa.Sign(cryptoBtcecPrivKey, cryptoBenchMsghash)
335 }
336
337 func BenchmarkBtcec_Schnorr_PubkeyDerivation(b *testing.B) {
338 initBtcecData(b)
339
340 b.ResetTimer()
341 b.ReportAllocs()
342 for i := 0; i < b.N; i++ {
343 priv, _ := btcec.PrivKeyFromBytes(cryptoBenchSeckey)
344 _ = priv.PubKey()
345 }
346 }
347
348 func BenchmarkBtcec_Schnorr_Sign(b *testing.B) {
349 initBtcecData(b)
350
351 b.ResetTimer()
352 b.ReportAllocs()
353 for i := 0; i < b.N; i++ {
354 _, err := schnorr.Sign(cryptoBtcecPrivKey, cryptoBenchMsghash)
355 if err != nil {
356 b.Fatal(err)
357 }
358 }
359 }
360
361 func BenchmarkBtcec_Schnorr_Verify(b *testing.B) {
362 initBtcecData(b)
363
364 b.ResetTimer()
365 b.ReportAllocs()
366 for i := 0; i < b.N; i++ {
367 if !cryptoBtcecSchnorrSig.Verify(cryptoBenchMsghash, cryptoBtcecPubKey) {
368 b.Fatal("verification failed")
369 }
370 }
371 }
372
373 // =============================================================================
374 // btcec (decred's secp256k1 in pure Go) - ECDSA
375 // =============================================================================
376
377 func BenchmarkBtcec_ECDSA_PubkeyDerivation(b *testing.B) {
378 initBtcecData(b)
379
380 b.ResetTimer()
381 b.ReportAllocs()
382 for i := 0; i < b.N; i++ {
383 priv, _ := btcec.PrivKeyFromBytes(cryptoBenchSeckey)
384 _ = priv.PubKey()
385 }
386 }
387
388 func BenchmarkBtcec_ECDSA_Sign(b *testing.B) {
389 initBtcecData(b)
390
391 b.ResetTimer()
392 b.ReportAllocs()
393 for i := 0; i < b.N; i++ {
394 _ = ecdsa.Sign(cryptoBtcecPrivKey, cryptoBenchMsghash)
395 }
396 }
397
398 func BenchmarkBtcec_ECDSA_Verify(b *testing.B) {
399 initBtcecData(b)
400
401 b.ResetTimer()
402 b.ReportAllocs()
403 for i := 0; i < b.N; i++ {
404 if !cryptoBtcecECDSASig.Verify(cryptoBenchMsghash, cryptoBtcecPubKey) {
405 b.Fatal("verification failed")
406 }
407 }
408 }
409
410 // =============================================================================
411 // Summary benchmark that prints comparison table
412 // =============================================================================
413
414 func BenchmarkCryptoComparison(b *testing.B) {
415 initCryptoBenchData(b)
416
417 hasLibSecp := cryptoBenchLibSecp != nil && cryptoBenchLibSecp.IsLoaded()
418
419 fmt.Println("\n=== Cryptographic Operations Benchmark ===")
420 fmt.Printf("Platform: AMD64, libsecp256k1 available: %v\n\n", hasLibSecp)
421
422 // This is a meta-benchmark that just prints info
423 b.Skip("Run individual benchmarks with: go test -bench='PureGo|LibSecp|Btcec' -benchtime=1s")
424 }
425