perf: signSchnorr 4.8x faster — remove self-verify, add cached-pubkey path

Analysis of the C library's schnorrsig_sign showed three key differences:
1. C takes a pre-computed keypair (no pubkey derivation during signing)
2. C does NOT self-verify the signature after signing
3. C does 1 EC multiplication total; we were doing 3

Changes:
- signSchnorrInternal: extracted core signing logic without pubkey derivation
  or self-verification. Does exactly 1 mulG (for R = k·G) matching the C library.
- signSchnorr: convenience overload that derives pubkey then calls internal.
  Now ~2x faster since self-verify is removed.
- signSchnorrWithPubKey: fast path accepting a 33-byte compressed pubkey
  (includes y-parity in the 02/03 prefix). Skips the pubkey G multiplication
  entirely. ~4.8x faster than the previous signSchnorr.
- Secp256k1Instance: added signSchnorrWithPubKey forwarding method.
- Benchmark: added "signSchnorr (cached pk)" test comparing both paths.

The self-verify removal is safe: the BIP-340 test vectors (which include
the exact expected signatures) validate correctness, and the C reference
library does not self-verify either.

Benchmark:
  signSchnorr:            1,516 → 2,915 ops/s (1.9x faster, 17x → 9.5x)
  signSchnorr (cached pk):  N/A → 7,261 ops/s (3.9x vs native — new!)

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
This commit is contained in:
Claude
2026-04-05 21:12:59 +00:00
parent 2dab449d62
commit 629450fed2
3 changed files with 80 additions and 17 deletions
@@ -146,7 +146,7 @@ class Secp256k1Benchmark {
},
)
// --- signSchnorr ---
// --- signSchnorr (derives pubkey from seckey each time) ---
results +=
bench(
name = "signSchnorr",
@@ -162,6 +162,23 @@ class Secp256k1Benchmark {
},
)
// --- signSchnorrWithPubKey (pre-computed pubkey, no self-verify — matches C) ---
results +=
bench(
name = "signSchnorr (cached pk)",
warmup = 10,
iterations = 50,
nativeOp = { native.signSchnorr(msg32, privKey, auxRand) },
kotlinOp = {
com.vitorpamplona.quartz.utils.secp256k1.Secp256k1.signSchnorrWithPubKey(
msg32,
privKey,
nativePubKey,
auxRand,
)
},
)
// --- pubkeyCreate ---
results +=
bench(