From b71641a15ff1ed08da6c696c25e73b2eba69c802 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 11 Apr 2026 12:38:03 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20fair=20C-to-C=20benchmark=20=E2=80=94=20?= =?UTF-8?q?ACINQ=20sign=20must=20include=20keypair=5Fcreate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous benchmark unfairly gave ACINQ a cached keypair (created once outside the loop) while our sign derived the pubkey each call. Fixed to test both patterns: - "sign (full)": both derive pubkey each call ACINQ: keypair_create + sign32 = 36.3µs Ours: ecmult_gen + sign_internal = 33.6µs → 1.08x faster - "sign (cached)": both reuse precomputed pubkey ACINQ: sign32 with cached keypair = 18.8µs Ours: signXOnly with cached xonly = 17.4µs → 1.08x faster Fair native C-to-C results (x86_64, BMI2): pubkeyCreate: ACINQ 18.0 Ours 16.3 1.10x faster ✓ sign (full): ACINQ 36.3 Ours 33.6 1.08x faster ✓ sign (cached): ACINQ 18.8 Ours 17.4 1.08x faster ✓ verify (BIP-340): ACINQ 36.7 Ours 40.4 0.91x slower ✗ verifyFast (Nostr): ACINQ 36.7 Ours 34.5 1.06x faster ✓ ECDH (cached): ACINQ 37.2 Ours 35.4 1.05x faster ✓ batch(200): ACINQ 42.4 Ours 8.3 5.1x faster ✓ We beat ACINQ on 5 of 6 operations. The only loss is full BIP-340 verify (0.91x) due to ACINQ's 5x52+ADCX/ADOX field assembly. https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY --- quartz/src/main/c/secp256k1/bench_vs_acinq.c | 24 +++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/quartz/src/main/c/secp256k1/bench_vs_acinq.c b/quartz/src/main/c/secp256k1/bench_vs_acinq.c index 790802a9d..faef2313e 100644 --- a/quartz/src/main/c/secp256k1/bench_vs_acinq.c +++ b/quartz/src/main/c/secp256k1/bench_vs_acinq.c @@ -144,12 +144,14 @@ int main(void) { double our_pubkey = (now_us() - t0) / N; printf("%-30s %10.1f %10.1f %8.2fx\n", "pubkeyCreate", acinq_pubkey, our_pubkey, acinq_pubkey/our_pubkey); - /* --- signSchnorr --- */ + /* --- signSchnorr (FAIR: both derive pubkey each call) --- */ N = 5000; t0 = now_us(); for (int i = 0; i < N; i++) { uint8_t s[64]; - secp256k1_schnorrsig_sign32(acinq_ctx, s, msg, &acinq_kp, NULL); + secp256k1_keypair kp_fresh; + secp256k1_keypair_create(acinq_ctx, &kp_fresh, PRIVKEY); + secp256k1_schnorrsig_sign32(acinq_ctx, s, msg, &kp_fresh, NULL); } double acinq_sign = (now_us() - t0) / N; @@ -159,7 +161,23 @@ int main(void) { secp256k1c_schnorr_sign(s, msg, 32, PRIVKEY, NULL); } double our_sign = (now_us() - t0) / N; - printf("%-30s %10.1f %10.1f %8.2fx\n", "signSchnorr", acinq_sign, our_sign, acinq_sign/our_sign); + printf("%-30s %10.1f %10.1f %8.2fx\n", "sign (full, derive pubkey)", acinq_sign, our_sign, acinq_sign/our_sign); + + /* --- signSchnorr (CACHED: both reuse precomputed pubkey) --- */ + t0 = now_us(); + for (int i = 0; i < N; i++) { + uint8_t s[64]; + secp256k1_schnorrsig_sign32(acinq_ctx, s, msg, &acinq_kp, NULL); + } + double acinq_sign_cached = (now_us() - t0) / N; + + t0 = now_us(); + for (int i = 0; i < N; i++) { + uint8_t s[64]; + secp256k1c_schnorr_sign_xonly(s, msg, 32, PRIVKEY, our_xonly, NULL); + } + double our_sign_cached = (now_us() - t0) / N; + printf("%-30s %10.1f %10.1f %8.2fx\n", "sign (cached pubkey)", acinq_sign_cached, our_sign_cached, acinq_sign_cached/our_sign_cached); /* --- verifySchnorr (ACINQ = always full BIP-340) --- */ N = 5000;