diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.android.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.android.kt new file mode 100644 index 000000000..393ebd13d --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.android.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.utils.secp256k1 + +/** + * Android field multiply/square with API-level-gated intrinsics. + * + * Dispatches ONCE per call to an API-level-specific implementation. Each implementation + * uses fieldMulReduceWith/fieldSqrReduceWith (inline) with the best available intrinsic + * inlined directly at the call site. This eliminates the per-multiply-high wrapper call + * overhead that hurts ART's JIT: + * + * Before: FieldP.mul → U256.mulWide → unsignedMultiplyHigh (branch) → Math.xxx + * = 2 extra function calls per multiply-high × 20 per field mul = 40 calls + * + * After: FieldP.mul → fieldMulReduce → (one of the inlined paths) + * = 1 function call total, Math.xxx is inlined directly into the loop + * + * The API level check is the outermost branch (not inside the hot loop), so ART + * profiles and JIT-compiles only the hot path. + */ + +private val API = android.os.Build.VERSION.SDK_INT + +@Suppress("NewApi") +internal actual fun fieldMulReduce( + out: LongArray, + a: LongArray, + b: LongArray, + w: LongArray, +) { + if (API >= 35) { + fieldMulReduceWith(out, a, b, w) { x, y -> Math.unsignedMultiplyHigh(x, y) } + } else if (API >= 31) { + fieldMulReduceWith(out, a, b, w) { x, y -> + Math.multiplyHigh(x, y) + (x and (y shr 63)) + (y and (x shr 63)) + } + } else { + fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) } + } +} + +@Suppress("NewApi") +internal actual fun fieldSqrReduce( + out: LongArray, + a: LongArray, + w: LongArray, +) { + if (API >= 35) { + fieldSqrReduceWith(out, a, w) { x, y -> Math.unsignedMultiplyHigh(x, y) } + } else if (API >= 31) { + fieldSqrReduceWith(out, a, w) { x, y -> + Math.multiplyHigh(x, y) + (x and (y shr 63)) + (y and (x shr 63)) + } + } else { + fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) } + } +} 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 new file mode 100644 index 000000000..e33d92de7 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.kt @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.utils.secp256k1 + +// ===================================================================================== +// FUSED FIELD MULTIPLY + REDUCE — PLATFORM-SPECIFIC INTRINSIC DISPATCH +// ===================================================================================== +// +// On Android, the unsignedMultiplyHigh wrapper has per-call branching for API levels +// (API 35+: Math.unsignedMultiplyHigh, API 31-34: Math.multiplyHigh + correction, +// API <31: pure-Kotlin fallback). ART's JIT may not inline this wrapper due to: +// 1. Branch complexity exceeding inline-candidate size threshold +// 2. Limited inlining depth (~3 levels vs HotSpot's 8+) +// +// This fused approach eliminates ALL wrapper overhead by: +// 1. Dispatching once per call to fieldMulReduce (not per-multiply) +// 2. Inlining the chosen intrinsic into the hot loop via crossinline lambda +// 3. Combining mulWide + reduceWide into one compilation unit for the JIT +// +// The inline functions below contain the full schoolbook multiplication and mod-p +// reduction. Since they're inline, the crossinline lambda is substituted at each +// call site, producing platform-specific code with zero function call overhead +// for the multiply-high operation. +// +// Impact: eliminates ~20 wrapper calls per field multiply × ~500 field muls per +// verify = ~10,000 function calls removed from the hot path. +// ===================================================================================== + +/** + * Fused field multiplication: out = (a × b) mod p. + * Platform implementations call fieldMulReduceWith with the best available intrinsic. + */ +internal expect fun fieldMulReduce( + out: LongArray, + a: LongArray, + b: LongArray, + w: LongArray, +) + +/** + * Fused field squaring: out = a² mod p. + * Platform implementations call fieldSqrReduceWith with the best available intrinsic. + */ +internal expect fun fieldSqrReduce( + out: LongArray, + a: LongArray, + w: LongArray, +) + +// P[0] constant for reduceSelf (duplicated here because inline functions can't +// access private members of other objects). +private const val FIELD_P0 = -4294968273L // 0xFFFFFFFEFFFFFC2F + +/** + * Fused 4×4 schoolbook multiplication + mod-p reduction. + * + * Combines U256.mulWide and FieldP.reduceWide into a single inline function. + * The umulh parameter is inlined at each call site, producing platform-specific + * code with direct intrinsic calls and zero wrapper overhead. + * + * @param umulh Returns the upper 64 bits of the unsigned 128-bit product. + */ +@Suppress("LongMethod") +internal inline fun fieldMulReduceWith( + out: LongArray, + a: LongArray, + b: LongArray, + w: LongArray, + umulh: (Long, Long) -> Long, +) { + // === Stage 1: 4×4 schoolbook multiplication (16 products) === + val a0 = a[0] + val a1 = a[1] + val a2 = a[2] + val a3 = a[3] + val b0 = b[0] + val b1 = b[1] + val b2 = b[2] + val b3 = b[3] + var lo: Long + var hi: Long + var prev: Long + var s: Long + var c1: Long + var c2: Long + var carry: Long + + // Row 0: a0 × [b0,b1,b2,b3] + lo = a0 * b0 + w[0] = lo + carry = umulh(a0, b0) + + lo = a0 * b1 + s = lo + carry + c1 = if (s.toULong() < lo.toULong()) 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 + w[2] = s + carry = umulh(a0, b2) + c1 + + lo = a0 * b3 + s = lo + carry + c1 = if (s.toULong() < lo.toULong()) 1L else 0L + w[3] = s + w[4] = umulh(a0, b3) + c1 + + // Row 1: a1 × [b0,b1,b2,b3] + lo = a1 * b0 + hi = umulh(a1, b0) + prev = w[1] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + w[1] = s + carry = hi + c1 + + lo = a1 * b1 + hi = umulh(a1, b1) + prev = w[2] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + s += carry + c2 = if (s.toULong() < carry.toULong()) 1L else 0L + w[2] = s + carry = hi + c1 + c2 + + lo = a1 * b2 + hi = umulh(a1, b2) + prev = w[3] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + s += carry + c2 = if (s.toULong() < carry.toULong()) 1L else 0L + w[3] = s + carry = hi + c1 + c2 + + lo = a1 * b3 + hi = umulh(a1, b3) + prev = w[4] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + s += carry + c2 = if (s.toULong() < carry.toULong()) 1L else 0L + w[4] = s + w[5] = hi + c1 + c2 + + // Row 2: a2 × [b0,b1,b2,b3] + lo = a2 * b0 + hi = umulh(a2, b0) + prev = w[2] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + w[2] = s + carry = hi + c1 + + lo = a2 * b1 + hi = umulh(a2, b1) + prev = w[3] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + s += carry + c2 = if (s.toULong() < carry.toULong()) 1L else 0L + w[3] = s + carry = hi + c1 + c2 + + lo = a2 * b2 + hi = umulh(a2, b2) + prev = w[4] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + s += carry + c2 = if (s.toULong() < carry.toULong()) 1L else 0L + w[4] = s + carry = hi + c1 + c2 + + lo = a2 * b3 + hi = umulh(a2, b3) + prev = w[5] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + s += carry + c2 = if (s.toULong() < carry.toULong()) 1L else 0L + w[5] = s + w[6] = hi + c1 + c2 + + // Row 3: a3 × [b0,b1,b2,b3] + lo = a3 * b0 + hi = umulh(a3, b0) + prev = w[3] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + w[3] = s + carry = hi + c1 + + lo = a3 * b1 + hi = umulh(a3, b1) + prev = w[4] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + s += carry + c2 = if (s.toULong() < carry.toULong()) 1L else 0L + w[4] = s + carry = hi + c1 + c2 + + lo = a3 * b2 + hi = umulh(a3, b2) + prev = w[5] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + s += carry + c2 = if (s.toULong() < carry.toULong()) 1L else 0L + w[5] = s + carry = hi + c1 + c2 + + lo = a3 * b3 + hi = umulh(a3, b3) + prev = w[6] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + s += carry + c2 = if (s.toULong() < carry.toULong()) 1L else 0L + w[6] = s + w[7] = hi + c1 + c2 + + // === Stage 2: mod-p reduction (2^256 ≡ 2^32 + 977 mod p) === + reduceWideInline(out, w, umulh) +} + +/** + * Fused squaring + mod-p reduction (10 products via symmetry). + * + * @param umulh Returns the upper 64 bits of the unsigned 128-bit product. + */ +@Suppress("LongMethod") +internal inline fun fieldSqrReduceWith( + out: LongArray, + a: LongArray, + w: LongArray, + umulh: (Long, Long) -> Long, +) { + val a0 = a[0] + val a1 = a[1] + val a2 = a[2] + val a3 = a[3] + var lo: Long + var hi: Long + var prev: Long + var s: Long + var c1: Long + var c2: Long + var carry: Long + var v: Long + + // Pass 1: cross-products a[i]*a[j] for i < j + w[0] = 0L + lo = a0 * a1 + w[1] = lo + carry = umulh(a0, a1) + + lo = a0 * a2 + s = lo + carry + c1 = if (s.toULong() < lo.toULong()) 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 + w[3] = s + w[4] = umulh(a0, a3) + c1 + + lo = a1 * a2 + hi = umulh(a1, a2) + prev = w[3] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + w[3] = s + carry = hi + c1 + + lo = a1 * a3 + hi = umulh(a1, a3) + prev = w[4] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + s += carry + c2 = if (s.toULong() < carry.toULong()) 1L else 0L + w[4] = s + w[5] = hi + c1 + c2 + + lo = a2 * a3 + hi = umulh(a2, a3) + prev = w[5] + s = prev + lo + c1 = if (s.toULong() < prev.toULong()) 1L else 0L + w[5] = s + w[6] = hi + c1 + + // Pass 2: double all cross-products (shift left by 1 bit) + v = w[1] + w[1] = v shl 1 + var shiftCarry = v ushr 63 + v = w[2] + w[2] = (v shl 1) or shiftCarry + shiftCarry = v ushr 63 + v = w[3] + w[3] = (v shl 1) or shiftCarry + shiftCarry = v ushr 63 + v = w[4] + w[4] = (v shl 1) or shiftCarry + shiftCarry = v ushr 63 + v = w[5] + w[5] = (v shl 1) or shiftCarry + shiftCarry = v ushr 63 + v = w[6] + w[6] = (v shl 1) or shiftCarry + shiftCarry = v ushr 63 + w[7] = shiftCarry + + // Pass 3: add diagonal products a[i]² + lo = a0 * a0 + hi = umulh(a0, a0) + w[0] = lo + s = w[1] + hi + c1 = if (s.toULong() < w[1].toULong()) 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 + s += dCarry + c2 = if (s.toULong() < dCarry.toULong()) 1L else 0L + w[2] = s + prev = w[3] + hi + val c3a = if (prev.toULong() < w[3].toULong()) 1L else 0L + prev += c1 + c2 + val c4a = if (prev.toULong() < (c1 + c2).toULong()) 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 + s += dCarry + c2 = if (s.toULong() < dCarry.toULong()) 1L else 0L + w[4] = s + prev = w[5] + hi + val c3b = if (prev.toULong() < w[5].toULong()) 1L else 0L + prev += c1 + c2 + val c4b = if (prev.toULong() < (c1 + c2).toULong()) 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 + s += dCarry + c2 = if (s.toULong() < dCarry.toULong()) 1L else 0L + w[6] = s + prev = w[7] + hi + prev += c1 + c2 + w[7] = prev + + // === Reduction === + reduceWideInline(out, w, umulh) +} + +/** + * Inline mod-p reduction of 512-bit value. Shared by fieldMulReduceWith and fieldSqrReduceWith. + * + * Uses 2^256 ≡ 2^32 + 977 (mod p) to fold high limbs into low limbs. + */ +@Suppress("LongMethod") +internal inline fun reduceWideInline( + out: LongArray, + w: LongArray, + umulh: (Long, Long) -> Long, +) { + val c = 4294968273L // 2^32 + 977 + var hcLo: Long + var hcHi: Long + var s1: Long + var s2: Long + var c1: Long + var c2: Long + + // Round 1: acc = lo + hi × C + hcLo = w[4] * c + hcHi = umulh(w[4], c) + s1 = w[0] + hcLo + c1 = if (s1.toULong() < w[0].toULong()) 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 + s2 = s1 + carry + c2 = if (s2.toULong() < s1.toULong()) 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 + s2 = s1 + carry + c2 = if (s2.toULong() < s1.toULong()) 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 + s2 = s1 + carry + c2 = if (s2.toULong() < s1.toULong()) 1L else 0L + out[3] = s2 + carry = hcHi + c1 + c2 + + // Round 2: fold carry × C + if (carry != 0L) { + val ccLo = carry * c + val ccHi = umulh(carry, c) + s1 = out[0] + ccLo + c1 = if (s1.toULong() < out[0].toULong()) 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 + out[1] = s1 + if (prop != 0L) { + s1 = out[2] + prop + prop = if (s1.toULong() < out[2].toULong()) 1L else 0L + out[2] = s1 + if (prop != 0L) { + s1 = out[3] + prop + prop = if (s1.toULong() < out[3].toULong()) 1L else 0L + out[3] = s1 + } + } + } + if (prop != 0L) { + s1 = out[0] + c + c1 = if (s1.toULong() < out[0].toULong()) 1L else 0L + out[0] = s1 + if (c1 != 0L) { + out[1]++ + if (out[1] == 0L) { + out[2]++ + if (out[2] == 0L) out[3]++ + } + } + } + } + + // Final normalization: ensure out < p + if (out[3] == -1L && out[2] == -1L && out[1] == -1L && + (out[0] xor Long.MIN_VALUE) >= (FIELD_P0 xor Long.MIN_VALUE) + ) { + out[0] -= FIELD_P0 + out[1] = 0L + out[2] = 0L + out[3] = 0L + } +} 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 0a119a682..75cfab789 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 @@ -124,8 +124,7 @@ internal object FieldP { b: LongArray, ) { val w = wide.get() - U256.mulWide(w, a, b) - reduceWide(out, w) + fieldMulReduce(out, a, b, w) } /** Multiply with caller-provided wide buffer (hot path — no ThreadLocal lookup). */ @@ -135,8 +134,7 @@ internal object FieldP { b: LongArray, w: LongArray, ) { - U256.mulWide(w, a, b) - reduceWide(out, w) + fieldMulReduce(out, a, b, w) } /** Square with ThreadLocal wide buffer (convenience for non-hot paths). */ @@ -145,8 +143,7 @@ internal object FieldP { a: LongArray, ) { val w = wide.get() - U256.sqrWide(w, a) - reduceWide(out, w) + fieldSqrReduce(out, a, w) } /** Square with caller-provided wide buffer (hot path — no ThreadLocal lookup). */ @@ -155,8 +152,7 @@ internal object FieldP { a: LongArray, w: LongArray, ) { - U256.sqrWide(w, a) - reduceWide(out, w) + fieldSqrReduce(out, a, w) } /** diff --git a/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.jvm.kt b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.jvm.kt new file mode 100644 index 000000000..4bc5cb104 --- /dev/null +++ b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.jvm.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.utils.secp256k1 + +/** + * JVM fused field multiply/square using Math.unsignedMultiplyHigh (Java 18+). + * HotSpot C2 already inlines unsignedMultiplyHigh well, but the fused path + * still helps by eliminating the mulWide → reduceWide call boundary. + */ +internal actual fun fieldMulReduce( + out: LongArray, + a: LongArray, + b: LongArray, + w: LongArray, +) { + fieldMulReduceWith(out, a, b, w) { x, y -> Math.unsignedMultiplyHigh(x, y) } +} + +internal actual fun fieldSqrReduce( + out: LongArray, + a: LongArray, + w: LongArray, +) { + fieldSqrReduceWith(out, a, w) { x, y -> Math.unsignedMultiplyHigh(x, y) } +} diff --git a/quartz/src/nativeMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.native.kt b/quartz/src/nativeMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.native.kt new file mode 100644 index 000000000..84946c832 --- /dev/null +++ b/quartz/src/nativeMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/FieldMulPlatform.native.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.utils.secp256k1 + +/** + * Native (iOS/macOS) fused field multiply/square using pure-Kotlin fallback. + * Kotlin/Native compiles ahead-of-time, so inline + direct call is optimal. + */ +internal actual fun fieldMulReduce( + out: LongArray, + a: LongArray, + b: LongArray, + w: LongArray, +) { + fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) } +} + +internal actual fun fieldSqrReduce( + out: LongArray, + a: LongArray, + w: LongArray, +) { + fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) } +}