perf: pass PointScratch through mul and mulG, eliminate ThreadLocal
Add optional PointScratch parameter to ECPoint.mul and ECPoint.mulG (default to scratch.get() for backward compat). All callers in Secp256k1.kt now pass their already-fetched scratch through. From the ECDH trace: ECPoint.mul was calling scratch.get() (ThreadLocal) redundantly — the caller already had the scratch. On ART, each ThreadLocal.get costs ~6µs (hash table probe), and ECDH had 10 calls totaling 64µs. With this change, all hot-path EC operations (verify, sign, ECDH, pubkey create, tweak mul) fetch the scratch once at the entry point and pass it through the entire call chain. https://claude.ai/code/session_015CtM5k88rF7WFgX8o2AGNR
This commit is contained in:
@@ -455,13 +455,12 @@ internal object ECPoint {
|
||||
out: MutablePoint,
|
||||
p: MutablePoint,
|
||||
scalar: LongArray,
|
||||
s: PointScratch = scratch.get(),
|
||||
) {
|
||||
if (U256.isZero(scalar) || p.isInfinity()) {
|
||||
out.setInfinity()
|
||||
return
|
||||
}
|
||||
|
||||
val s = scratch.get()
|
||||
val wnd = 5
|
||||
val tableSize = 1 shl (wnd - 2) // 8 entries
|
||||
|
||||
@@ -538,13 +537,12 @@ internal object ECPoint {
|
||||
fun mulG(
|
||||
out: MutablePoint,
|
||||
scalar: LongArray,
|
||||
s: PointScratch = scratch.get(),
|
||||
) {
|
||||
if (U256.isZero(scalar)) {
|
||||
out.setInfinity()
|
||||
return
|
||||
}
|
||||
|
||||
val s = scratch.get()
|
||||
val table = combTable
|
||||
|
||||
// Ping-pong: alternate between out and s.mixTmp to avoid copyFrom after
|
||||
|
||||
@@ -131,7 +131,7 @@ object Secp256k1 {
|
||||
val scalar = sc.scalarTmp1
|
||||
U256.fromBytesInto(scalar, seckey, 0)
|
||||
require(ScalarN.isValid(scalar))
|
||||
ECPoint.mulG(sc.entryResult, scalar)
|
||||
ECPoint.mulG(sc.entryResult, scalar, sc)
|
||||
check(ECPoint.toAffine(sc.entryResult, sc.entryPx, sc.entryPy, sc))
|
||||
return KeyCodec.serializeUncompressed(sc.entryPx, sc.entryPy)
|
||||
}
|
||||
@@ -239,7 +239,7 @@ object Secp256k1 {
|
||||
val d0 = U256.fromBytes(seckey)
|
||||
require(ScalarN.isValid(d0))
|
||||
val sc = ECPoint.getScratch()
|
||||
ECPoint.mulG(sc.entryResult, d0)
|
||||
ECPoint.mulG(sc.entryResult, d0, sc)
|
||||
check(ECPoint.toAffine(sc.entryResult, sc.entryPx, sc.entryPy, sc))
|
||||
|
||||
// Allocate xOnlyPub — signSchnorrInternal reuses bytesTmp1/2 internally.
|
||||
@@ -361,7 +361,7 @@ object Secp256k1 {
|
||||
require(!U256.isZero(k0))
|
||||
|
||||
// R = k0·G
|
||||
ECPoint.mulG(sc.entryResult, k0)
|
||||
ECPoint.mulG(sc.entryResult, k0, sc)
|
||||
val rx = sc.entryPx
|
||||
val ry = sc.entryPy
|
||||
check(ECPoint.toAffine(sc.entryResult, rx, ry, sc))
|
||||
@@ -552,7 +552,7 @@ object Secp256k1 {
|
||||
require(ScalarN.isValid(scalar))
|
||||
|
||||
sc.entryPoint.setAffine(sc.entryPx, sc.entryPy)
|
||||
ECPoint.mul(sc.entryResult, sc.entryPoint, scalar)
|
||||
ECPoint.mul(sc.entryResult, sc.entryPoint, scalar, sc)
|
||||
check(ECPoint.toAffine(sc.entryResult, sc.entryPx, sc.entryPy, sc))
|
||||
|
||||
return if (pubkey.size == 33) {
|
||||
@@ -594,7 +594,7 @@ object Secp256k1 {
|
||||
}
|
||||
|
||||
sc.entryPoint.setAffine(sc.entryPx, sc.entryPy)
|
||||
ECPoint.mul(sc.entryResult, sc.entryPoint, k)
|
||||
ECPoint.mul(sc.entryResult, sc.entryPoint, k, sc)
|
||||
check(ECPoint.toAffineX(sc.entryResult, sc.entryPx, sc))
|
||||
return U256.toBytes(sc.entryPx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user