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
This commit is contained in:
Claude
2026-04-06 19:28:48 +00:00
parent 20bab17440
commit 494ca22bdf
@@ -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(