From 494ca22bdf00a30a081dc8f804d457b281524e70 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 19:28:48 +0000 Subject: [PATCH] perf: pre-allocate inv/sqrt addition chain scratch via ThreadLocal inv() and sqrt() each allocated 11 LongArray(4) (plus 2 for sqrt verification) on every call. These are now served from a thread-local Array(11) { LongArray(4) } cache, eliminating 22-24 allocations per ECDH operation (inv called in toAffine, sqrt called in liftX). Also reuses chain scratch slots for sqrt's verification step instead of allocating separate check/ar arrays. https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg --- .../quartz/utils/secp256k1/FieldP.kt | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldP.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldP.kt index 6c58407e6..f41205672 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldP.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldP.kt @@ -50,6 +50,10 @@ internal object FieldP { private val wide = ThreadLocal.withInitial { LongArray(8) } + // Pre-allocated scratch for inv/sqrt addition chains (11 field elements). + // Avoids 11 LongArray(4) allocations per inv/sqrt call. + private val chainScratch = ThreadLocal.withInitial { Array(11) { LongArray(4) } } + /** Get a thread-local wide buffer. Call once at the top-level entry point, then pass through. */ fun getWide(): LongArray = wide.get() @@ -172,17 +176,18 @@ internal object FieldP { ) { require(!U256.isZero(a)) val w = wide.get() - val x2 = LongArray(4) - val x3 = LongArray(4) - val x6 = LongArray(4) - val x9 = LongArray(4) - val x11 = LongArray(4) - val x22 = LongArray(4) - val x44 = LongArray(4) - val x88 = LongArray(4) - val x176 = LongArray(4) - val x220 = LongArray(4) - val x223 = LongArray(4) + val cs = chainScratch.get() + val x2 = cs[0] + val x3 = cs[1] + val x6 = cs[2] + val x9 = cs[3] + val x11 = cs[4] + val x22 = cs[5] + val x44 = cs[6] + val x88 = cs[7] + val x176 = cs[8] + val x220 = cs[9] + val x223 = cs[10] sqr(x2, a, w) mul(x2, x2, a, w) @@ -222,17 +227,18 @@ internal object FieldP { a: LongArray, ): Boolean { val w = wide.get() - val x2 = LongArray(4) - val x3 = LongArray(4) - val x6 = LongArray(4) - val x9 = LongArray(4) - val x11 = LongArray(4) - val x22 = LongArray(4) - val x44 = LongArray(4) - val x88 = LongArray(4) - val x176 = LongArray(4) - val x220 = LongArray(4) - val x223 = LongArray(4) + val cs = chainScratch.get() + val x2 = cs[0] + val x3 = cs[1] + val x6 = cs[2] + val x9 = cs[3] + val x11 = cs[4] + val x22 = cs[5] + val x44 = cs[6] + val x88 = cs[7] + val x176 = cs[8] + val x220 = cs[9] + val x223 = cs[10] sqr(x2, a, w) mul(x2, x2, a, w) @@ -263,12 +269,12 @@ internal object FieldP { mul(out, out, x2, w) sqrN(out, out, 2, w) - val check = LongArray(4) - mul(check, out, out, w) - val ar = LongArray(4) - U256.copyInto(ar, a) - reduceSelf(ar) - return U256.cmp(check, ar) == 0 + // Verify: check that out² == a (mod p) + // Reuse cs[0], cs[1] as scratch since we're done with the chain + mul(cs[0], out, out, w) // cs[0] = out² + U256.copyInto(cs[1], a) + reduceSelf(cs[1]) // cs[1] = a reduced + return U256.cmp(cs[0], cs[1]) == 0 } private fun sqrN(