simd_comparison_test.go raw
1 //go:build !js && !wasm && !tinygo && !wasm32
2
3 package bench
4
5 import (
6 "crypto/rand"
7 "testing"
8
9 "github.com/btcsuite/btcd/btcec/v2"
10 "github.com/btcsuite/btcd/btcec/v2/schnorr"
11
12 "next.orly.dev/pkg/p256k1"
13 "next.orly.dev/pkg/p256k1/signer"
14 )
15
16 // This file contains comprehensive benchmarks comparing:
17 // 1. btcec/v2 (decred's secp256k1 implementation)
18 // 2. P256K1 Pure Go (AVX2 disabled)
19 // 3. P256K1 with ASM/BMI2 (AVX2 enabled where applicable)
20 // 4. libsecp256k1.so via purego (dlopen)
21
22 var (
23 simdBenchSeckey []byte
24 simdBenchSeckey2 []byte
25 simdBenchMsghash []byte
26
27 // btcec
28 btcecPrivKey *btcec.PrivateKey
29 btcecPrivKey2 *btcec.PrivateKey
30 btcecSig *schnorr.Signature
31
32 // P256K1
33 p256k1Signer *signer.P256K1Signer
34 p256k1Signer2 *signer.P256K1Signer
35 p256k1Sig []byte
36
37 // libsecp256k1
38 libsecp *p256k1.LibSecp256k1
39 )
40
41 func initSIMDBenchData() {
42 if simdBenchSeckey != nil {
43 return
44 }
45
46 // Generate deterministic secret key
47 simdBenchSeckey = []byte{
48 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
49 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
50 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
51 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
52 }
53
54 // Second key for ECDH
55 simdBenchSeckey2 = make([]byte, 32)
56 for {
57 if _, err := rand.Read(simdBenchSeckey2); err != nil {
58 panic(err)
59 }
60 // Validate
61 _, err := btcec.PrivKeyFromBytes(simdBenchSeckey2)
62 if err == nil {
63 break
64 }
65 }
66
67 // Message hash
68 simdBenchMsghash = make([]byte, 32)
69 if _, err := rand.Read(simdBenchMsghash); err != nil {
70 panic(err)
71 }
72
73 // Initialize btcec
74 btcecPrivKey, _ = btcec.PrivKeyFromBytes(simdBenchSeckey)
75 btcecPrivKey2, _ = btcec.PrivKeyFromBytes(simdBenchSeckey2)
76 btcecSig, _ = schnorr.Sign(btcecPrivKey, simdBenchMsghash)
77
78 // Initialize P256K1
79 p256k1Signer = signer.NewP256K1Signer()
80 if err := p256k1Signer.InitSec(simdBenchSeckey); err != nil {
81 panic(err)
82 }
83 p256k1Signer2 = signer.NewP256K1Signer()
84 if err := p256k1Signer2.InitSec(simdBenchSeckey2); err != nil {
85 panic(err)
86 }
87 p256k1Sig, _ = p256k1Signer.Sign(simdBenchMsghash)
88
89 // Initialize libsecp256k1
90 libsecp, _ = p256k1.GetLibSecp256k1()
91 }
92
93 // =============================================================================
94 // btcec/v2 Benchmarks
95 // =============================================================================
96
97 func BenchmarkBtcec_PubkeyDerivation(b *testing.B) {
98 initSIMDBenchData()
99
100 b.ResetTimer()
101 for i := 0; i < b.N; i++ {
102 priv, _ := btcec.PrivKeyFromBytes(simdBenchSeckey)
103 _ = priv.PubKey()
104 }
105 }
106
107 func BenchmarkBtcec_Sign(b *testing.B) {
108 initSIMDBenchData()
109
110 b.ResetTimer()
111 for i := 0; i < b.N; i++ {
112 _, err := schnorr.Sign(btcecPrivKey, simdBenchMsghash)
113 if err != nil {
114 b.Fatal(err)
115 }
116 }
117 }
118
119 func BenchmarkBtcec_Verify(b *testing.B) {
120 initSIMDBenchData()
121
122 pubKey := btcecPrivKey.PubKey()
123
124 b.ResetTimer()
125 for i := 0; i < b.N; i++ {
126 if !btcecSig.Verify(simdBenchMsghash, pubKey) {
127 b.Fatal("verification failed")
128 }
129 }
130 }
131
132 func BenchmarkBtcec_ECDH(b *testing.B) {
133 initSIMDBenchData()
134
135 pub2 := btcecPrivKey2.PubKey()
136
137 b.ResetTimer()
138 for i := 0; i < b.N; i++ {
139 // ECDH: privKey1 * pubKey2
140 x, y := btcec.S256().ScalarMult(pub2.X(), pub2.Y(), simdBenchSeckey)
141 _ = x
142 _ = y
143 }
144 }
145
146 // =============================================================================
147 // P256K1 Pure Go Benchmarks (AVX2 disabled)
148 // =============================================================================
149
150 func BenchmarkP256K1PureGo_PubkeyDerivation(b *testing.B) {
151 initSIMDBenchData()
152
153 p256k1.SetAVX2Enabled(false)
154 defer p256k1.SetAVX2Enabled(true)
155
156 b.ResetTimer()
157 for i := 0; i < b.N; i++ {
158 s := signer.NewP256K1Signer()
159 if err := s.InitSec(simdBenchSeckey); err != nil {
160 b.Fatal(err)
161 }
162 _ = s.Pub()
163 }
164 }
165
166 func BenchmarkP256K1PureGo_Sign(b *testing.B) {
167 initSIMDBenchData()
168
169 p256k1.SetAVX2Enabled(false)
170 defer p256k1.SetAVX2Enabled(true)
171
172 b.ResetTimer()
173 for i := 0; i < b.N; i++ {
174 _, err := p256k1Signer.Sign(simdBenchMsghash)
175 if err != nil {
176 b.Fatal(err)
177 }
178 }
179 }
180
181 func BenchmarkP256K1PureGo_Verify(b *testing.B) {
182 initSIMDBenchData()
183
184 p256k1.SetAVX2Enabled(false)
185 defer p256k1.SetAVX2Enabled(true)
186
187 b.ResetTimer()
188 for i := 0; i < b.N; i++ {
189 verifier := signer.NewP256K1Signer()
190 if err := verifier.InitPub(p256k1Signer.Pub()); err != nil {
191 b.Fatal(err)
192 }
193 valid, err := verifier.Verify(simdBenchMsghash, p256k1Sig)
194 if err != nil {
195 b.Fatal(err)
196 }
197 if !valid {
198 b.Fatal("verification failed")
199 }
200 }
201 }
202
203 func BenchmarkP256K1PureGo_ECDH(b *testing.B) {
204 initSIMDBenchData()
205
206 p256k1.SetAVX2Enabled(false)
207 defer p256k1.SetAVX2Enabled(true)
208
209 b.ResetTimer()
210 for i := 0; i < b.N; i++ {
211 _, err := p256k1Signer.ECDH(p256k1Signer2.Pub())
212 if err != nil {
213 b.Fatal(err)
214 }
215 }
216 }
217
218 // =============================================================================
219 // P256K1 with ASM/BMI2 Benchmarks (AVX2 enabled)
220 // =============================================================================
221
222 func BenchmarkP256K1ASM_PubkeyDerivation(b *testing.B) {
223 initSIMDBenchData()
224
225 if !p256k1.HasAVX2CPU() {
226 b.Skip("AVX2/BMI2 not available")
227 }
228
229 p256k1.SetAVX2Enabled(true)
230
231 b.ResetTimer()
232 for i := 0; i < b.N; i++ {
233 s := signer.NewP256K1Signer()
234 if err := s.InitSec(simdBenchSeckey); err != nil {
235 b.Fatal(err)
236 }
237 _ = s.Pub()
238 }
239 }
240
241 func BenchmarkP256K1ASM_Sign(b *testing.B) {
242 initSIMDBenchData()
243
244 if !p256k1.HasAVX2CPU() {
245 b.Skip("AVX2/BMI2 not available")
246 }
247
248 p256k1.SetAVX2Enabled(true)
249
250 b.ResetTimer()
251 for i := 0; i < b.N; i++ {
252 _, err := p256k1Signer.Sign(simdBenchMsghash)
253 if err != nil {
254 b.Fatal(err)
255 }
256 }
257 }
258
259 func BenchmarkP256K1ASM_Verify(b *testing.B) {
260 initSIMDBenchData()
261
262 if !p256k1.HasAVX2CPU() {
263 b.Skip("AVX2/BMI2 not available")
264 }
265
266 p256k1.SetAVX2Enabled(true)
267
268 b.ResetTimer()
269 for i := 0; i < b.N; i++ {
270 verifier := signer.NewP256K1Signer()
271 if err := verifier.InitPub(p256k1Signer.Pub()); err != nil {
272 b.Fatal(err)
273 }
274 valid, err := verifier.Verify(simdBenchMsghash, p256k1Sig)
275 if err != nil {
276 b.Fatal(err)
277 }
278 if !valid {
279 b.Fatal("verification failed")
280 }
281 }
282 }
283
284 func BenchmarkP256K1ASM_ECDH(b *testing.B) {
285 initSIMDBenchData()
286
287 if !p256k1.HasAVX2CPU() {
288 b.Skip("AVX2/BMI2 not available")
289 }
290
291 p256k1.SetAVX2Enabled(true)
292
293 b.ResetTimer()
294 for i := 0; i < b.N; i++ {
295 _, err := p256k1Signer.ECDH(p256k1Signer2.Pub())
296 if err != nil {
297 b.Fatal(err)
298 }
299 }
300 }
301
302 // =============================================================================
303 // libsecp256k1.so via purego (dlopen) Benchmarks
304 // =============================================================================
305
306 func BenchmarkLibSecp256k1_PubkeyDerivation(b *testing.B) {
307 initSIMDBenchData()
308
309 if libsecp == nil || !libsecp.IsLoaded() {
310 b.Skip("libsecp256k1.so not available")
311 }
312
313 b.ResetTimer()
314 for i := 0; i < b.N; i++ {
315 _, err := libsecp.CreatePubkey(simdBenchSeckey)
316 if err != nil {
317 b.Fatal(err)
318 }
319 }
320 }
321
322 func BenchmarkLibSecp256k1_Sign(b *testing.B) {
323 initSIMDBenchData()
324
325 if libsecp == nil || !libsecp.IsLoaded() {
326 b.Skip("libsecp256k1.so not available")
327 }
328
329 b.ResetTimer()
330 for i := 0; i < b.N; i++ {
331 _, err := libsecp.SchnorrSign(simdBenchMsghash, simdBenchSeckey)
332 if err != nil {
333 b.Fatal(err)
334 }
335 }
336 }
337
338 func BenchmarkLibSecp256k1_Verify(b *testing.B) {
339 initSIMDBenchData()
340
341 if libsecp == nil || !libsecp.IsLoaded() {
342 b.Skip("libsecp256k1.so not available")
343 }
344
345 sig, err := libsecp.SchnorrSign(simdBenchMsghash, simdBenchSeckey)
346 if err != nil {
347 b.Fatal(err)
348 }
349
350 pubkey, err := libsecp.CreatePubkey(simdBenchSeckey)
351 if err != nil {
352 b.Fatal(err)
353 }
354
355 b.ResetTimer()
356 for i := 0; i < b.N; i++ {
357 if !libsecp.SchnorrVerify(sig, simdBenchMsghash, pubkey) {
358 b.Fatal("verification failed")
359 }
360 }
361 }
362
363