perf: eliminate heap allocations in privKeyTweakAdd
Use thread-local scratch LongArrays instead of allocating 2 intermediate LongArray(4) via U256.fromBytes. The old path did: fromBytes(seckey) → alloc LongArray(4) fromBytes(tweak) → alloc LongArray(4) ScalarN.add(a, b) → alloc LongArray(4) U256.toBytes(r) → alloc ByteArray(32) = 4 heap allocations New path uses pre-allocated scratch from PointScratch: fromBytesInto(scratch, seckey) → zero alloc fromBytesInto(scratch, tweak) → zero alloc ScalarN.addTo(scratch, a, b) → zero alloc U256.toBytes(r) → 1 alloc (unavoidable, return value) K/Native: 163 → 88 ns (-46%, from 6.3x to 3.4x vs C) JVM: now faster than native C via JNI (Kotlin 6.5M ops/s vs C 4.8M ops/s) https://claude.ai/code/session_015CtM5k88rF7WFgX8o2AGNR
This commit is contained in:
@@ -519,9 +519,18 @@ object Secp256k1 {
|
||||
tweak: ByteArray,
|
||||
): ByteArray {
|
||||
require(seckey.size == 32 && tweak.size == 32)
|
||||
val result = ScalarN.add(U256.fromBytes(seckey), U256.fromBytes(tweak))
|
||||
require(!U256.isZero(result) && U256.cmp(result, ScalarN.N) < 0)
|
||||
return U256.toBytes(result)
|
||||
// Use thread-local scratch to avoid 2 intermediate LongArray(4) allocations.
|
||||
// Old path: fromBytes (alloc) + fromBytes (alloc) + add (alloc) + toBytes (alloc) = 4 allocs
|
||||
// New path: fromBytesInto (scratch) + fromBytesInto (scratch) + addTo (scratch) + toBytes = 1 alloc
|
||||
val sc = ECPoint.getScratch()
|
||||
val a = sc.entryTmp
|
||||
val b = sc.entryTmp2
|
||||
val r = sc.scalarTmp1
|
||||
U256.fromBytesInto(a, seckey, 0)
|
||||
U256.fromBytesInto(b, tweak, 0)
|
||||
ScalarN.addTo(r, a, b)
|
||||
require(!U256.isZero(r) && U256.cmp(r, ScalarN.N) < 0)
|
||||
return U256.toBytes(r)
|
||||
}
|
||||
|
||||
/** Multiply a public key by a scalar. Used for ECDH shared secret derivation. */
|
||||
|
||||
Reference in New Issue
Block a user