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