perf: specialize FieldP.sub and neg for secp256k1 prime structure

Inline the P = [P0, -1, -1, -1] constant into sub and neg instead
of going through generic U256.addTo/subTo + P array reads.

For sub (add-back P on borrow): when carry from limb 0 is 1, adding
P[1..3]=-1 with carry is identity (no-op). When carry is 0, just
subtract 1 with borrow propagation through limbs 1-3. Replaces a
full 4-limb generic addition with a conditional decrement.

For neg (P - a): uses bitwise NOT for limbs 1-3 (since P[i]=-1,
P[i]-a[i] = ~a[i]) with simple borrow propagation from limb 0.
Replaces a full 4-limb generic subtraction with P array reads.

These are called ~500 times per verify. On JVM/HotSpot the gain is
marginal (inlining already handles this), but on Android ART the
reduced function call depth and eliminated array loads should help.

https://claude.ai/code/session_017UbWduFi1sLUsgVUMUH2nx
This commit is contained in:
Claude
2026-04-08 01:35:54 +00:00
parent f912520ee2
commit cf7e2d4b0d
@@ -81,13 +81,40 @@ internal object FieldP {
reduceSelf(out)
}
/**
* out = a - b mod p. Specialized add-back for P = [P0, -1, -1, -1]:
* adding -1 to limbs 1-3 with carry=1 is identity, so only the carry=0
* case needs work (subtract 1 with borrow propagation). ~500 calls/verify.
*/
fun sub(
out: LongArray,
a: LongArray,
b: LongArray,
) {
val borrow = U256.subTo(out, a, b)
if (borrow != 0) U256.addTo(out, out, P)
if (borrow != 0) {
// Add P = [P0, -1, -1, -1].
val s0 = out[0] + P0
val c0 = if (s0.toULong() < out[0].toULong()) 1L else 0L
out[0] = s0
// For limbs 1-3: adding P[i]=-1 with carry c:
// c=1 → result unchanged, carry out=1 (identity propagation)
// c=0 → result = out[i]-1, carry out = (out[i] != 0) ? 1 : 0
// So if c0=1, limbs 1-3 are untouched. If c0=0, subtract 1 with borrow:
if (c0 == 0L) {
if (out[1] != 0L) {
out[1]--
} else {
out[1] = -1L // 0-1 wraps
if (out[2] != 0L) {
out[2]--
} else {
out[2] = -1L
out[3]--
}
}
}
}
}
/** Multiply with ThreadLocal wide buffer (convenience for non-hot paths). */
@@ -132,15 +159,31 @@ internal object FieldP {
reduceWide(out, w)
}
/**
* out = -a mod p = P - a. Specialized for P = [P0, -1, -1, -1]:
* P[i]-a[i] = ~a[i] for i>=1 (bitwise NOT), with borrow from limb 0.
* Avoids generic U256.subTo + P array reads (~260 calls/verify).
*/
fun neg(
out: LongArray,
a: LongArray,
) {
if (U256.isZero(a)) {
for (i in 0 until 4) out[i] = 0L
} else {
U256.subTo(out, P, a)
out[0] = 0L
out[1] = 0L
out[2] = 0L
out[3] = 0L
return
}
// P - a: limb 0 is P0 - a[0], limbs 1-3 are (-1) - a[i] = ~a[i]
out[0] = P0 - a[0]
val borrow = if (a[0].toULong() > P0.toULong()) 1L else 0L
// ~a[i] - borrow. New borrow only if ~a[i] == 0 (i.e., a[i] == -1) and borrow == 1
out[1] = a[1].inv() - borrow
val b1 = if (a[1] == -1L && borrow != 0L) 1L else 0L
out[2] = a[2].inv() - b1
val b2 = if (a[2] == -1L && b1 != 0L) 1L else 0L
out[3] = a[3].inv() - b2
}
/**