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