fix: fair C-to-C benchmark — ACINQ sign must include keypair_create

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
This commit is contained in:
Claude
2026-04-11 12:38:03 +00:00
parent 2f5152efd6
commit b71641a15f
+21 -3
View File
@@ -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;