From ab3a9076e381d9e9e7cd0baed400b6fff6c3e84c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 22:27:37 +0000 Subject: [PATCH] perf: eliminate ULong.constructor-impl NOOP calls in unsigned comparisons Bytecode analysis revealed that every `a.toULong() < b.toULong()` comparison generates 2 invokestatic calls to ULong.constructor-impl (NOOPs that return the input unchanged) plus Long.compareUnsigned. Across all secp256k1 hot paths, this produced 554 NOOP invokestatic calls in the bytecode, translating to ~18,000 wasted calls per verify. Replace all toULong() comparisons with an inline uLt() helper that uses the XOR-with-MIN_VALUE trick directly: (a xor Long.MIN_VALUE) < (b xor Long.MIN_VALUE) This produces pure arithmetic bytecode (lxor, lcmp, ifge) with ZERO method calls, eliminating all ULong.constructor-impl overhead. Before: lload, invokestatic ULong.constructor-impl, lload, invokestatic ULong.constructor-impl, invokestatic Long.compareUnsigned, ifge (6 bytecodes, 3 method calls) After: lload, ldc MIN_VALUE, lxor, lload, ldc MIN_VALUE, lxor, lcmp, ifge (8 bytecodes, 0 method calls) https://claude.ai/code/session_01EMY5RnXb9rnsyU2KbXrSaY --- .../utils/secp256k1/FieldMulPlatform.kt | 106 +++++++------- .../quartz/utils/secp256k1/FieldP.kt | 44 +++--- .../quartz/utils/secp256k1/Glv.kt | 2 +- .../quartz/utils/secp256k1/ScalarN.kt | 14 +- .../quartz/utils/secp256k1/U256.kt | 136 ++++++++++-------- 5 files changed, 162 insertions(+), 140 deletions(-) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.kt index e33d92de7..380f22fd2 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.kt @@ -110,19 +110,19 @@ internal inline fun fieldMulReduceWith( lo = a0 * b1 s = lo + carry - c1 = if (s.toULong() < lo.toULong()) 1L else 0L + c1 = if (uLt(s, lo)) 1L else 0L w[1] = s carry = umulh(a0, b1) + c1 lo = a0 * b2 s = lo + carry - c1 = if (s.toULong() < lo.toULong()) 1L else 0L + c1 = if (uLt(s, lo)) 1L else 0L w[2] = s carry = umulh(a0, b2) + c1 lo = a0 * b3 s = lo + carry - c1 = if (s.toULong() < lo.toULong()) 1L else 0L + c1 = if (uLt(s, lo)) 1L else 0L w[3] = s w[4] = umulh(a0, b3) + c1 @@ -131,7 +131,7 @@ internal inline fun fieldMulReduceWith( hi = umulh(a1, b0) prev = w[1] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L w[1] = s carry = hi + c1 @@ -139,9 +139,9 @@ internal inline fun fieldMulReduceWith( hi = umulh(a1, b1) prev = w[2] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L w[2] = s carry = hi + c1 + c2 @@ -149,9 +149,9 @@ internal inline fun fieldMulReduceWith( hi = umulh(a1, b2) prev = w[3] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L w[3] = s carry = hi + c1 + c2 @@ -159,9 +159,9 @@ internal inline fun fieldMulReduceWith( hi = umulh(a1, b3) prev = w[4] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L w[4] = s w[5] = hi + c1 + c2 @@ -170,7 +170,7 @@ internal inline fun fieldMulReduceWith( hi = umulh(a2, b0) prev = w[2] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L w[2] = s carry = hi + c1 @@ -178,9 +178,9 @@ internal inline fun fieldMulReduceWith( hi = umulh(a2, b1) prev = w[3] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L w[3] = s carry = hi + c1 + c2 @@ -188,9 +188,9 @@ internal inline fun fieldMulReduceWith( hi = umulh(a2, b2) prev = w[4] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L w[4] = s carry = hi + c1 + c2 @@ -198,9 +198,9 @@ internal inline fun fieldMulReduceWith( hi = umulh(a2, b3) prev = w[5] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L w[5] = s w[6] = hi + c1 + c2 @@ -209,7 +209,7 @@ internal inline fun fieldMulReduceWith( hi = umulh(a3, b0) prev = w[3] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L w[3] = s carry = hi + c1 @@ -217,9 +217,9 @@ internal inline fun fieldMulReduceWith( hi = umulh(a3, b1) prev = w[4] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L w[4] = s carry = hi + c1 + c2 @@ -227,9 +227,9 @@ internal inline fun fieldMulReduceWith( hi = umulh(a3, b2) prev = w[5] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L w[5] = s carry = hi + c1 + c2 @@ -237,9 +237,9 @@ internal inline fun fieldMulReduceWith( hi = umulh(a3, b3) prev = w[6] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L w[6] = s w[7] = hi + c1 + c2 @@ -280,13 +280,13 @@ internal inline fun fieldSqrReduceWith( lo = a0 * a2 s = lo + carry - c1 = if (s.toULong() < lo.toULong()) 1L else 0L + c1 = if (uLt(s, lo)) 1L else 0L w[2] = s carry = umulh(a0, a2) + c1 lo = a0 * a3 s = lo + carry - c1 = if (s.toULong() < lo.toULong()) 1L else 0L + c1 = if (uLt(s, lo)) 1L else 0L w[3] = s w[4] = umulh(a0, a3) + c1 @@ -294,7 +294,7 @@ internal inline fun fieldSqrReduceWith( hi = umulh(a1, a2) prev = w[3] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L w[3] = s carry = hi + c1 @@ -302,9 +302,9 @@ internal inline fun fieldSqrReduceWith( hi = umulh(a1, a3) prev = w[4] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L w[4] = s w[5] = hi + c1 + c2 @@ -312,7 +312,7 @@ internal inline fun fieldSqrReduceWith( hi = umulh(a2, a3) prev = w[5] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L w[5] = s w[6] = hi + c1 @@ -342,44 +342,44 @@ internal inline fun fieldSqrReduceWith( hi = umulh(a0, a0) w[0] = lo s = w[1] + hi - c1 = if (s.toULong() < w[1].toULong()) 1L else 0L + c1 = if (uLt(s, w[1])) 1L else 0L w[1] = s var dCarry = c1 lo = a1 * a1 hi = umulh(a1, a1) s = w[2] + lo - c1 = if (s.toULong() < w[2].toULong()) 1L else 0L + c1 = if (uLt(s, w[2])) 1L else 0L s += dCarry - c2 = if (s.toULong() < dCarry.toULong()) 1L else 0L + c2 = if (uLt(s, dCarry)) 1L else 0L w[2] = s prev = w[3] + hi - val c3a = if (prev.toULong() < w[3].toULong()) 1L else 0L + val c3a = if (uLt(prev, w[3])) 1L else 0L prev += c1 + c2 - val c4a = if (prev.toULong() < (c1 + c2).toULong()) 1L else 0L + val c4a = if (uLt(prev, c1 + c2)) 1L else 0L w[3] = prev dCarry = c3a + c4a lo = a2 * a2 hi = umulh(a2, a2) s = w[4] + lo - c1 = if (s.toULong() < w[4].toULong()) 1L else 0L + c1 = if (uLt(s, w[4])) 1L else 0L s += dCarry - c2 = if (s.toULong() < dCarry.toULong()) 1L else 0L + c2 = if (uLt(s, dCarry)) 1L else 0L w[4] = s prev = w[5] + hi - val c3b = if (prev.toULong() < w[5].toULong()) 1L else 0L + val c3b = if (uLt(prev, w[5])) 1L else 0L prev += c1 + c2 - val c4b = if (prev.toULong() < (c1 + c2).toULong()) 1L else 0L + val c4b = if (uLt(prev, c1 + c2)) 1L else 0L w[5] = prev dCarry = c3b + c4b lo = a3 * a3 hi = umulh(a3, a3) s = w[6] + lo - c1 = if (s.toULong() < w[6].toULong()) 1L else 0L + c1 = if (uLt(s, w[6])) 1L else 0L s += dCarry - c2 = if (s.toULong() < dCarry.toULong()) 1L else 0L + c2 = if (uLt(s, dCarry)) 1L else 0L w[6] = s prev = w[7] + hi prev += c1 + c2 @@ -412,34 +412,34 @@ internal inline fun reduceWideInline( hcLo = w[4] * c hcHi = umulh(w[4], c) s1 = w[0] + hcLo - c1 = if (s1.toULong() < w[0].toULong()) 1L else 0L + c1 = if (uLt(s1, w[0])) 1L else 0L out[0] = s1 var carry = hcHi + c1 hcLo = w[5] * c hcHi = umulh(w[5], c) s1 = w[1] + hcLo - c1 = if (s1.toULong() < w[1].toULong()) 1L else 0L + c1 = if (uLt(s1, w[1])) 1L else 0L s2 = s1 + carry - c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + c2 = if (uLt(s2, s1)) 1L else 0L out[1] = s2 carry = hcHi + c1 + c2 hcLo = w[6] * c hcHi = umulh(w[6], c) s1 = w[2] + hcLo - c1 = if (s1.toULong() < w[2].toULong()) 1L else 0L + c1 = if (uLt(s1, w[2])) 1L else 0L s2 = s1 + carry - c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + c2 = if (uLt(s2, s1)) 1L else 0L out[2] = s2 carry = hcHi + c1 + c2 hcLo = w[7] * c hcHi = umulh(w[7], c) s1 = w[3] + hcLo - c1 = if (s1.toULong() < w[3].toULong()) 1L else 0L + c1 = if (uLt(s1, w[3])) 1L else 0L s2 = s1 + carry - c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + c2 = if (uLt(s2, s1)) 1L else 0L out[3] = s2 carry = hcHi + c1 + c2 @@ -448,27 +448,27 @@ internal inline fun reduceWideInline( val ccLo = carry * c val ccHi = umulh(carry, c) s1 = out[0] + ccLo - c1 = if (s1.toULong() < out[0].toULong()) 1L else 0L + c1 = if (uLt(s1, out[0])) 1L else 0L out[0] = s1 var prop = ccHi + c1 if (prop != 0L) { s1 = out[1] + prop - prop = if (s1.toULong() < out[1].toULong()) 1L else 0L + prop = if (uLt(s1, out[1])) 1L else 0L out[1] = s1 if (prop != 0L) { s1 = out[2] + prop - prop = if (s1.toULong() < out[2].toULong()) 1L else 0L + prop = if (uLt(s1, out[2])) 1L else 0L out[2] = s1 if (prop != 0L) { s1 = out[3] + prop - prop = if (s1.toULong() < out[3].toULong()) 1L else 0L + prop = if (uLt(s1, out[3])) 1L else 0L out[3] = s1 } } } if (prop != 0L) { s1 = out[0] + c - c1 = if (s1.toULong() < out[0].toULong()) 1L else 0L + c1 = if (uLt(s1, out[0])) 1L else 0L out[0] = s1 if (c1 != 0L) { out[1]++ diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldP.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldP.kt index 75cfab789..fec7200e5 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldP.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldP.kt @@ -68,7 +68,7 @@ internal object FieldP { if (carry != 0) { // Overflow past 2^256: add 2^256 mod p = 2^32 + 977 = 0x1000003D1 val s1 = out[0] + 4294968273L - val c1 = if (s1.toULong() < out[0].toULong()) 1L else 0L + val c1 = if (uLt(s1, out[0])) 1L else 0L out[0] = s1 if (c1 != 0L) { out[1]++ @@ -95,7 +95,7 @@ internal object FieldP { 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 + val c0 = if (uLt(s0, out[0])) 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) @@ -173,7 +173,7 @@ internal object FieldP { } // 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 + val borrow = if (uLt(P0, a[0])) 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 @@ -200,28 +200,28 @@ internal object FieldP { // Conditional add: out = a + (P & mask), unrolled // Limb 0 s1 = a[0] + p0 - c1 = if (s1.toULong() < a[0].toULong()) 1L else 0L + c1 = if (uLt(s1, a[0])) 1L else 0L out[0] = s1 var carry = c1 // Limb 1 s1 = a[1] + mask - c1 = if (s1.toULong() < a[1].toULong()) 1L else 0L + c1 = if (uLt(s1, a[1])) 1L else 0L s2 = s1 + carry - c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + c2 = if (uLt(s2, s1)) 1L else 0L out[1] = s2 carry = c1 + c2 // Limb 2 s1 = a[2] + mask - c1 = if (s1.toULong() < a[2].toULong()) 1L else 0L + c1 = if (uLt(s1, a[2])) 1L else 0L s2 = s1 + carry - c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + c2 = if (uLt(s2, s1)) 1L else 0L out[2] = s2 carry = c1 + c2 // Limb 3 s1 = a[3] + mask - c1 = if (s1.toULong() < a[3].toULong()) 1L else 0L + c1 = if (uLt(s1, a[3])) 1L else 0L s2 = s1 + carry - c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + c2 = if (uLt(s2, s1)) 1L else 0L out[3] = s2 carry = c1 + c2 @@ -405,7 +405,7 @@ internal object FieldP { hcLo = w[4] * c hcHi = unsignedMultiplyHigh(w[4], c) s1 = w[0] + hcLo - c1 = if (s1.toULong() < w[0].toULong()) 1L else 0L + c1 = if (uLt(s1, w[0])) 1L else 0L out[0] = s1 var carry = hcHi + c1 @@ -413,9 +413,9 @@ internal object FieldP { hcLo = w[5] * c hcHi = unsignedMultiplyHigh(w[5], c) s1 = w[1] + hcLo - c1 = if (s1.toULong() < w[1].toULong()) 1L else 0L + c1 = if (uLt(s1, w[1])) 1L else 0L s2 = s1 + carry - c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + c2 = if (uLt(s2, s1)) 1L else 0L out[1] = s2 carry = hcHi + c1 + c2 @@ -423,9 +423,9 @@ internal object FieldP { hcLo = w[6] * c hcHi = unsignedMultiplyHigh(w[6], c) s1 = w[2] + hcLo - c1 = if (s1.toULong() < w[2].toULong()) 1L else 0L + c1 = if (uLt(s1, w[2])) 1L else 0L s2 = s1 + carry - c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + c2 = if (uLt(s2, s1)) 1L else 0L out[2] = s2 carry = hcHi + c1 + c2 @@ -433,9 +433,9 @@ internal object FieldP { hcLo = w[7] * c hcHi = unsignedMultiplyHigh(w[7], c) s1 = w[3] + hcLo - c1 = if (s1.toULong() < w[3].toULong()) 1L else 0L + c1 = if (uLt(s1, w[3])) 1L else 0L s2 = s1 + carry - c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + c2 = if (uLt(s2, s1)) 1L else 0L out[3] = s2 carry = hcHi + c1 + c2 @@ -444,21 +444,21 @@ internal object FieldP { val ccLo = carry * c val ccHi = unsignedMultiplyHigh(carry, c) s1 = out[0] + ccLo - c1 = if (s1.toULong() < out[0].toULong()) 1L else 0L + c1 = if (uLt(s1, out[0])) 1L else 0L out[0] = s1 // Propagate carry (unrolled, with early exit) var prop = ccHi + c1 if (prop != 0L) { s1 = out[1] + prop - prop = if (s1.toULong() < out[1].toULong()) 1L else 0L + prop = if (uLt(s1, out[1])) 1L else 0L out[1] = s1 if (prop != 0L) { s1 = out[2] + prop - prop = if (s1.toULong() < out[2].toULong()) 1L else 0L + prop = if (uLt(s1, out[2])) 1L else 0L out[2] = s1 if (prop != 0L) { s1 = out[3] + prop - prop = if (s1.toULong() < out[3].toULong()) 1L else 0L + prop = if (uLt(s1, out[3])) 1L else 0L out[3] = s1 } } @@ -466,7 +466,7 @@ internal object FieldP { // Overflow past 256 bits: 2^256 ≡ C (mod p) if (prop != 0L) { s1 = out[0] + c - c1 = if (s1.toULong() < out[0].toULong()) 1L else 0L + c1 = if (uLt(s1, out[0])) 1L else 0L out[0] = s1 if (c1 != 0L) { out[1]++ diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/Glv.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/Glv.kt index ff2dfd6ac..95d933c9d 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/Glv.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/Glv.kt @@ -224,7 +224,7 @@ internal object Glv { for (i in limb until s.size) { val old = s[i] s[i] = old + if (i == limb) addVal else 1L - if (s[i].toULong() >= old.toULong() || (i == limb && addVal == 0L)) break + if (!uLt(s[i], old) || (i == limb && addVal == 0L)) break // overflowed — carry to next limb } } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScalarN.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScalarN.kt index 04932711b..daf4bb107 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScalarN.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScalarN.kt @@ -198,9 +198,9 @@ internal object ScalarN { 0L } val s1 = hiTimesNC[i] + loVal - val c1 = if (s1.toULong() < hiTimesNC[i].toULong()) 1L else 0L + val c1 = if (uLt(s1, hiTimesNC[i])) 1L else 0L val s2 = s1 + carry - val c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + val c2 = if (uLt(s2, s1)) 1L else 0L w[i] = s2 carry = c1 + c2 } @@ -238,9 +238,9 @@ internal object ScalarN { saved3 } val s1 = loVal + hi2NC[i] - val c1 = if (s1.toULong() < loVal.toULong()) 1L else 0L + val c1 = if (uLt(s1, loVal)) 1L else 0L val s2 = s1 + c2 - val cc = if (s2.toULong() < s1.toULong()) 1L else 0L + val cc = if (uLt(s2, s1)) 1L else 0L out[i] = s2 c2 = c1 + cc } @@ -252,13 +252,13 @@ internal object ScalarN { val c1lo = ov * N_COMPLEMENT[1] val c1hi = unsignedMultiplyHigh(ov, N_COMPLEMENT[1]) val s0 = out[0] + c0lo - val carry0 = if (s0.toULong() < out[0].toULong()) 1L else 0L + val carry0 = if (uLt(s0, out[0])) 1L else 0L out[0] = s0 val s1 = out[1] + c0hi + c1lo + carry0 - val carry1 = if (s1.toULong() < out[1].toULong()) 1L else 0L + val carry1 = if (uLt(s1, out[1])) 1L else 0L out[1] = s1 val s2 = out[2] + c1hi + ov + carry1 - val carry2 = if (s2.toULong() < out[2].toULong()) 1L else 0L + val carry2 = if (uLt(s2, out[2])) 1L else 0L out[2] = s2 out[3] += carry2 } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/U256.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/U256.kt index cb79e60a4..8f06c06a1 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/U256.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/U256.kt @@ -45,6 +45,28 @@ package com.vitorpamplona.quartz.utils.secp256k1 // Package structure: U256 → FieldP/ScalarN → Glv/KeyCodec → Point → Secp256k1 // ===================================================================================== +/** + * Unsigned less-than comparison without ULong inline class overhead. + * + * Kotlin's `uLt(a, b)` generates 2 invokestatic calls to + * ULong.constructor-impl (NOOPs that return the input unchanged) plus + * Long.compareUnsigned per comparison. On ART, these extra invokestatic + * calls add ~2-3ns each × ~18,000 comparisons per verify = ~36-54μs. + * + * This inline function uses the XOR-with-MIN_VALUE trick directly, + * producing pure arithmetic bytecode with ZERO method calls: + * lload a, ldc MIN_VALUE, lxor, lload b, ldc MIN_VALUE, lxor, lcmp, ifge + * + * vs the toULong() path: + * lload a, invokestatic ULong.constructor-impl, lload b, + * invokestatic ULong.constructor-impl, invokestatic Long.compareUnsigned, ifge + */ +@Suppress("NOTHING_TO_INLINE") +internal inline fun uLt( + a: Long, + b: Long, +): Boolean = (a xor Long.MIN_VALUE) < (b xor Long.MIN_VALUE) + /** * Raw 256-bit unsigned integer arithmetic using 4×64-bit limbs. */ @@ -58,7 +80,7 @@ internal object U256 { ): Int { for (i in 3 downTo 0) { if (a[i] != b[i]) { - return if (a[i].toULong() < b[i].toULong()) -1 else 1 + return if (uLt(a[i], b[i])) -1 else 1 } } return 0 @@ -77,31 +99,31 @@ internal object U256 { // Limb 0 (no carry input) s1 = a[0] + b[0] - c1 = if (s1.toULong() < a[0].toULong()) 1L else 0L + c1 = if (uLt(s1, a[0])) 1L else 0L out[0] = s1 var carry = c1 // Limb 1 s1 = a[1] + b[1] - c1 = if (s1.toULong() < a[1].toULong()) 1L else 0L + c1 = if (uLt(s1, a[1])) 1L else 0L s2 = s1 + carry - c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + c2 = if (uLt(s2, s1)) 1L else 0L out[1] = s2 carry = c1 + c2 // Limb 2 s1 = a[2] + b[2] - c1 = if (s1.toULong() < a[2].toULong()) 1L else 0L + c1 = if (uLt(s1, a[2])) 1L else 0L s2 = s1 + carry - c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + c2 = if (uLt(s2, s1)) 1L else 0L out[2] = s2 carry = c1 + c2 // Limb 3 s1 = a[3] + b[3] - c1 = if (s1.toULong() < a[3].toULong()) 1L else 0L + c1 = if (uLt(s1, a[3])) 1L else 0L s2 = s1 + carry - c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + c2 = if (uLt(s2, s1)) 1L else 0L out[3] = s2 carry = c1 + c2 @@ -121,31 +143,31 @@ internal object U256 { // Limb 0 (no borrow input) d1 = a[0] - b[0] - c1 = if (a[0].toULong() < b[0].toULong()) 1L else 0L + c1 = if (uLt(a[0], b[0])) 1L else 0L out[0] = d1 var borrow = c1 // Limb 1 d1 = a[1] - b[1] - c1 = if (a[1].toULong() < b[1].toULong()) 1L else 0L + c1 = if (uLt(a[1], b[1])) 1L else 0L d2 = d1 - borrow - c2 = if (d1.toULong() < borrow.toULong()) 1L else 0L + c2 = if (uLt(d1, borrow)) 1L else 0L out[1] = d2 borrow = c1 + c2 // Limb 2 d1 = a[2] - b[2] - c1 = if (a[2].toULong() < b[2].toULong()) 1L else 0L + c1 = if (uLt(a[2], b[2])) 1L else 0L d2 = d1 - borrow - c2 = if (d1.toULong() < borrow.toULong()) 1L else 0L + c2 = if (uLt(d1, borrow)) 1L else 0L out[2] = d2 borrow = c1 + c2 // Limb 3 d1 = a[3] - b[3] - c1 = if (a[3].toULong() < b[3].toULong()) 1L else 0L + c1 = if (uLt(a[3], b[3])) 1L else 0L d2 = d1 - borrow - c2 = if (d1.toULong() < borrow.toULong()) 1L else 0L + c2 = if (uLt(d1, borrow)) 1L else 0L out[3] = d2 borrow = c1 + c2 @@ -187,19 +209,19 @@ internal object U256 { lo = a0 * b1 s = lo + carry - c1 = if (s.toULong() < lo.toULong()) 1L else 0L + c1 = if (uLt(s, lo)) 1L else 0L out[1] = s carry = unsignedMultiplyHigh(a0, b1) + c1 lo = a0 * b2 s = lo + carry - c1 = if (s.toULong() < lo.toULong()) 1L else 0L + c1 = if (uLt(s, lo)) 1L else 0L out[2] = s carry = unsignedMultiplyHigh(a0, b2) + c1 lo = a0 * b3 s = lo + carry - c1 = if (s.toULong() < lo.toULong()) 1L else 0L + c1 = if (uLt(s, lo)) 1L else 0L out[3] = s out[4] = unsignedMultiplyHigh(a0, b3) + c1 @@ -208,7 +230,7 @@ internal object U256 { hi = unsignedMultiplyHigh(a1, b0) prev = out[1] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L out[1] = s carry = hi + c1 @@ -216,9 +238,9 @@ internal object U256 { hi = unsignedMultiplyHigh(a1, b1) prev = out[2] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L out[2] = s carry = hi + c1 + c2 @@ -226,9 +248,9 @@ internal object U256 { hi = unsignedMultiplyHigh(a1, b2) prev = out[3] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L out[3] = s carry = hi + c1 + c2 @@ -236,9 +258,9 @@ internal object U256 { hi = unsignedMultiplyHigh(a1, b3) prev = out[4] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L out[4] = s out[5] = hi + c1 + c2 @@ -247,7 +269,7 @@ internal object U256 { hi = unsignedMultiplyHigh(a2, b0) prev = out[2] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L out[2] = s carry = hi + c1 @@ -255,9 +277,9 @@ internal object U256 { hi = unsignedMultiplyHigh(a2, b1) prev = out[3] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L out[3] = s carry = hi + c1 + c2 @@ -265,9 +287,9 @@ internal object U256 { hi = unsignedMultiplyHigh(a2, b2) prev = out[4] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L out[4] = s carry = hi + c1 + c2 @@ -275,9 +297,9 @@ internal object U256 { hi = unsignedMultiplyHigh(a2, b3) prev = out[5] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L out[5] = s out[6] = hi + c1 + c2 @@ -286,7 +308,7 @@ internal object U256 { hi = unsignedMultiplyHigh(a3, b0) prev = out[3] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L out[3] = s carry = hi + c1 @@ -294,9 +316,9 @@ internal object U256 { hi = unsignedMultiplyHigh(a3, b1) prev = out[4] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L out[4] = s carry = hi + c1 + c2 @@ -304,9 +326,9 @@ internal object U256 { hi = unsignedMultiplyHigh(a3, b2) prev = out[5] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L out[5] = s carry = hi + c1 + c2 @@ -314,9 +336,9 @@ internal object U256 { hi = unsignedMultiplyHigh(a3, b3) prev = out[6] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L out[6] = s out[7] = hi + c1 + c2 } @@ -353,13 +375,13 @@ internal object U256 { lo = a0 * a2 s = lo + carry - c1 = if (s.toULong() < lo.toULong()) 1L else 0L + c1 = if (uLt(s, lo)) 1L else 0L out[2] = s carry = unsignedMultiplyHigh(a0, a2) + c1 lo = a0 * a3 s = lo + carry - c1 = if (s.toULong() < lo.toULong()) 1L else 0L + c1 = if (uLt(s, lo)) 1L else 0L out[3] = s out[4] = unsignedMultiplyHigh(a0, a3) + c1 @@ -368,7 +390,7 @@ internal object U256 { hi = unsignedMultiplyHigh(a1, a2) prev = out[3] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L out[3] = s carry = hi + c1 @@ -376,9 +398,9 @@ internal object U256 { hi = unsignedMultiplyHigh(a1, a3) prev = out[4] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L s += carry - c2 = if (s.toULong() < carry.toULong()) 1L else 0L + c2 = if (uLt(s, carry)) 1L else 0L out[4] = s out[5] = hi + c1 + c2 @@ -387,7 +409,7 @@ internal object U256 { hi = unsignedMultiplyHigh(a2, a3) prev = out[5] s = prev + lo - c1 = if (s.toULong() < prev.toULong()) 1L else 0L + c1 = if (uLt(s, prev)) 1L else 0L out[5] = s out[6] = hi + c1 @@ -419,7 +441,7 @@ internal object U256 { hi = unsignedMultiplyHigh(a0, a0) out[0] = lo // out[0] was 0 s = out[1] + hi - c1 = if (s.toULong() < out[1].toULong()) 1L else 0L + c1 = if (uLt(s, out[1])) 1L else 0L out[1] = s var dCarry = c1 @@ -427,14 +449,14 @@ internal object U256 { lo = a1 * a1 hi = unsignedMultiplyHigh(a1, a1) s = out[2] + lo - c1 = if (s.toULong() < out[2].toULong()) 1L else 0L + c1 = if (uLt(s, out[2])) 1L else 0L s += dCarry - c2 = if (s.toULong() < dCarry.toULong()) 1L else 0L + c2 = if (uLt(s, dCarry)) 1L else 0L out[2] = s prev = out[3] + hi - val c3a = if (prev.toULong() < out[3].toULong()) 1L else 0L + val c3a = if (uLt(prev, out[3])) 1L else 0L prev += c1 + c2 - val c4a = if (prev.toULong() < (c1 + c2).toULong()) 1L else 0L + val c4a = if (uLt(prev, c1 + c2)) 1L else 0L out[3] = prev dCarry = c3a + c4a @@ -442,14 +464,14 @@ internal object U256 { lo = a2 * a2 hi = unsignedMultiplyHigh(a2, a2) s = out[4] + lo - c1 = if (s.toULong() < out[4].toULong()) 1L else 0L + c1 = if (uLt(s, out[4])) 1L else 0L s += dCarry - c2 = if (s.toULong() < dCarry.toULong()) 1L else 0L + c2 = if (uLt(s, dCarry)) 1L else 0L out[4] = s prev = out[5] + hi - val c3b = if (prev.toULong() < out[5].toULong()) 1L else 0L + val c3b = if (uLt(prev, out[5])) 1L else 0L prev += c1 + c2 - val c4b = if (prev.toULong() < (c1 + c2).toULong()) 1L else 0L + val c4b = if (uLt(prev, c1 + c2)) 1L else 0L out[5] = prev dCarry = c3b + c4b @@ -457,12 +479,12 @@ internal object U256 { lo = a3 * a3 hi = unsignedMultiplyHigh(a3, a3) s = out[6] + lo - c1 = if (s.toULong() < out[6].toULong()) 1L else 0L + c1 = if (uLt(s, out[6])) 1L else 0L s += dCarry - c2 = if (s.toULong() < dCarry.toULong()) 1L else 0L + c2 = if (uLt(s, dCarry)) 1L else 0L out[6] = s prev = out[7] + hi - val c3c = if (prev.toULong() < out[7].toULong()) 1L else 0L + val c3c = if (uLt(prev, out[7])) 1L else 0L prev += c1 + c2 out[7] = prev }