feat: 4×64-bit limb representation with Math.multiplyHigh (WIP — breaks build)
Foundation layer for the 4×64-bit limb optimization. This commit rewrites U256 from IntArray(8) to LongArray(4) and adds platform-specific Math.multiplyHigh for 64×64→128-bit products: - JVM: Math.multiplyHigh intrinsic (single IMULH/SMULH instruction) - Android API 31+: Math.multiplyHigh, fallback to pure Kotlin on older - Native: pure Kotlin fallback (4 sub-products per multiplyHigh call) The 4×64 representation reduces inner products from 64 to 16 per field multiply on JVM, a potential ~1.5-2× speedup on the critical path. NOTE: This commit intentionally breaks the build — FieldP, ScalarN, Glv, Point, KeyCodec, Secp256k1, and all tests still expect IntArray(8). They will be updated in subsequent commits. https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
This commit is contained in:
+35
@@ -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
|
||||
|
||||
/**
|
||||
* Android implementation. Uses Math.multiplyHigh on API 31+ (Android 12),
|
||||
* falls back to pure Kotlin on older devices.
|
||||
*/
|
||||
internal actual fun multiplyHigh(
|
||||
a: Long,
|
||||
b: Long,
|
||||
): Long =
|
||||
if (android.os.Build.VERSION.SDK_INT >= 31) {
|
||||
Math.multiplyHigh(a, b)
|
||||
} else {
|
||||
multiplyHighFallback(a, b)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Returns the upper 64 bits of the signed 128-bit product of two Long values.
|
||||
*
|
||||
* On JVM (Java 9+), this maps to Math.multiplyHigh which the JIT compiles to
|
||||
* a single SMULH (ARM64) or IMUL (x86-64) instruction. On other platforms,
|
||||
* a pure-Kotlin fallback computes it via four 32-bit sub-products.
|
||||
*/
|
||||
internal expect fun multiplyHigh(
|
||||
a: Long,
|
||||
b: Long,
|
||||
): Long
|
||||
|
||||
/**
|
||||
* Returns the upper 64 bits of the UNSIGNED 128-bit product of two Long values.
|
||||
* Built on top of the signed multiplyHigh with a correction for sign bits.
|
||||
*/
|
||||
internal fun unsignedMultiplyHigh(
|
||||
a: Long,
|
||||
b: Long,
|
||||
): Long = multiplyHigh(a, b) + (a and (b shr 63)) + (b and (a shr 63))
|
||||
|
||||
/**
|
||||
* Pure-Kotlin fallback for multiplyHigh, using four 32-bit sub-products.
|
||||
* Used on platforms where no hardware intrinsic is available.
|
||||
*/
|
||||
internal fun multiplyHighFallback(
|
||||
a: Long,
|
||||
b: Long,
|
||||
): Long {
|
||||
val aLo = a and 0xFFFFFFFFL
|
||||
val aHi = a ushr 32
|
||||
val bLo = b and 0xFFFFFFFFL
|
||||
val bHi = b ushr 32
|
||||
|
||||
val mid1 = aHi * bLo
|
||||
val mid2 = aLo * bHi
|
||||
val low = aLo * bLo
|
||||
|
||||
val carry = ((low ushr 32) + (mid1 and 0xFFFFFFFFL) + (mid2 and 0xFFFFFFFFL)) ushr 32
|
||||
return aHi * bHi + (mid1 ushr 32) + (mid2 ushr 32) + carry
|
||||
}
|
||||
@@ -21,275 +21,245 @@
|
||||
package com.vitorpamplona.quartz.utils.secp256k1
|
||||
|
||||
// =====================================================================================
|
||||
// 256-BIT UNSIGNED INTEGER ARITHMETIC FOR secp256k1
|
||||
// 256-BIT UNSIGNED INTEGER ARITHMETIC FOR secp256k1 (4×64-bit limbs)
|
||||
// =====================================================================================
|
||||
//
|
||||
// This file implements the raw 256-bit unsigned integer arithmetic that underlies all
|
||||
// secp256k1 operations. It is the foundation of the secp256k1 package, which consists of:
|
||||
// Numbers are stored as LongArray(4) in little-endian order. Each Long holds 64 bits
|
||||
// treated as unsigned. Element [0] is the least significant limb.
|
||||
//
|
||||
// - U256.kt (this file): Raw 256-bit arithmetic (add, subtract, multiply, compare)
|
||||
// - FieldP.kt: Modular arithmetic mod p (field prime), for point coordinates
|
||||
// - ScalarN.kt: Modular arithmetic mod n (group order), for keys and signatures
|
||||
// - Glv.kt: GLV endomorphism and wNAF scalar encoding
|
||||
// - Point.kt: EC point operations, scalar multiplication (comb, Strauss, GLV)
|
||||
// - Secp256k1.kt: Public API (sign, verify, key operations)
|
||||
// This representation uses Math.multiplyHigh (JVM 9+) or a pure-Kotlin fallback to
|
||||
// compute the upper 64 bits of 64×64→128-bit products. On JVM, this maps to a single
|
||||
// hardware instruction (IMULH on x86-64, SMULH on ARM64), reducing the inner product
|
||||
// count from 64 (with 8×32-bit limbs) to 16 per field multiplication.
|
||||
//
|
||||
// REPRESENTATION
|
||||
// ==============
|
||||
// A 256-bit number is stored as IntArray(8) in little-endian order. Each Int holds 32 bits,
|
||||
// treated as unsigned. Element [0] is the least significant. For example, the number 1 is
|
||||
// stored as [1, 0, 0, 0, 0, 0, 0, 0].
|
||||
//
|
||||
// We chose 8×32-bit limbs over alternatives like 5×52-bit because Kotlin's Long (64-bit)
|
||||
// can hold the product of two 32-bit values without overflow (32+32=64 ≤ 63 signed bits
|
||||
// for most cases). The C reference implementation uses 5×52-bit with compiler-specific
|
||||
// __int128 which is unavailable on JVM. A future optimization could use 5×52-bit with
|
||||
// split-product techniques to reduce the inner product count from 64 to ~40.
|
||||
//
|
||||
// FIELD REDUCTION
|
||||
// ===============
|
||||
// secp256k1's field prime p = 2^256 - 2^32 - 977 has a special sparse form that makes
|
||||
// modular reduction efficient. After a 512-bit multiplication result, we split into
|
||||
// lo (256-bit) + hi (256-bit) and use the identity:
|
||||
//
|
||||
// hi × 2^256 ≡ hi × (2^32 + 977) (mod p)
|
||||
//
|
||||
// This replaces a generic 512-bit mod with a 256×33-bit multiply and add. A second
|
||||
// round handles any remaining overflow. This is much cheaper than generic Barrett or
|
||||
// Montgomery reduction because secp256k1's prime was specifically chosen for this property.
|
||||
//
|
||||
// MODULAR INVERSION
|
||||
// =================
|
||||
// We use Fermat's little theorem: a^(-1) = a^(p-2) mod p, computed via repeated
|
||||
// squaring (~255 squarings + ~255 multiplications). This is simple but expensive.
|
||||
//
|
||||
// The C reference library uses a faster algorithm called "safegcd" (Bernstein-Yang 2019)
|
||||
// that computes the modular inverse using ~590 cheap division steps (shifts and additions)
|
||||
// instead of ~510 field multiplications. Implementing safegcd would make inversion ~10×
|
||||
// faster, but since inversion only happens once per signature verification (in the final
|
||||
// Jacobian-to-affine conversion), the total impact on verify throughput is modest (~10%).
|
||||
//
|
||||
// PERFORMANCE APPROACH
|
||||
// ====================
|
||||
// Hot-path functions (mul, sqr, add, sub) take an output IntArray parameter to avoid
|
||||
// allocating a new array on every call. During a single signature verification, the field
|
||||
// multiplication is called thousands of times — allocating a new IntArray(8) each time
|
||||
// would create significant GC pressure on Android. Convenience wrappers that allocate
|
||||
// are provided for non-hot-path code.
|
||||
//
|
||||
// A thread-local IntArray(16) scratch buffer is reused across field multiplications to
|
||||
// avoid allocating a 512-bit intermediate on every mul/sqr call.
|
||||
// Package structure: U256 → FieldP/ScalarN → Glv/KeyCodec → Point → Secp256k1
|
||||
// =====================================================================================
|
||||
|
||||
/**
|
||||
* Raw 256-bit unsigned integer arithmetic.
|
||||
*
|
||||
* All operations treat IntArray(8) as a 256-bit unsigned integer in little-endian
|
||||
* limb order. No modular reduction is performed — callers (FieldP, ScalarN) handle that.
|
||||
* Raw 256-bit unsigned integer arithmetic using 4×64-bit limbs.
|
||||
*/
|
||||
internal object U256 {
|
||||
val ZERO = IntArray(8)
|
||||
val ZERO = LongArray(4)
|
||||
|
||||
/** Branchless zero check — OR all limbs, avoiding per-limb branching. */
|
||||
fun isZero(a: IntArray): Boolean = (a[0] or a[1] or a[2] or a[3] or a[4] or a[5] or a[6] or a[7]) == 0
|
||||
fun isZero(a: LongArray): Boolean = (a[0] or a[1] or a[2] or a[3]) == 0L
|
||||
|
||||
/** Unsigned comparison. Returns -1 if a < b, 0 if equal, 1 if a > b. */
|
||||
fun cmp(
|
||||
a: IntArray,
|
||||
b: IntArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
): Int {
|
||||
for (i in 7 downTo 0) {
|
||||
val ai = a[i].toLong() and 0xFFFFFFFFL
|
||||
val bi = b[i].toLong() and 0xFFFFFFFFL
|
||||
if (ai != bi) return if (ai < bi) -1 else 1
|
||||
for (i in 3 downTo 0) {
|
||||
if (a[i] != b[i]) {
|
||||
return if (a[i].toULong() < b[i].toULong()) -1 else 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
/** out = a + b. Returns the carry bit (0 or 1). Safe for out aliasing a or b. */
|
||||
/** out = a + b. Returns carry (0 or 1). Safe for aliasing. */
|
||||
fun addTo(
|
||||
out: IntArray,
|
||||
a: IntArray,
|
||||
b: IntArray,
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
): Int {
|
||||
var carry = 0L
|
||||
for (i in 0 until 8) {
|
||||
carry += (a[i].toLong() and 0xFFFFFFFFL) + (b[i].toLong() and 0xFFFFFFFFL)
|
||||
out[i] = carry.toInt()
|
||||
carry = carry ushr 32
|
||||
for (i in 0 until 4) {
|
||||
val s1 = a[i] + b[i]
|
||||
val c1 = if (s1.toULong() < a[i].toULong()) 1L else 0L
|
||||
val s2 = s1 + carry
|
||||
val c2 = if (s2.toULong() < s1.toULong()) 1L else 0L
|
||||
out[i] = s2
|
||||
carry = c1 + c2
|
||||
}
|
||||
return carry.toInt()
|
||||
}
|
||||
|
||||
/** out = a - b. Returns the borrow bit (0 or 1). Safe for out aliasing a or b. */
|
||||
/** out = a - b. Returns borrow (0 or 1). Safe for aliasing. */
|
||||
fun subTo(
|
||||
out: IntArray,
|
||||
a: IntArray,
|
||||
b: IntArray,
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
): Int {
|
||||
var borrow = 0L
|
||||
for (i in 0 until 8) {
|
||||
val diff = (a[i].toLong() and 0xFFFFFFFFL) - (b[i].toLong() and 0xFFFFFFFFL) - borrow
|
||||
out[i] = diff.toInt()
|
||||
borrow = if (diff < 0) 1L else 0L
|
||||
for (i in 0 until 4) {
|
||||
val d1 = a[i] - b[i]
|
||||
val c1 = if (a[i].toULong() < b[i].toULong()) 1L else 0L
|
||||
val d2 = d1 - borrow
|
||||
val c2 = if (d1.toULong() < borrow.toULong()) 1L else 0L
|
||||
out[i] = d2
|
||||
borrow = c1 + c2
|
||||
}
|
||||
return borrow.toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Schoolbook multiplication: out = a × b (512-bit result in IntArray(16)).
|
||||
* 4×4 schoolbook multiplication: out = a × b (512-bit result in LongArray(8)).
|
||||
*
|
||||
* Uses the standard O(n²) algorithm with 8×8 = 64 inner Long multiplications.
|
||||
* Each partial product is at most 32×32 = 64 bits, which fits in a signed Long
|
||||
* with room for carry accumulation.
|
||||
*
|
||||
* Note: Karatsuba (splitting into 4-limb halves for 48 products) was attempted
|
||||
* but the overhead of extra additions, carry propagation, and 5 temporary array
|
||||
* allocations per call negates the product-count savings at only 8 limbs.
|
||||
* Uses unsignedMultiplyHigh for the upper 64 bits of each 64×64→128-bit product.
|
||||
* On JVM, this is a hardware intrinsic (single instruction). Total: 16 products
|
||||
* vs 64 for the previous 8×32-bit representation.
|
||||
*/
|
||||
fun mulWide(
|
||||
out: IntArray,
|
||||
a: IntArray,
|
||||
b: IntArray,
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
) {
|
||||
for (i in 0 until 16) out[i] = 0
|
||||
for (i in 0 until 8) {
|
||||
for (i in 0 until 8) out[i] = 0L
|
||||
|
||||
for (i in 0 until 4) {
|
||||
var carry = 0L
|
||||
val ai = a[i].toLong() and 0xFFFFFFFFL
|
||||
for (j in 0 until 8) {
|
||||
val prod = ai * (b[j].toLong() and 0xFFFFFFFFL) + (out[i + j].toLong() and 0xFFFFFFFFL) + carry
|
||||
out[i + j] = prod.toInt()
|
||||
carry = prod ushr 32
|
||||
val ai = a[i]
|
||||
for (j in 0 until 4) {
|
||||
val lo = ai * b[j]
|
||||
val hi = unsignedMultiplyHigh(ai, b[j])
|
||||
|
||||
val prev = out[i + j]
|
||||
val s1 = prev + lo
|
||||
val c1 = if (s1.toULong() < prev.toULong()) 1L else 0L
|
||||
val s2 = s1 + carry
|
||||
val c2 = if (s2.toULong() < s1.toULong()) 1L else 0L
|
||||
out[i + j] = s2
|
||||
carry = hi + c1 + c2
|
||||
}
|
||||
out[i + 8] = carry.toInt()
|
||||
out[i + 4] = carry
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedicated squaring: out = a² (512-bit result in IntArray(16)).
|
||||
*
|
||||
* Exploits the identity a²[i,j] = a²[j,i] to compute each cross-product once
|
||||
* and double it, reducing from 64 to 36 multiplications:
|
||||
* - 28 cross-products (i < j), doubled
|
||||
* - 8 diagonal products (i == i)
|
||||
*
|
||||
* This gives ~40% fewer multiplications than generic mulWide for squaring.
|
||||
* Dedicated squaring: out = a² (512-bit result in LongArray(8)).
|
||||
* Exploits symmetry: 6 cross-products doubled + 4 diagonal = 10 multiplyHigh calls.
|
||||
*/
|
||||
fun sqrWide(
|
||||
out: IntArray,
|
||||
a: IntArray,
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
) {
|
||||
for (i in 0 until 16) out[i] = 0
|
||||
for (i in 0 until 8) out[i] = 0L
|
||||
|
||||
// Pass 1: accumulate cross-products a[i]*a[j] for i < j (single, not doubled yet)
|
||||
for (i in 0 until 8) {
|
||||
// Pass 1: cross-products a[i]*a[j] for i < j (single)
|
||||
for (i in 0 until 4) {
|
||||
var carry = 0L
|
||||
val ai = a[i].toLong() and 0xFFFFFFFFL
|
||||
for (j in i + 1 until 8) {
|
||||
val prod = ai * (a[j].toLong() and 0xFFFFFFFFL) + (out[i + j].toLong() and 0xFFFFFFFFL) + carry
|
||||
out[i + j] = prod.toInt()
|
||||
carry = prod ushr 32
|
||||
val ai = a[i]
|
||||
for (j in i + 1 until 4) {
|
||||
val lo = ai * a[j]
|
||||
val hi = unsignedMultiplyHigh(ai, a[j])
|
||||
val prev = out[i + j]
|
||||
val s1 = prev + lo
|
||||
val c1 = if (s1.toULong() < prev.toULong()) 1L else 0L
|
||||
val s2 = s1 + carry
|
||||
val c2 = if (s2.toULong() < s1.toULong()) 1L else 0L
|
||||
out[i + j] = s2
|
||||
carry = hi + c1 + c2
|
||||
}
|
||||
out[i + 8] = carry.toInt()
|
||||
out[i + 4] = carry
|
||||
}
|
||||
|
||||
// Pass 2: double all cross-products (shift entire 512-bit result left by 1 bit)
|
||||
var shiftCarry = 0
|
||||
for (i in 1 until 16) {
|
||||
// Pass 2: double all cross-products (shift left by 1 bit)
|
||||
var shiftCarry = 0L
|
||||
for (i in 1 until 8) {
|
||||
val v = out[i]
|
||||
out[i] = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 31
|
||||
shiftCarry = v ushr 63
|
||||
}
|
||||
|
||||
// Pass 3: add diagonal products a[i]² at positions 2i and 2i+1
|
||||
// Pass 3: add diagonal products a[i]²
|
||||
var dCarry = 0L
|
||||
for (i in 0 until 8) {
|
||||
val ai = a[i].toLong() and 0xFFFFFFFFL
|
||||
val diag = ai * ai
|
||||
for (i in 0 until 4) {
|
||||
val lo = a[i] * a[i]
|
||||
val hi = unsignedMultiplyHigh(a[i], a[i])
|
||||
val pos = 2 * i
|
||||
dCarry += (out[pos].toLong() and 0xFFFFFFFFL) + (diag and 0xFFFFFFFFL)
|
||||
out[pos] = dCarry.toInt()
|
||||
dCarry = dCarry ushr 32
|
||||
dCarry += (out[pos + 1].toLong() and 0xFFFFFFFFL) + (diag ushr 32)
|
||||
out[pos + 1] = dCarry.toInt()
|
||||
dCarry = dCarry ushr 32
|
||||
|
||||
val s1 = out[pos] + lo
|
||||
val c1 = if (s1.toULong() < out[pos].toULong()) 1L else 0L
|
||||
val s2 = s1 + dCarry
|
||||
val c2 = if (s2.toULong() < s1.toULong()) 1L else 0L
|
||||
out[pos] = s2
|
||||
|
||||
val s3 = out[pos + 1] + hi
|
||||
val c3 = if (s3.toULong() < out[pos + 1].toULong()) 1L else 0L
|
||||
val s4 = s3 + c1 + c2
|
||||
val c4 = if (s4.toULong() < s3.toULong()) 1L else 0L
|
||||
out[pos + 1] = s4
|
||||
dCarry = c3 + c4
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Serialization ====================
|
||||
|
||||
/** Decode a big-endian 32-byte array into little-endian IntArray(8). */
|
||||
fun fromBytes(bytes: ByteArray): IntArray = fromBytes(bytes, 0)
|
||||
/** Decode big-endian 32 bytes into LongArray(4) little-endian limbs. */
|
||||
fun fromBytes(bytes: ByteArray): LongArray = fromBytes(bytes, 0)
|
||||
|
||||
/** Decode 32 big-endian bytes starting at [offset] into little-endian IntArray(8). */
|
||||
fun fromBytes(
|
||||
bytes: ByteArray,
|
||||
offset: Int,
|
||||
): IntArray {
|
||||
val r = IntArray(8)
|
||||
for (i in 0 until 8) {
|
||||
val o = offset + 28 - i * 4
|
||||
r[i] = ((bytes[o].toInt() and 0xFF) shl 24) or
|
||||
((bytes[o + 1].toInt() and 0xFF) shl 16) or
|
||||
((bytes[o + 2].toInt() and 0xFF) shl 8) or
|
||||
(bytes[o + 3].toInt() and 0xFF)
|
||||
): LongArray {
|
||||
val r = LongArray(4)
|
||||
for (i in 0 until 4) {
|
||||
val o = offset + 24 - i * 8
|
||||
r[i] = ((bytes[o].toLong() and 0xFF) shl 56) or
|
||||
((bytes[o + 1].toLong() and 0xFF) shl 48) or
|
||||
((bytes[o + 2].toLong() and 0xFF) shl 40) or
|
||||
((bytes[o + 3].toLong() and 0xFF) shl 32) or
|
||||
((bytes[o + 4].toLong() and 0xFF) shl 24) or
|
||||
((bytes[o + 5].toLong() and 0xFF) shl 16) or
|
||||
((bytes[o + 6].toLong() and 0xFF) shl 8) or
|
||||
(bytes[o + 7].toLong() and 0xFF)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
/** Encode little-endian IntArray(8) to a big-endian 32-byte array. */
|
||||
fun toBytes(a: IntArray): ByteArray {
|
||||
fun toBytes(a: LongArray): ByteArray {
|
||||
val r = ByteArray(32)
|
||||
toBytesInto(a, r, 0)
|
||||
return r
|
||||
}
|
||||
|
||||
/** Encode into an existing byte array at the given offset. Avoids allocation. */
|
||||
fun toBytesInto(
|
||||
a: IntArray,
|
||||
a: LongArray,
|
||||
dest: ByteArray,
|
||||
offset: Int,
|
||||
) {
|
||||
for (i in 0 until 8) {
|
||||
val o = offset + 28 - i * 4
|
||||
dest[o] = (a[i] ushr 24).toByte()
|
||||
dest[o + 1] = (a[i] ushr 16).toByte()
|
||||
dest[o + 2] = (a[i] ushr 8).toByte()
|
||||
dest[o + 3] = a[i].toByte()
|
||||
for (i in 0 until 4) {
|
||||
val o = offset + 24 - i * 8
|
||||
dest[o] = (a[i] ushr 56).toByte()
|
||||
dest[o + 1] = (a[i] ushr 48).toByte()
|
||||
dest[o + 2] = (a[i] ushr 40).toByte()
|
||||
dest[o + 3] = (a[i] ushr 32).toByte()
|
||||
dest[o + 4] = (a[i] ushr 24).toByte()
|
||||
dest[o + 5] = (a[i] ushr 16).toByte()
|
||||
dest[o + 6] = (a[i] ushr 8).toByte()
|
||||
dest[o + 7] = a[i].toByte()
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Bit manipulation ====================
|
||||
|
||||
/** Extract 4-bit nibble at position pos (0 = lowest nibble). Used by windowed scalar mul. */
|
||||
/** Extract 4-bit nibble at position pos (0 = lowest nibble). */
|
||||
fun getNibble(
|
||||
a: IntArray,
|
||||
a: LongArray,
|
||||
pos: Int,
|
||||
): Int {
|
||||
val limb = pos / 8
|
||||
val shift = (pos % 8) * 4
|
||||
return (a[limb] ushr shift) and 0xF
|
||||
val limb = pos / 16
|
||||
val shift = (pos % 16) * 4
|
||||
return ((a[limb] ushr shift) and 0xF).toInt()
|
||||
}
|
||||
|
||||
/** Test if bit at position pos is set (0 = LSB). */
|
||||
/** Test if bit at position pos is set. */
|
||||
fun testBit(
|
||||
a: IntArray,
|
||||
a: LongArray,
|
||||
pos: Int,
|
||||
): Boolean = (a[pos / 32] ushr (pos % 32)) and 1 == 1
|
||||
): Boolean = (a[pos / 64] ushr (pos % 64)) and 1L == 1L
|
||||
|
||||
/** out = a XOR b. Used by BIP-340 signing for nonce derivation. */
|
||||
fun xorTo(
|
||||
out: IntArray,
|
||||
a: IntArray,
|
||||
b: IntArray,
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
) {
|
||||
for (i in 0 until 8) out[i] = a[i] xor b[i]
|
||||
for (i in 0 until 4) out[i] = a[i] xor b[i]
|
||||
}
|
||||
|
||||
/** Copy the contents of a into out. */
|
||||
fun copyInto(
|
||||
out: IntArray,
|
||||
a: IntArray,
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
) {
|
||||
a.copyInto(out)
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 implementation using Math.multiplyHigh (Java 9+).
|
||||
* The HotSpot JIT compiles this to a single hardware instruction:
|
||||
* IMULH on x86-64, SMULH on ARM64.
|
||||
*/
|
||||
internal actual fun multiplyHigh(
|
||||
a: Long,
|
||||
b: Long,
|
||||
): Long = Math.multiplyHigh(a, b)
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 implementation using the pure-Kotlin fallback (no hardware intrinsic). */
|
||||
internal actual fun multiplyHigh(
|
||||
a: Long,
|
||||
b: Long,
|
||||
): Long = multiplyHighFallback(a, b)
|
||||
Reference in New Issue
Block a user