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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,6 +23,27 @@ var zeroMask = [32]byte{
|
|||||||
170, 247, 175, 105, 39, 10, 165, 20,
|
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
|
// NonceFunctionBIP340 implements BIP-340 nonce generation
|
||||||
func NonceFunctionBIP340(nonce32 []byte, msg []byte, key32 []byte, xonlyPk32 []byte, auxRand32 []byte) error {
|
func NonceFunctionBIP340(nonce32 []byte, msg []byte, key32 []byte, xonlyPk32 []byte, auxRand32 []byte) error {
|
||||||
if len(nonce32) != 32 {
|
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.
|
// SchnorrVerify verifies a Schnorr signature following BIP-340.
|
||||||
// This is the new implementation translated from C secp256k1_schnorrsig_verify.
|
// 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 {
|
func SchnorrVerify(sig64 []byte, msg32 []byte, xonlyPubkey *XOnlyPubkey) bool {
|
||||||
if len(sig64) != 64 {
|
if len(sig64) != 64 {
|
||||||
return false
|
return false
|
||||||
@@ -306,11 +329,8 @@ func SchnorrVerify(sig64 []byte, msg32 []byte, xonlyPubkey *XOnlyPubkey) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a context (required by secp256k1_schnorrsig_verify)
|
// Use precomputed context (initialized once, reused across calls)
|
||||||
ctx := &secp256k1_context{
|
ctx := getSchnorrVerifyContext()
|
||||||
ecmult_gen_ctx: secp256k1_ecmult_gen_context{built: 1},
|
|
||||||
declassify: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert x-only pubkey to secp256k1_xonly_pubkey format
|
// Convert x-only pubkey to secp256k1_xonly_pubkey format
|
||||||
var secp_xonly secp256k1_xonly_pubkey
|
var secp_xonly secp256k1_xonly_pubkey
|
||||||
|
|||||||
@@ -236,3 +236,39 @@ func TestSchnorrMultipleSignatures(t *testing.T) {
|
|||||||
t.Error("with different aux_rand, signatures should differ")
|
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 (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"hash"
|
||||||
|
"sync"
|
||||||
"unsafe"
|
"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)
|
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
|
// EC MULTIPLICATION OPERATIONS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -899,14 +918,16 @@ func secp256k1_schnorrsig_sha256_tagged(sha *secp256k1_sha256) {
|
|||||||
|
|
||||||
// secp256k1_schnorrsig_challenge computes challenge hash
|
// secp256k1_schnorrsig_challenge computes challenge hash
|
||||||
func secp256k1_schnorrsig_challenge(e *secp256k1_scalar, r32 []byte, msg []byte, msglen int, pubkey32 []byte) {
|
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
|
var challengeHash [32]byte
|
||||||
|
|
||||||
// First hash: SHA256(tag)
|
// First hash: SHA256(tag)
|
||||||
tagHash := sha256.Sum256(bip340ChallengeTag)
|
tagHash := sha256.Sum256(bip340ChallengeTag)
|
||||||
|
|
||||||
// Second hash: SHA256(SHA256(tag) || SHA256(tag) || r32 || pubkey32 || msg)
|
// 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)
|
||||||
h.Write(tagHash[:]) // SHA256(tag) again
|
h.Write(tagHash[:]) // SHA256(tag) again
|
||||||
h.Write(r32[:32]) // r32
|
h.Write(r32[:32]) // r32
|
||||||
|
|||||||
Reference in New Issue
Block a user