perf: cache lambda(G) table, optimize P table build, reduce allocations
Three targeted optimizations in the verify hot path: 1. Precompute and cache lambda(G) table: the 8 AffinePoints for λ(G) odd-multiples are now lazily initialized once (like gTable) instead of recomputing 8 field multiplications per verify call. 2. Efficient P odd-multiples table: build [1P, 3P, 5P, ..., 15P] via 1 doubling + 7 additions (compute 2P then add repeatedly) instead of building all 16 multiples [1P..16P] with 15 additions and discarding the even ones. 3. Pre-allocated Jacobian negation scratch: the MutablePoint used for negating P-side table entries (when wNAF digit is negative) is now allocated once before the main loop instead of per-digit. Also removed the unused maybeNegateTable function. Benchmark: verifySchnorr 3,429 → 3,556 ops/s (7.2x vs native) https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
This commit is contained in:
@@ -163,6 +163,11 @@ internal object ECPoint {
|
|||||||
*/
|
*/
|
||||||
internal val gTable: Array<AffinePoint> by lazy { buildGTable() }
|
internal val gTable: Array<AffinePoint> by lazy { buildGTable() }
|
||||||
|
|
||||||
|
/** Precomputed λ(G) odd-multiples for GLV: gLamTable[i] = λ((2i+1)·G) as affine. */
|
||||||
|
private val gLamTable: Array<AffinePoint> by lazy {
|
||||||
|
Array(8) { AffinePoint(FieldP.mul(gTable[it * 2].x, BETA), gTable[it * 2].y.copyOf()) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildGTable(): Array<AffinePoint> {
|
private fun buildGTable(): Array<AffinePoint> {
|
||||||
val jac = Array(16) { MutablePoint() }
|
val jac = Array(16) { MutablePoint() }
|
||||||
jac[0].setAffine(GX, GY)
|
jac[0].setAffine(GX, GY)
|
||||||
@@ -682,24 +687,17 @@ internal object ECPoint {
|
|||||||
val wnafE1 = wnaf(eSplit.k1, w, 129)
|
val wnafE1 = wnaf(eSplit.k1, w, 129)
|
||||||
val wnafE2 = wnaf(eSplit.k2, w, 129)
|
val wnafE2 = wnaf(eSplit.k2, w, 129)
|
||||||
|
|
||||||
// G odd-multiples [1G, 3G, 5G, ..., 15G] (affine, from precomputed table)
|
// G tables: precomputed and cached (no per-verify allocation)
|
||||||
val gOddBase = Array(tableSize) { gTable[it * 2] }
|
val gOdd = Array(tableSize) { gTable[it * 2] }
|
||||||
// λ(G) odd-multiples: apply endomorphism (β·x, y) to each G table entry
|
val gLam = gLamTable
|
||||||
val gLamBase = Array(tableSize) { AffinePoint(FieldP.mul(gOddBase[it].x, BETA), gOddBase[it].y.copyOf()) }
|
|
||||||
|
|
||||||
// Apply GLV sign: if the split negated k₁/k₂, negate the corresponding table y-coords
|
// P odd-multiples [1P, 3P, 5P, ..., 15P] via 2P stepping (1 double + 7 adds)
|
||||||
// GLV sign is NOT baked into tables. Instead, we XOR the negation flag
|
val p2 = MutablePoint()
|
||||||
// with each wNAF digit's sign in the main loop. This avoids the
|
doublePoint(p2, p)
|
||||||
// double-negation bug where a negative wNAF digit + negated table cancel out.
|
val pOdd = Array(tableSize) { MutablePoint() }
|
||||||
val gOdd = gOddBase
|
pOdd[0].copyFrom(p)
|
||||||
val gLam = gLamBase
|
for (i in 1 until tableSize) addPoints(pOdd[i], pOdd[i - 1], p2)
|
||||||
|
// λ(P) table: (β·X, Y, Z) in Jacobian — endomorphism preserves projective coords
|
||||||
// P odd-multiples [1P, 3P, ..., 15P] as Jacobian (avoids expensive inversions)
|
|
||||||
val pAll = Array(16) { MutablePoint() }
|
|
||||||
pAll[0].copyFrom(p)
|
|
||||||
for (i in 1 until 16) addPoints(pAll[i], pAll[i - 1], pAll[0])
|
|
||||||
val pOdd = Array(tableSize) { pAll[it * 2] }
|
|
||||||
// λ(P) table: (β·X, Y, Z) in Jacobian — endomorphism works in projective coords
|
|
||||||
val pLamOdd =
|
val pLamOdd =
|
||||||
Array(tableSize) { i ->
|
Array(tableSize) { i ->
|
||||||
val lp = MutablePoint()
|
val lp = MutablePoint()
|
||||||
@@ -721,6 +719,7 @@ internal object ECPoint {
|
|||||||
out.setInfinity()
|
out.setInfinity()
|
||||||
val tmp = MutablePoint()
|
val tmp = MutablePoint()
|
||||||
val negY = IntArray(8)
|
val negY = IntArray(8)
|
||||||
|
val negJac = MutablePoint() // Reused scratch for Jacobian negation
|
||||||
|
|
||||||
for (i in bits - 1 downTo 0) {
|
for (i in bits - 1 downTo 0) {
|
||||||
doublePoint(out, out)
|
doublePoint(out, out)
|
||||||
@@ -728,19 +727,8 @@ internal object ECPoint {
|
|||||||
addWnafMixed(out, tmp, negY, wnafS1, i, gOdd, sSplit.negK1)
|
addWnafMixed(out, tmp, negY, wnafS1, i, gOdd, sSplit.negK1)
|
||||||
addWnafMixed(out, tmp, negY, wnafS2, i, gLam, sSplit.negK2)
|
addWnafMixed(out, tmp, negY, wnafS2, i, gLam, sSplit.negK2)
|
||||||
// Streams 3-4: P-side (Jacobian tables, full addition)
|
// Streams 3-4: P-side (Jacobian tables, full addition)
|
||||||
addWnafJacobian(out, tmp, wnafE1, i, pOdd, eSplit.negK1)
|
addWnafJacobian(out, tmp, negJac, wnafE1, i, pOdd, eSplit.negK1)
|
||||||
addWnafJacobian(out, tmp, wnafE2, i, pLamOdd, eSplit.negK2)
|
addWnafJacobian(out, tmp, negJac, wnafE2, i, pLamOdd, eSplit.negK2)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Conditionally negate all y-coordinates in an affine table. */
|
|
||||||
private fun maybeNegateTable(
|
|
||||||
table: Array<AffinePoint>,
|
|
||||||
negate: Boolean,
|
|
||||||
): Array<AffinePoint> {
|
|
||||||
if (!negate) return table
|
|
||||||
return Array(table.size) { i ->
|
|
||||||
AffinePoint(table[i].x.copyOf(), FieldP.neg(table[i].y))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -776,6 +764,7 @@ internal object ECPoint {
|
|||||||
private fun addWnafJacobian(
|
private fun addWnafJacobian(
|
||||||
out: MutablePoint,
|
out: MutablePoint,
|
||||||
tmp: MutablePoint,
|
tmp: MutablePoint,
|
||||||
|
negScratch: MutablePoint,
|
||||||
wnafDigits: IntArray,
|
wnafDigits: IntArray,
|
||||||
bitIndex: Int,
|
bitIndex: Int,
|
||||||
table: Array<MutablePoint>,
|
table: Array<MutablePoint>,
|
||||||
@@ -789,12 +778,11 @@ internal object ECPoint {
|
|||||||
if (!effectiveNeg) {
|
if (!effectiveNeg) {
|
||||||
addPoints(tmp, out, table[idx])
|
addPoints(tmp, out, table[idx])
|
||||||
} else {
|
} else {
|
||||||
// Negate the Jacobian point: (X, -Y, Z)
|
// Negate the Jacobian point: (X, -Y, Z) using pre-allocated scratch
|
||||||
val neg = MutablePoint()
|
table[idx].x.copyInto(negScratch.x)
|
||||||
table[idx].x.copyInto(neg.x)
|
FieldP.neg(negScratch.y, table[idx].y)
|
||||||
FieldP.neg(neg.y, table[idx].y)
|
table[idx].z.copyInto(negScratch.z)
|
||||||
table[idx].z.copyInto(neg.z)
|
addPoints(tmp, out, negScratch)
|
||||||
addPoints(tmp, out, neg)
|
|
||||||
}
|
}
|
||||||
out.copyFrom(tmp)
|
out.copyFrom(tmp)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user