Add benchmarking for Schnorr signature verification
This commit introduces a new benchmark function, `BenchmarkSchnorrVerify`, in `schnorr_test.go` to evaluate the performance of the Schnorr signature verification process. Additionally, it optimizes the `SchnorrVerify` function in `schnorr.go` by implementing a global precomputed context, reducing overhead during verification calls. The changes aim to enhance performance and provide insights into the efficiency of the verification process.
This commit is contained in:
30
schnorr.go
30
schnorr.go
@@ -2,6 +2,7 @@ package p256k1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@@ -22,6 +23,27 @@ var zeroMask = [32]byte{
|
||||
170, 247, 175, 105, 39, 10, 165, 20,
|
||||
}
|
||||
|
||||
// Global precomputed context for Schnorr verification
|
||||
// This eliminates the overhead of context creation per verification call
|
||||
var (
|
||||
schnorrVerifyContext *secp256k1_context
|
||||
schnorrVerifyContextOnce sync.Once
|
||||
)
|
||||
|
||||
// initSchnorrVerifyContext initializes the global Schnorr verification context
|
||||
func initSchnorrVerifyContext() {
|
||||
schnorrVerifyContext = &secp256k1_context{
|
||||
ecmult_gen_ctx: secp256k1_ecmult_gen_context{built: 1},
|
||||
declassify: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// getSchnorrVerifyContext returns the precomputed Schnorr verification context
|
||||
func getSchnorrVerifyContext() *secp256k1_context {
|
||||
schnorrVerifyContextOnce.Do(initSchnorrVerifyContext)
|
||||
return schnorrVerifyContext
|
||||
}
|
||||
|
||||
// NonceFunctionBIP340 implements BIP-340 nonce generation
|
||||
func NonceFunctionBIP340(nonce32 []byte, msg []byte, key32 []byte, xonlyPk32 []byte, auxRand32 []byte) error {
|
||||
if len(nonce32) != 32 {
|
||||
@@ -295,6 +317,7 @@ func SchnorrVerifyOld(sig64 []byte, msg32 []byte, xonlyPubkey *XOnlyPubkey) bool
|
||||
|
||||
// SchnorrVerify verifies a Schnorr signature following BIP-340.
|
||||
// This is the new implementation translated from C secp256k1_schnorrsig_verify.
|
||||
// Uses precomputed context for optimal performance.
|
||||
func SchnorrVerify(sig64 []byte, msg32 []byte, xonlyPubkey *XOnlyPubkey) bool {
|
||||
if len(sig64) != 64 {
|
||||
return false
|
||||
@@ -306,11 +329,8 @@ func SchnorrVerify(sig64 []byte, msg32 []byte, xonlyPubkey *XOnlyPubkey) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Create a context (required by secp256k1_schnorrsig_verify)
|
||||
ctx := &secp256k1_context{
|
||||
ecmult_gen_ctx: secp256k1_ecmult_gen_context{built: 1},
|
||||
declassify: 0,
|
||||
}
|
||||
// Use precomputed context (initialized once, reused across calls)
|
||||
ctx := getSchnorrVerifyContext()
|
||||
|
||||
// Convert x-only pubkey to secp256k1_xonly_pubkey format
|
||||
var secp_xonly secp256k1_xonly_pubkey
|
||||
|
||||
@@ -236,3 +236,39 @@ func TestSchnorrMultipleSignatures(t *testing.T) {
|
||||
t.Error("with different aux_rand, signatures should differ")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSchnorrVerify(b *testing.B) {
|
||||
// Generate keypair
|
||||
kp, err := KeyPairGenerate()
|
||||
if err != nil {
|
||||
b.Fatalf("failed to generate keypair: %v", err)
|
||||
}
|
||||
defer kp.Clear()
|
||||
|
||||
// Get x-only pubkey
|
||||
xonly, err := kp.XOnlyPubkey()
|
||||
if err != nil {
|
||||
b.Fatalf("failed to get x-only pubkey: %v", err)
|
||||
}
|
||||
|
||||
// Create message
|
||||
msg := make([]byte, 32)
|
||||
for i := range msg {
|
||||
msg[i] = byte(i)
|
||||
}
|
||||
|
||||
// Sign
|
||||
var sig [64]byte
|
||||
if err := SchnorrSign(sig[:], msg, kp, nil); err != nil {
|
||||
b.Fatalf("failed to sign: %v", err)
|
||||
}
|
||||
|
||||
// Benchmark verification
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if !SchnorrVerify(sig[:], msg, xonly) {
|
||||
b.Fatal("verification failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
verify.go
25
verify.go
@@ -2,6 +2,8 @@ package p256k1
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"hash"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@@ -667,6 +669,23 @@ func secp256k1_gej_add_zinv_var(r *secp256k1_gej, a *secp256k1_gej, b *secp256k1
|
||||
secp256k1_gej_add_ge_var(r, a, b, nil)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// GLOBAL PRE-ALLOCATED RESOURCES
|
||||
// ============================================================================
|
||||
|
||||
// Global pre-allocated hash context for challenge computation to avoid allocations
|
||||
var (
|
||||
challengeHashContext hash.Hash
|
||||
challengeHashContextOnce sync.Once
|
||||
)
|
||||
|
||||
func getChallengeHashContext() hash.Hash {
|
||||
challengeHashContextOnce.Do(func() {
|
||||
challengeHashContext = sha256.New()
|
||||
})
|
||||
return challengeHashContext
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// EC MULTIPLICATION OPERATIONS
|
||||
// ============================================================================
|
||||
@@ -899,14 +918,16 @@ func secp256k1_schnorrsig_sha256_tagged(sha *secp256k1_sha256) {
|
||||
|
||||
// secp256k1_schnorrsig_challenge computes challenge hash
|
||||
func secp256k1_schnorrsig_challenge(e *secp256k1_scalar, r32 []byte, msg []byte, msglen int, pubkey32 []byte) {
|
||||
// Optimized challenge computation - avoid allocations by writing directly to hash
|
||||
// Optimized challenge computation using pre-allocated hash context to avoid allocations
|
||||
var challengeHash [32]byte
|
||||
|
||||
// First hash: SHA256(tag)
|
||||
tagHash := sha256.Sum256(bip340ChallengeTag)
|
||||
|
||||
// Second hash: SHA256(SHA256(tag) || SHA256(tag) || r32 || pubkey32 || msg)
|
||||
h := sha256.New()
|
||||
// Use pre-allocated hash context to avoid allocations
|
||||
h := getChallengeHashContext()
|
||||
h.Reset()
|
||||
h.Write(tagHash[:]) // SHA256(tag)
|
||||
h.Write(tagHash[:]) // SHA256(tag) again
|
||||
h.Write(r32[:32]) // r32
|
||||
|
||||
Reference in New Issue
Block a user