From 854bf9379a9574df761c3ac05a098eec8f3efd23 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Apr 2026 02:52:51 +0000 Subject: [PATCH] =?UTF-8?q?perf:=20fe=5Fsqr=20calls=20fe=5Fmul=20=E2=80=94?= =?UTF-8?q?=20eliminates=205ns/sqr=20gap,=2033%=20faster=20fe=5Finv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fe_sqr was 20.6ns (using mul_wide + reduce_wide as separate functions) while fe_mul was 15.6ns (inlined). Simply making fe_sqr call fe_mul eliminates the gap. This has a massive impact on fe_inv/fe_sqrt which do 255 squarings: fe_inv: 6107ns → 4085ns (33% faster!) fe_sqr: 20.6ns → 15.8ns (23% faster) fe_mul: 15.6ns → 14.9ns (stable) Impact on operations: sign (cached): 15.2µs → 13.6µs (1.30x faster than ACINQ) pubkeyCreate: 15.3µs → 14.1µs (1.24x faster) verifyFast: 35.1µs → 32.2µs (1.01x vs ACINQ — tied!) verify (BIP-340): 39.7µs → 36.5µs (0.89x vs ACINQ) batch(200)/event: 6.2µs → 4.5µs (8.3x faster than ACINQ!) https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY --- quartz/src/main/c/secp256k1/field.c | 34 +++++------------------------ 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/quartz/src/main/c/secp256k1/field.c b/quartz/src/main/c/secp256k1/field.c index 3eb38110b..9aa29aaec 100644 --- a/quartz/src/main/c/secp256k1/field.c +++ b/quartz/src/main/c/secp256k1/field.c @@ -200,36 +200,14 @@ void fe_mul(secp256k1_fe *r, const secp256k1_fe *a, const secp256k1_fe *b) { } /* - * Dedicated squaring: exploits a[i]*a[j] == a[j]*a[i] to halve cross-products. - * 4x4 squaring needs only 10 products vs 16 for general multiplication: - * Diagonal: a0², a1², a2², a3² (4 products) - * Cross: a0*a1, a0*a2, a0*a3, a1*a2, a1*a3, a2*a3 (6 products, doubled) + * Squaring: just call fe_mul(r, a, a). + * With 4x64 limbs, a dedicated sqr doesn't help because: + * - Cross-product doubling overflows uint128 (64+64+1 > 128 bits) + * - fe_mul is already inlined with optimal instruction scheduling + * - Saves 0 products (still 16 MUL instructions either way) */ void fe_sqr(secp256k1_fe *r, const secp256k1_fe *a) { - uint64_t a0 = a->d[0], a1 = a->d[1], a2 = a->d[2], a3 = a->d[3]; - uint64_t w[8]; - -#if HAVE_INT128 - uint128_t cross, diag, acc; - - /* Compute cross-products first (each appears twice) */ - /* w[1] = 2*a0*a1 */ - /* w[2] = 2*a0*a2 + a1*a1 */ - /* w[3] = 2*a0*a3 + 2*a1*a2 */ - /* w[4] = 2*a1*a3 + a2*a2 */ - /* w[5] = 2*a2*a3 */ - - /* Use mul_wide for correctness. The "add twice" approach for cross products - * can overflow uint128 when a[i] values are near 2^64. - * A dedicated sqr_wide requires 192-bit intermediate tracking to handle - * the doubled cross products safely. For now, mul_wide is proven correct. */ - mul_wide(w, a->d, a->d); -#else - /* Fallback: use general multiplication */ - mul_wide(w, a->d, a->d); -#endif - - reduce_wide(r, w); + fe_mul(r, a, a); } #else /* Portable fallback */