From 034c1ab2d157964513518e27d232d40a01ffae0e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 21:05:48 +0000 Subject: [PATCH] fix: replace ThreadLocal with KMP-compatible ScratchLocal expect/actual ThreadLocal.withInitial is a JVM-only API that doesn't compile on Kotlin/Native or iOS targets. Replace all usages in commonMain (FieldP.kt, Point.kt) with a new ScratchLocal expect/actual: - jvmMain/androidMain: delegates to java.lang.ThreadLocal (same behavior) - nativeMain: holds value directly (Kotlin/Native coroutines are cooperative, scratch buffers don't need thread isolation) https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg --- .../utils/secp256k1/ScratchLocal.android.kt | 30 ++++++++++++++++ .../quartz/utils/secp256k1/FieldP.kt | 4 +-- .../quartz/utils/secp256k1/Point.kt | 2 +- .../quartz/utils/secp256k1/ScratchLocal.kt | 35 +++++++++++++++++++ .../utils/secp256k1/ScratchLocal.jvm.kt | 30 ++++++++++++++++ .../utils/secp256k1/ScratchLocal.native.kt | 34 ++++++++++++++++++ 6 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.android.kt create mode 100644 quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.kt create mode 100644 quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.jvm.kt create mode 100644 quartz/src/nativeMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.native.kt diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.android.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.android.kt new file mode 100644 index 000000000..7f6f78eb6 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.android.kt @@ -0,0 +1,30 @@ +/* + * 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: delegates to java.lang.ThreadLocal for per-thread scratch buffers. */ +internal actual class ScratchLocal actual constructor( + initializer: () -> T, +) { + private val tl = ThreadLocal.withInitial(initializer) + + actual fun get(): T = tl.get() +} 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 19ad7bd9e..c4f2b3efc 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 @@ -48,11 +48,11 @@ internal object FieldP { -1L, // 0xFFFFFFFFFFFFFFFF ) - private val wide = ThreadLocal.withInitial { LongArray(8) } + private val wide = ScratchLocal { LongArray(8) } // Pre-allocated scratch for inv/sqrt addition chains (11 field elements). // Avoids 11 LongArray(4) allocations per inv/sqrt call. - private val chainScratch = ThreadLocal.withInitial { Array(11) { LongArray(4) } } + private val chainScratch = ScratchLocal { Array(11) { LongArray(4) } } /** Get a thread-local wide buffer. Call once at the top-level entry point, then pass through. */ fun getWide(): LongArray = wide.get() diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/Point.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/Point.kt index b2654303d..7d0070c43 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/Point.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/Point.kt @@ -348,7 +348,7 @@ internal object ECPoint { val batchZInv3 = LongArray(4) } - private val scratch = ThreadLocal.withInitial { PointScratch() } + private val scratch = ScratchLocal { PointScratch() } /** Get thread-local scratch. Call once at the top-level entry point. */ internal fun getScratch(): PointScratch = scratch.get() diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.kt new file mode 100644 index 000000000..5c1356758 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.kt @@ -0,0 +1,35 @@ +/* + * 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 + +/** + * KMP-compatible thread-local storage. + * + * On JVM/Android: delegates to java.lang.ThreadLocal for true per-thread isolation. + * On Native: single-threaded assumption — just holds the value directly. + * (Kotlin/Native has its own threading model; for the secp256k1 use case, + * scratch buffers don't need thread isolation since coroutines are cooperative.) + */ +internal expect class ScratchLocal( + initializer: () -> T, +) { + fun get(): T +} diff --git a/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.jvm.kt b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.jvm.kt new file mode 100644 index 000000000..e3d10e904 --- /dev/null +++ b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.jvm.kt @@ -0,0 +1,30 @@ +/* + * 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: delegates to java.lang.ThreadLocal for per-thread scratch buffers. */ +internal actual class ScratchLocal actual constructor( + initializer: () -> T, +) { + private val tl = ThreadLocal.withInitial(initializer) + + actual fun get(): T = tl.get() +} diff --git a/quartz/src/nativeMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.native.kt b/quartz/src/nativeMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.native.kt new file mode 100644 index 000000000..fd0391349 --- /dev/null +++ b/quartz/src/nativeMain/kotlin/com/vitorpamplona/quartz/utils/secp256k1/ScratchLocal.native.kt @@ -0,0 +1,34 @@ +/* + * 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: holds value directly (no thread-local needed). + * Kotlin/Native coroutines are cooperative — scratch buffers are safe to share + * within a single thread's call stack. + */ +internal actual class ScratchLocal actual constructor( + initializer: () -> T, +) { + private val value: T = initializer() + + actual fun get(): T = value +}