mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
feat: migrate secp256k1 from LongArray to Fe4/Wide8 structs
Full migration of the secp256k1 library from LongArray(4)/LongArray(8) to Fe4/Wide8 struct types with @JvmField named Long fields. This eliminates all array bounds checks from the hot path. Files migrated (13 source + 7 test + 2 benchmark): - U256.kt, FieldP.kt, ScalarN.kt, Glv.kt, ECPoint.kt - FieldMulPlatform.kt (expect + 3 actuals), FieldMulFused.kt - PointTypes.kt (MutablePoint, AffinePoint, PointScratch) - KeyCodec.kt, Secp256k1.kt - All test files and benchmarks Bytecode impact: Before: 464 laload/lastore (bounds-checked) in core arithmetic After: 0 laload/lastore, all getfield/putfield (no checks) The public API (Secp256k1 object) is unchanged - it still accepts and returns ByteArray. Fe4 conversion happens at the API boundary via U256.fromBytes()/U256.toBytes(). All secp256k1 unit tests pass on JVM. https://claude.ai/code/session_01Sxi6Gpxbstuj3Y8TBY7XrU
This commit is contained in:
+7
-7
@@ -43,18 +43,18 @@ package com.vitorpamplona.quartz.utils.secp256k1
|
||||
* - The fused function stays within ART's inlining budget
|
||||
*/
|
||||
internal actual fun fieldMulReduce(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
||||
}
|
||||
|
||||
internal actual fun fieldSqrReduce(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
||||
}
|
||||
|
||||
@@ -51,14 +51,14 @@ internal object ECPoint {
|
||||
// ==================== Generator point G ====================
|
||||
|
||||
val GX =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
6481385041966929816L,
|
||||
188021827762530521L,
|
||||
6170039885052185351L,
|
||||
8772561819708210092L,
|
||||
)
|
||||
val GY =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
-7185545363635252040L,
|
||||
-209500633525038055L,
|
||||
6747795201694173352L,
|
||||
@@ -66,7 +66,7 @@ internal object ECPoint {
|
||||
)
|
||||
|
||||
/** Curve constant b = 7 in y² = x³ + 7. */
|
||||
private val B = longArrayOf(7L, 0L, 0L, 0L)
|
||||
private val B = Fe4(7L, 0L, 0L, 0L)
|
||||
|
||||
// Thread-local scratch — declared before precomputed tables because
|
||||
// buildGOddTable() and buildCombTable() use doublePoint/addPoints which
|
||||
@@ -119,7 +119,7 @@ internal object ECPoint {
|
||||
private const val P_TABLE_CACHE_MASK = P_TABLE_CACHE_SIZE - 1
|
||||
|
||||
private class CachedPTable(
|
||||
val px: LongArray, // x-coordinate of the point (cache key, 4 limbs)
|
||||
val px: Fe4, // x-coordinate of the point (cache key, 4 limbs)
|
||||
val pOdd: Array<AffinePoint>, // 8 affine odd-multiples of P
|
||||
val pLamOdd: Array<AffinePoint>, // 8 affine odd-multiples of λ(P)
|
||||
)
|
||||
@@ -127,7 +127,7 @@ internal object ECPoint {
|
||||
private val pTableCache = arrayOfNulls<CachedPTable>(P_TABLE_CACHE_SIZE)
|
||||
|
||||
/** Hash a field element to a cache slot index. */
|
||||
private fun cacheSlot(px: LongArray): Int = (px[0].toInt() xor px[1].toInt().shl(3)) and P_TABLE_CACHE_MASK
|
||||
private fun cacheSlot(px: Fe4): Int = (px.l0.toInt() xor px.l1.toInt().shl(3)) and P_TABLE_CACHE_MASK
|
||||
|
||||
private fun buildGOddTable(): Array<AffinePoint> {
|
||||
val g = MutablePoint()
|
||||
@@ -153,21 +153,21 @@ internal object ECPoint {
|
||||
|
||||
// Step 1: compute prefix products of Z coordinates
|
||||
// prods[i] = z[0] * z[1] * ... * z[i]
|
||||
val prods = Array(n) { LongArray(4) }
|
||||
jac[0].z.copyInto(prods[0], 0, 0, 4)
|
||||
val prods = Array(n) { Fe4() }
|
||||
prods[0].copyFrom(jac[0].z)
|
||||
for (i in 1 until n) {
|
||||
FieldP.mul(prods[i], prods[i - 1], jac[i].z)
|
||||
}
|
||||
|
||||
// Step 2: invert the total product
|
||||
val inv = LongArray(4)
|
||||
val inv = Fe4()
|
||||
FieldP.inv(inv, prods[n - 1])
|
||||
|
||||
// Step 3: recover individual inverses by multiplying back
|
||||
// zInv[i] = product_inv * prods[i-1] = 1 / z[i]
|
||||
val zInv = LongArray(4)
|
||||
val zInv2 = LongArray(4)
|
||||
val zInv3 = LongArray(4)
|
||||
val zInv = Fe4()
|
||||
val zInv2 = Fe4()
|
||||
val zInv3 = Fe4()
|
||||
val result = Array(n) { AffinePoint() }
|
||||
|
||||
for (i in n - 1 downTo 1) {
|
||||
@@ -245,10 +245,10 @@ internal object ECPoint {
|
||||
|
||||
return Array(tableSize) { i ->
|
||||
if (jac[i].isInfinity()) {
|
||||
AffinePoint(LongArray(4), LongArray(4))
|
||||
AffinePoint(Fe4(), Fe4())
|
||||
} else {
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
toAffine(jac[i], x, y)
|
||||
AffinePoint(x, y)
|
||||
}
|
||||
@@ -319,16 +319,16 @@ internal object ECPoint {
|
||||
fun addMixed(
|
||||
out: MutablePoint,
|
||||
p: MutablePoint,
|
||||
qx: LongArray,
|
||||
qy: LongArray,
|
||||
qx: Fe4,
|
||||
qy: Fe4,
|
||||
) = addMixed(out, p, qx, qy, scratch.get())
|
||||
|
||||
/** addMixed with caller-provided scratch (hot path — no ThreadLocal lookup). */
|
||||
fun addMixed(
|
||||
out: MutablePoint,
|
||||
p: MutablePoint,
|
||||
qx: LongArray,
|
||||
qy: LongArray,
|
||||
qx: Fe4,
|
||||
qy: Fe4,
|
||||
s: PointScratch,
|
||||
) {
|
||||
if (p.isInfinity()) {
|
||||
@@ -344,9 +344,9 @@ internal object ECPoint {
|
||||
FieldP.mul(t[3], qy, t[1], w) // S₂ = qy·Z₁³ (S₁ = Y₁ since Z₂=1)
|
||||
FieldP.sub(t[4], t[2], p.x) // H = U₂ - U₁
|
||||
|
||||
if (U256.isZero(t[4])) {
|
||||
if (t[4].isZero()) {
|
||||
FieldP.sub(t[5], t[3], p.y)
|
||||
if (U256.isZero(t[5])) doublePoint(out, p, s) else out.setInfinity()
|
||||
if (t[5].isZero()) doublePoint(out, p, s) else out.setInfinity()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -454,10 +454,10 @@ internal object ECPoint {
|
||||
fun mul(
|
||||
out: MutablePoint,
|
||||
p: MutablePoint,
|
||||
scalar: LongArray,
|
||||
scalar: Fe4,
|
||||
s: PointScratch = scratch.get(),
|
||||
) {
|
||||
if (U256.isZero(scalar) || p.isInfinity()) {
|
||||
if (scalar.isZero() || p.isInfinity()) {
|
||||
out.setInfinity()
|
||||
return
|
||||
}
|
||||
@@ -472,7 +472,7 @@ internal object ECPoint {
|
||||
val wnaf2 = s.wnaf2
|
||||
|
||||
// P odd-multiples [1P, 3P, 5P, ..., 15P] via 2P stepping (Jacobian)
|
||||
// Uses pre-allocated tables from PointScratch to avoid ~80 LongArray allocs
|
||||
// Uses pre-allocated tables from PointScratch to avoid ~80 Fe4 allocs
|
||||
doublePoint(s.p2, p, s)
|
||||
val pOddJac = s.pOddJac
|
||||
pOddJac[0].copyFrom(p)
|
||||
@@ -482,8 +482,8 @@ internal object ECPoint {
|
||||
val pLamOddJac = s.pLamOddJac
|
||||
for (i in 0 until tableSize) {
|
||||
FieldP.mul(pLamOddJac[i].x, pOddJac[i].x, Glv.BETA, s.w)
|
||||
pOddJac[i].y.copyInto(pLamOddJac[i].y, 0, 0, 4)
|
||||
pOddJac[i].z.copyInto(pLamOddJac[i].z, 0, 0, 4)
|
||||
pLamOddJac[i].y.copyFrom(pOddJac[i].y)
|
||||
pLamOddJac[i].z.copyFrom(pOddJac[i].z)
|
||||
}
|
||||
|
||||
// Effective-affine: batch-convert with shared Z inversion
|
||||
@@ -536,10 +536,10 @@ internal object ECPoint {
|
||||
*/
|
||||
fun mulG(
|
||||
out: MutablePoint,
|
||||
scalar: LongArray,
|
||||
scalar: Fe4,
|
||||
s: PointScratch = scratch.get(),
|
||||
) {
|
||||
if (U256.isZero(scalar)) {
|
||||
if (scalar.isZero()) {
|
||||
out.setInfinity()
|
||||
return
|
||||
}
|
||||
@@ -593,9 +593,9 @@ internal object ECPoint {
|
||||
*/
|
||||
fun mulDoubleG(
|
||||
out: MutablePoint,
|
||||
s: LongArray,
|
||||
s: Fe4,
|
||||
p: MutablePoint,
|
||||
e: LongArray,
|
||||
e: Fe4,
|
||||
sc: PointScratch = scratch.get(),
|
||||
) {
|
||||
val wP = 5 // Window for P-side (table built per-call, keep small)
|
||||
@@ -639,8 +639,8 @@ internal object ECPoint {
|
||||
val pLamOddJac = sc.pLamOddJac
|
||||
for (i in 0 until pTableSize) {
|
||||
FieldP.mul(pLamOddJac[i].x, pOddJac[i].x, Glv.BETA, sc.w)
|
||||
pOddJac[i].y.copyInto(pLamOddJac[i].y, 0, 0, 4)
|
||||
pOddJac[i].z.copyInto(pLamOddJac[i].z, 0, 0, 4)
|
||||
pLamOddJac[i].y.copyFrom(pOddJac[i].y)
|
||||
pLamOddJac[i].z.copyFrom(pOddJac[i].z)
|
||||
}
|
||||
// Batch-convert to affine (into scratch arrays)
|
||||
batchToAffinePair(pOddJac, pLamOddJac, sc.pOddAff, sc.pLamOddAff, sc)
|
||||
@@ -769,7 +769,7 @@ internal object ECPoint {
|
||||
private fun addWnafMixedPP(
|
||||
cur: MutablePoint,
|
||||
alt: MutablePoint,
|
||||
negY: LongArray,
|
||||
negY: Fe4,
|
||||
wnafDigits: IntArray,
|
||||
bitIndex: Int,
|
||||
table: Array<AffinePoint>,
|
||||
@@ -816,21 +816,21 @@ internal object ECPoint {
|
||||
val w = s.w
|
||||
|
||||
// Prefix products of Z coordinates
|
||||
val cumZ = Array(n) { LongArray(4) }
|
||||
U256.copyInto(cumZ[0], points[0].z)
|
||||
val cumZ = Array(n) { Fe4() }
|
||||
cumZ[0].copyFrom(points[0].z)
|
||||
for (i in 1 until n) {
|
||||
FieldP.mul(cumZ[i], cumZ[i - 1], points[i].z, w)
|
||||
}
|
||||
|
||||
// Invert the total product
|
||||
val inv = LongArray(4)
|
||||
val inv = Fe4()
|
||||
FieldP.inv(inv, cumZ[n - 1])
|
||||
|
||||
// Recover individual Z inverses and convert to affine
|
||||
val result = Array(n) { AffinePoint() }
|
||||
val zInv = LongArray(4)
|
||||
val zInv2 = LongArray(4)
|
||||
val zInv3 = LongArray(4)
|
||||
val zInv = Fe4()
|
||||
val zInv2 = Fe4()
|
||||
val zInv3 = Fe4()
|
||||
|
||||
for (i in n - 1 downTo 1) {
|
||||
// zInv = inv * cumZ[i-1] gives Z[i]^{-1}
|
||||
@@ -870,7 +870,7 @@ internal object ECPoint {
|
||||
|
||||
// Build prefix products of Z (shared between a and b)
|
||||
val cumZ = s.cumZ
|
||||
a[0].z.copyInto(cumZ[0], 0, 0, 4)
|
||||
cumZ[0].copyFrom(a[0].z)
|
||||
for (i in 1 until n) {
|
||||
FieldP.mul(cumZ[i], cumZ[i - 1], a[i].z, w)
|
||||
}
|
||||
@@ -910,13 +910,13 @@ internal object ECPoint {
|
||||
/** Convert Jacobian → affine (convenience, allocates temps). For one-time init paths. */
|
||||
fun toAffine(
|
||||
p: MutablePoint,
|
||||
outX: LongArray,
|
||||
outY: LongArray,
|
||||
outX: Fe4,
|
||||
outY: Fe4,
|
||||
): Boolean {
|
||||
if (p.isInfinity()) return false
|
||||
val zInv = LongArray(4)
|
||||
val zInv2 = LongArray(4)
|
||||
val zInv3 = LongArray(4)
|
||||
val zInv = Fe4()
|
||||
val zInv2 = Fe4()
|
||||
val zInv3 = Fe4()
|
||||
FieldP.inv(zInv, p.z)
|
||||
FieldP.sqr(zInv2, zInv)
|
||||
FieldP.mul(zInv3, zInv2, zInv)
|
||||
@@ -928,8 +928,8 @@ internal object ECPoint {
|
||||
/** Convert Jacobian → affine using pre-allocated scratch (hot path). */
|
||||
fun toAffine(
|
||||
p: MutablePoint,
|
||||
outX: LongArray,
|
||||
outY: LongArray,
|
||||
outX: Fe4,
|
||||
outY: Fe4,
|
||||
s: PointScratch,
|
||||
): Boolean {
|
||||
if (p.isInfinity()) return false
|
||||
@@ -944,7 +944,7 @@ internal object ECPoint {
|
||||
/** Convert Jacobian → affine x-only using pre-allocated scratch (hot path). */
|
||||
fun toAffineX(
|
||||
p: MutablePoint,
|
||||
outX: LongArray,
|
||||
outX: Fe4,
|
||||
s: PointScratch,
|
||||
): Boolean {
|
||||
if (p.isInfinity()) return false
|
||||
|
||||
@@ -50,6 +50,8 @@ import kotlin.jvm.JvmField
|
||||
* Mutable 256-bit field element using 4 named Long fields in little-endian order.
|
||||
* l0 = least significant 64 bits, l3 = most significant 64 bits.
|
||||
*
|
||||
* Used for both field elements (mod p) and scalars (mod n) — same 4×64-bit layout.
|
||||
*
|
||||
* @JvmField eliminates virtual getter/setter generation — direct field access.
|
||||
*/
|
||||
internal class Fe4(
|
||||
@@ -67,12 +69,30 @@ internal class Fe4(
|
||||
l3 = other.l3
|
||||
}
|
||||
|
||||
fun copyOf(): Fe4 = Fe4(l0, l1, l2, l3)
|
||||
|
||||
fun setZero() {
|
||||
l0 = 0L
|
||||
l1 = 0L
|
||||
l2 = 0L
|
||||
l3 = 0L
|
||||
}
|
||||
|
||||
/** Copy this Fe4's limbs into the first 4 elements of a LongArray. */
|
||||
fun copyIntoArray(dest: LongArray) {
|
||||
dest[0] = l0
|
||||
dest[1] = l1
|
||||
dest[2] = l2
|
||||
dest[3] = l3
|
||||
}
|
||||
|
||||
/** Load from a LongArray's first 4 elements. */
|
||||
fun loadFromArray(src: LongArray) {
|
||||
l0 = src[0]
|
||||
l1 = src[1]
|
||||
l2 = src[2]
|
||||
l3 = src[3]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,599 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
// =====================================================================================
|
||||
// FIELD OPERATIONS ON Fe4 (struct-based, zero bounds checks)
|
||||
//
|
||||
// Mirror implementations of U256 and FieldP operations using Fe4/Wide8 instead of
|
||||
// LongArray(4)/LongArray(8). The arithmetic is identical; only the access pattern
|
||||
// differs (field access vs array indexing).
|
||||
//
|
||||
// These are used for benchmarking to measure the impact of eliminating array bounds
|
||||
// checks on various platforms (JVM HotSpot, Android ART, Kotlin/Native LLVM).
|
||||
// =====================================================================================
|
||||
|
||||
/**
|
||||
* 256-bit unsigned arithmetic on Fe4 (struct-based, no bounds checks).
|
||||
*/
|
||||
internal object Fe4U256 {
|
||||
/** out = a + b. Returns carry (0 or 1). */
|
||||
fun addTo(
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
): Int {
|
||||
var s1: Long
|
||||
var s2: Long
|
||||
var c1: Long
|
||||
var c2: Long
|
||||
|
||||
// Limb 0
|
||||
s1 = a.l0 + b.l0
|
||||
c1 = if (uLtInline(s1, a.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
var carry = c1
|
||||
|
||||
// Limb 1
|
||||
s1 = a.l1 + b.l1
|
||||
c1 = if (uLtInline(s1, a.l1)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out.l1 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
// Limb 2
|
||||
s1 = a.l2 + b.l2
|
||||
c1 = if (uLtInline(s1, a.l2)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out.l2 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
// Limb 3
|
||||
s1 = a.l3 + b.l3
|
||||
c1 = if (uLtInline(s1, a.l3)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out.l3 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
return carry.toInt()
|
||||
}
|
||||
|
||||
/** out = a - b. Returns borrow (0 or 1). */
|
||||
fun subTo(
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
): Int {
|
||||
var d1: Long
|
||||
var d2: Long
|
||||
var c1: Long
|
||||
var c2: Long
|
||||
|
||||
// Limb 0
|
||||
d1 = a.l0 - b.l0
|
||||
c1 = if (uLtInline(a.l0, b.l0)) 1L else 0L
|
||||
out.l0 = d1
|
||||
var borrow = c1
|
||||
|
||||
// Limb 1
|
||||
d1 = a.l1 - b.l1
|
||||
c1 = if (uLtInline(a.l1, b.l1)) 1L else 0L
|
||||
d2 = d1 - borrow
|
||||
c2 = if (uLtInline(d1, borrow)) 1L else 0L
|
||||
out.l1 = d2
|
||||
borrow = c1 + c2
|
||||
|
||||
// Limb 2
|
||||
d1 = a.l2 - b.l2
|
||||
c1 = if (uLtInline(a.l2, b.l2)) 1L else 0L
|
||||
d2 = d1 - borrow
|
||||
c2 = if (uLtInline(d1, borrow)) 1L else 0L
|
||||
out.l2 = d2
|
||||
borrow = c1 + c2
|
||||
|
||||
// Limb 3
|
||||
d1 = a.l3 - b.l3
|
||||
c1 = if (uLtInline(a.l3, b.l3)) 1L else 0L
|
||||
d2 = d1 - borrow
|
||||
c2 = if (uLtInline(d1, borrow)) 1L else 0L
|
||||
out.l3 = d2
|
||||
borrow = c1 + c2
|
||||
|
||||
return borrow.toInt()
|
||||
}
|
||||
|
||||
/** 4×4 schoolbook multiplication: w = a × b (512-bit result). */
|
||||
@Suppress("LongMethod")
|
||||
fun mulWide(
|
||||
w: Wide8,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
) {
|
||||
val a0 = a.l0
|
||||
val a1 = a.l1
|
||||
val a2 = a.l2
|
||||
val a3 = a.l3
|
||||
val b0 = b.l0
|
||||
val b1 = b.l1
|
||||
val b2 = b.l2
|
||||
val b3 = b.l3
|
||||
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.l0 = lo
|
||||
carry = unsignedMultiplyHigh(a0, b0)
|
||||
|
||||
lo = a0 * b1
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w.l1 = s
|
||||
carry = unsignedMultiplyHigh(a0, b1) + c1
|
||||
|
||||
lo = a0 * b2
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w.l2 = s
|
||||
carry = unsignedMultiplyHigh(a0, b2) + c1
|
||||
|
||||
lo = a0 * b3
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w.l3 = s
|
||||
w.l4 = unsignedMultiplyHigh(a0, b3) + c1
|
||||
|
||||
// Row 1: a1 × [b0,b1,b2,b3]
|
||||
lo = a1 * b0
|
||||
hi = unsignedMultiplyHigh(a1, b0)
|
||||
prev = w.l1
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w.l1 = s
|
||||
carry = hi + c1
|
||||
|
||||
lo = a1 * b1
|
||||
hi = unsignedMultiplyHigh(a1, b1)
|
||||
prev = w.l2
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w.l2 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a1 * b2
|
||||
hi = unsignedMultiplyHigh(a1, b2)
|
||||
prev = w.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w.l3 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a1 * b3
|
||||
hi = unsignedMultiplyHigh(a1, b3)
|
||||
prev = w.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w.l4 = s
|
||||
w.l5 = hi + c1 + c2
|
||||
|
||||
// Row 2: a2 × [b0,b1,b2,b3]
|
||||
lo = a2 * b0
|
||||
hi = unsignedMultiplyHigh(a2, b0)
|
||||
prev = w.l2
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w.l2 = s
|
||||
carry = hi + c1
|
||||
|
||||
lo = a2 * b1
|
||||
hi = unsignedMultiplyHigh(a2, b1)
|
||||
prev = w.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w.l3 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a2 * b2
|
||||
hi = unsignedMultiplyHigh(a2, b2)
|
||||
prev = w.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w.l4 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a2 * b3
|
||||
hi = unsignedMultiplyHigh(a2, b3)
|
||||
prev = w.l5
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w.l5 = s
|
||||
w.l6 = hi + c1 + c2
|
||||
|
||||
// Row 3: a3 × [b0,b1,b2,b3]
|
||||
lo = a3 * b0
|
||||
hi = unsignedMultiplyHigh(a3, b0)
|
||||
prev = w.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w.l3 = s
|
||||
carry = hi + c1
|
||||
|
||||
lo = a3 * b1
|
||||
hi = unsignedMultiplyHigh(a3, b1)
|
||||
prev = w.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w.l4 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a3 * b2
|
||||
hi = unsignedMultiplyHigh(a3, b2)
|
||||
prev = w.l5
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w.l5 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a3 * b3
|
||||
hi = unsignedMultiplyHigh(a3, b3)
|
||||
prev = w.l6
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w.l6 = s
|
||||
w.l7 = hi + c1 + c2
|
||||
}
|
||||
|
||||
/** Dedicated squaring: w = a² (512-bit result). */
|
||||
@Suppress("LongMethod")
|
||||
fun sqrWide(
|
||||
w: Wide8,
|
||||
a: Fe4,
|
||||
) {
|
||||
val a0 = a.l0
|
||||
val a1 = a.l1
|
||||
val a2 = a.l2
|
||||
val a3 = a.l3
|
||||
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.l0 = 0L
|
||||
lo = a0 * a1
|
||||
w.l1 = lo
|
||||
carry = unsignedMultiplyHigh(a0, a1)
|
||||
|
||||
lo = a0 * a2
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w.l2 = s
|
||||
carry = unsignedMultiplyHigh(a0, a2) + c1
|
||||
|
||||
lo = a0 * a3
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w.l3 = s
|
||||
w.l4 = unsignedMultiplyHigh(a0, a3) + c1
|
||||
|
||||
lo = a1 * a2
|
||||
hi = unsignedMultiplyHigh(a1, a2)
|
||||
prev = w.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w.l3 = s
|
||||
carry = hi + c1
|
||||
|
||||
lo = a1 * a3
|
||||
hi = unsignedMultiplyHigh(a1, a3)
|
||||
prev = w.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w.l4 = s
|
||||
w.l5 = hi + c1 + c2
|
||||
|
||||
lo = a2 * a3
|
||||
hi = unsignedMultiplyHigh(a2, a3)
|
||||
prev = w.l5
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w.l5 = s
|
||||
w.l6 = hi + c1
|
||||
|
||||
// Pass 2: double all cross-products (shift left by 1 bit)
|
||||
v = w.l1
|
||||
w.l1 = v shl 1
|
||||
var shiftCarry = v ushr 63
|
||||
v = w.l2
|
||||
w.l2 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = w.l3
|
||||
w.l3 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = w.l4
|
||||
w.l4 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = w.l5
|
||||
w.l5 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = w.l6
|
||||
w.l6 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
w.l7 = shiftCarry
|
||||
|
||||
// Pass 3: add diagonal products a[i]²
|
||||
lo = a0 * a0
|
||||
hi = unsignedMultiplyHigh(a0, a0)
|
||||
w.l0 = lo
|
||||
s = w.l1 + hi
|
||||
c1 = if (uLtInline(s, w.l1)) 1L else 0L
|
||||
w.l1 = s
|
||||
var dCarry = c1
|
||||
|
||||
lo = a1 * a1
|
||||
hi = unsignedMultiplyHigh(a1, a1)
|
||||
s = w.l2 + lo
|
||||
c1 = if (uLtInline(s, w.l2)) 1L else 0L
|
||||
s += dCarry
|
||||
c2 = if (uLtInline(s, dCarry)) 1L else 0L
|
||||
w.l2 = s
|
||||
prev = w.l3 + hi
|
||||
val c3a = if (uLtInline(prev, w.l3)) 1L else 0L
|
||||
prev += c1 + c2
|
||||
val c4a = if (uLtInline(prev, c1 + c2)) 1L else 0L
|
||||
w.l3 = prev
|
||||
dCarry = c3a + c4a
|
||||
|
||||
lo = a2 * a2
|
||||
hi = unsignedMultiplyHigh(a2, a2)
|
||||
s = w.l4 + lo
|
||||
c1 = if (uLtInline(s, w.l4)) 1L else 0L
|
||||
s += dCarry
|
||||
c2 = if (uLtInline(s, dCarry)) 1L else 0L
|
||||
w.l4 = s
|
||||
prev = w.l5 + hi
|
||||
val c3b = if (uLtInline(prev, w.l5)) 1L else 0L
|
||||
prev += c1 + c2
|
||||
val c4b = if (uLtInline(prev, c1 + c2)) 1L else 0L
|
||||
w.l5 = prev
|
||||
dCarry = c3b + c4b
|
||||
|
||||
lo = a3 * a3
|
||||
hi = unsignedMultiplyHigh(a3, a3)
|
||||
s = w.l6 + lo
|
||||
c1 = if (uLtInline(s, w.l6)) 1L else 0L
|
||||
s += dCarry
|
||||
c2 = if (uLtInline(s, dCarry)) 1L else 0L
|
||||
w.l6 = s
|
||||
prev = w.l7 + hi
|
||||
prev += c1 + c2
|
||||
w.l7 = prev
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Field arithmetic mod p using Fe4 (struct-based, no bounds checks).
|
||||
*/
|
||||
internal object Fe4FieldP {
|
||||
private const val P0 = -4294968273L // 0xFFFFFFFEFFFFFC2F
|
||||
|
||||
fun reduceSelf(a: Fe4) {
|
||||
if (a.l3 == -1L && a.l2 == -1L && a.l1 == -1L &&
|
||||
(a.l0 xor Long.MIN_VALUE) >= (P0 xor Long.MIN_VALUE)
|
||||
) {
|
||||
a.l0 -= P0
|
||||
a.l1 = 0L
|
||||
a.l2 = 0L
|
||||
a.l3 = 0L
|
||||
}
|
||||
}
|
||||
|
||||
/** Reduce 512-bit value mod p. */
|
||||
@Suppress("LongMethod")
|
||||
fun reduceWide(
|
||||
out: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
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.l4 * c
|
||||
hcHi = unsignedMultiplyHigh(w.l4, c)
|
||||
s1 = w.l0 + hcLo
|
||||
c1 = if (uLtInline(s1, w.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
var carry = hcHi + c1
|
||||
|
||||
hcLo = w.l5 * c
|
||||
hcHi = unsignedMultiplyHigh(w.l5, c)
|
||||
s1 = w.l1 + hcLo
|
||||
c1 = if (uLtInline(s1, w.l1)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out.l1 = s2
|
||||
carry = hcHi + c1 + c2
|
||||
|
||||
hcLo = w.l6 * c
|
||||
hcHi = unsignedMultiplyHigh(w.l6, c)
|
||||
s1 = w.l2 + hcLo
|
||||
c1 = if (uLtInline(s1, w.l2)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out.l2 = s2
|
||||
carry = hcHi + c1 + c2
|
||||
|
||||
hcLo = w.l7 * c
|
||||
hcHi = unsignedMultiplyHigh(w.l7, c)
|
||||
s1 = w.l3 + hcLo
|
||||
c1 = if (uLtInline(s1, w.l3)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out.l3 = s2
|
||||
carry = hcHi + c1 + c2
|
||||
|
||||
// Round 2: fold carry × C
|
||||
if (carry != 0L) {
|
||||
val ccLo = carry * c
|
||||
val ccHi = unsignedMultiplyHigh(carry, c)
|
||||
s1 = out.l0 + ccLo
|
||||
c1 = if (uLtInline(s1, out.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
var prop = ccHi + c1
|
||||
if (prop != 0L) {
|
||||
s1 = out.l1 + prop
|
||||
prop = if (uLtInline(s1, out.l1)) 1L else 0L
|
||||
out.l1 = s1
|
||||
if (prop != 0L) {
|
||||
s1 = out.l2 + prop
|
||||
prop = if (uLtInline(s1, out.l2)) 1L else 0L
|
||||
out.l2 = s1
|
||||
if (prop != 0L) {
|
||||
s1 = out.l3 + prop
|
||||
prop = if (uLtInline(s1, out.l3)) 1L else 0L
|
||||
out.l3 = s1
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prop != 0L) {
|
||||
s1 = out.l0 + c
|
||||
c1 = if (uLtInline(s1, out.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
if (c1 != 0L) {
|
||||
out.l1++
|
||||
if (out.l1 == 0L) {
|
||||
out.l2++
|
||||
if (out.l2 == 0L) out.l3++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reduceSelf(out)
|
||||
}
|
||||
|
||||
/** out = (a + b) mod p. */
|
||||
fun add(
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
) {
|
||||
val carry = Fe4U256.addTo(out, a, b)
|
||||
if (carry != 0) {
|
||||
val s1 = out.l0 + 4294968273L
|
||||
val c1 = if (uLtInline(s1, out.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
if (c1 != 0L) {
|
||||
out.l1++
|
||||
if (out.l1 == 0L) {
|
||||
out.l2++
|
||||
if (out.l2 == 0L) out.l3++
|
||||
}
|
||||
}
|
||||
}
|
||||
reduceSelf(out)
|
||||
}
|
||||
|
||||
/** out = (a - b) mod p. */
|
||||
fun sub(
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
) {
|
||||
val borrow = Fe4U256.subTo(out, a, b)
|
||||
if (borrow != 0) {
|
||||
val s0 = out.l0 + P0
|
||||
val c0 = if (uLtInline(s0, out.l0)) 1L else 0L
|
||||
out.l0 = s0
|
||||
if (c0 == 0L) {
|
||||
if (out.l1 != 0L) {
|
||||
out.l1--
|
||||
} else {
|
||||
out.l1 = -1L
|
||||
if (out.l2 != 0L) {
|
||||
out.l2--
|
||||
} else {
|
||||
out.l2 = -1L
|
||||
out.l3--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** out = (a × b) mod p using caller-provided wide buffer. */
|
||||
fun mul(
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
Fe4U256.mulWide(w, a, b)
|
||||
reduceWide(out, w)
|
||||
}
|
||||
|
||||
/** out = a² mod p using caller-provided wide buffer. */
|
||||
fun sqr(
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
Fe4U256.sqrWide(w, a)
|
||||
reduceWide(out, w)
|
||||
}
|
||||
}
|
||||
+98
-98
@@ -90,19 +90,19 @@ private inline fun umulhFused(
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
internal fun fieldMulReduceFused(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
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]
|
||||
val a0 = a.l0
|
||||
val a1 = a.l1
|
||||
val a2 = a.l2
|
||||
val a3 = a.l3
|
||||
val b0 = b.l0
|
||||
val b1 = b.l1
|
||||
val b2 = b.l2
|
||||
val b3 = b.l3
|
||||
var s: Long = 0L
|
||||
var c1: Long
|
||||
var c2: Long
|
||||
@@ -110,135 +110,135 @@ internal fun fieldMulReduceFused(
|
||||
|
||||
// Row 0: a0 × [b0,b1,b2,b3]
|
||||
mulFull(a0, b0) { lo, hi ->
|
||||
w[0] = lo
|
||||
w.l0 = lo
|
||||
carry = hi
|
||||
}
|
||||
|
||||
mulFull(a0, b1) { lo, hi ->
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w[1] = s
|
||||
w.l1 = s
|
||||
carry = hi + c1
|
||||
}
|
||||
mulFull(a0, b2) { lo, hi ->
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w[2] = s
|
||||
w.l2 = s
|
||||
carry = hi + c1
|
||||
}
|
||||
mulFull(a0, b3) { lo, hi ->
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w[3] = s
|
||||
w[4] = hi + c1
|
||||
w.l3 = s
|
||||
w.l4 = hi + c1
|
||||
}
|
||||
|
||||
// Row 1: a1 × [b0,b1,b2,b3]
|
||||
mulFull(a1, b0) { lo, hi ->
|
||||
val prev = w[1]
|
||||
val prev = w.l1
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w[1] = s
|
||||
w.l1 = s
|
||||
carry = hi + c1
|
||||
}
|
||||
mulFull(a1, b1) { lo, hi ->
|
||||
val prev = w[2]
|
||||
val prev = w.l2
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[2] = s
|
||||
w.l2 = s
|
||||
carry = hi + c1 + c2
|
||||
}
|
||||
mulFull(a1, b2) { lo, hi ->
|
||||
val prev = w[3]
|
||||
val prev = w.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[3] = s
|
||||
w.l3 = s
|
||||
carry = hi + c1 + c2
|
||||
}
|
||||
mulFull(a1, b3) { lo, hi ->
|
||||
val prev = w[4]
|
||||
val prev = w.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[4] = s
|
||||
w[5] = hi + c1 + c2
|
||||
w.l4 = s
|
||||
w.l5 = hi + c1 + c2
|
||||
}
|
||||
|
||||
// Row 2: a2 × [b0,b1,b2,b3]
|
||||
mulFull(a2, b0) { lo, hi ->
|
||||
val prev = w[2]
|
||||
val prev = w.l2
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w[2] = s
|
||||
w.l2 = s
|
||||
carry = hi + c1
|
||||
}
|
||||
mulFull(a2, b1) { lo, hi ->
|
||||
val prev = w[3]
|
||||
val prev = w.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[3] = s
|
||||
w.l3 = s
|
||||
carry = hi + c1 + c2
|
||||
}
|
||||
mulFull(a2, b2) { lo, hi ->
|
||||
val prev = w[4]
|
||||
val prev = w.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[4] = s
|
||||
w.l4 = s
|
||||
carry = hi + c1 + c2
|
||||
}
|
||||
mulFull(a2, b3) { lo, hi ->
|
||||
val prev = w[5]
|
||||
val prev = w.l5
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[5] = s
|
||||
w[6] = hi + c1 + c2
|
||||
w.l5 = s
|
||||
w.l6 = hi + c1 + c2
|
||||
}
|
||||
|
||||
// Row 3: a3 × [b0,b1,b2,b3]
|
||||
mulFull(a3, b0) { lo, hi ->
|
||||
val prev = w[3]
|
||||
val prev = w.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w[3] = s
|
||||
w.l3 = s
|
||||
carry = hi + c1
|
||||
}
|
||||
mulFull(a3, b1) { lo, hi ->
|
||||
val prev = w[4]
|
||||
val prev = w.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[4] = s
|
||||
w.l4 = s
|
||||
carry = hi + c1 + c2
|
||||
}
|
||||
mulFull(a3, b2) { lo, hi ->
|
||||
val prev = w[5]
|
||||
val prev = w.l5
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[5] = s
|
||||
w.l5 = s
|
||||
carry = hi + c1 + c2
|
||||
}
|
||||
mulFull(a3, b3) { lo, hi ->
|
||||
val prev = w[6]
|
||||
val prev = w.l6
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[6] = s
|
||||
w[7] = hi + c1 + c2
|
||||
w.l6 = s
|
||||
w.l7 = hi + c1 + c2
|
||||
}
|
||||
|
||||
// Reduction uses umulh only (lo computed with hardware *)
|
||||
@@ -251,84 +251,84 @@ internal fun fieldMulReduceFused(
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
internal fun fieldSqrReduceFused(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
val a0 = a[0]
|
||||
val a1 = a[1]
|
||||
val a2 = a[2]
|
||||
val a3 = a[3]
|
||||
val a0 = a.l0
|
||||
val a1 = a.l1
|
||||
val a2 = a.l2
|
||||
val a3 = a.l3
|
||||
var s: Long = 0L
|
||||
var c1: Long
|
||||
var c2: Long
|
||||
var carry: Long = 0L
|
||||
|
||||
// Pass 1: cross-products a[i]*a[j] for i < j
|
||||
w[0] = 0L
|
||||
w.l0 = 0L
|
||||
mulFull(a0, a1) { lo, hi ->
|
||||
w[1] = lo
|
||||
w.l1 = lo
|
||||
carry = hi
|
||||
}
|
||||
|
||||
mulFull(a0, a2) { lo, hi ->
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w[2] = s
|
||||
w.l2 = s
|
||||
carry = hi + c1
|
||||
}
|
||||
mulFull(a0, a3) { lo, hi ->
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w[3] = s
|
||||
w[4] = hi + c1
|
||||
w.l3 = s
|
||||
w.l4 = hi + c1
|
||||
}
|
||||
|
||||
mulFull(a1, a2) { lo, hi ->
|
||||
val prev = w[3]
|
||||
val prev = w.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w[3] = s
|
||||
w.l3 = s
|
||||
carry = hi + c1
|
||||
}
|
||||
mulFull(a1, a3) { lo, hi ->
|
||||
val prev = w[4]
|
||||
val prev = w.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[4] = s
|
||||
w[5] = hi + c1 + c2
|
||||
w.l4 = s
|
||||
w.l5 = hi + c1 + c2
|
||||
}
|
||||
|
||||
mulFull(a2, a3) { lo, hi ->
|
||||
val prev = w[5]
|
||||
val prev = w.l5
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w[5] = s
|
||||
w[6] = hi + c1
|
||||
w.l5 = s
|
||||
w.l6 = hi + c1
|
||||
}
|
||||
|
||||
// Pass 2: double all cross-products (shift left by 1 bit)
|
||||
var v = w[1]
|
||||
w[1] = v shl 1
|
||||
var v = w.l1
|
||||
w.l1 = v shl 1
|
||||
var shiftCarry = v ushr 63
|
||||
v = w[2]
|
||||
w[2] = (v shl 1) or shiftCarry
|
||||
v = w.l2
|
||||
w.l2 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = w[3]
|
||||
w[3] = (v shl 1) or shiftCarry
|
||||
v = w.l3
|
||||
w.l3 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = w[4]
|
||||
w[4] = (v shl 1) or shiftCarry
|
||||
v = w.l4
|
||||
w.l4 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = w[5]
|
||||
w[5] = (v shl 1) or shiftCarry
|
||||
v = w.l5
|
||||
w.l5 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = w[6]
|
||||
w[6] = (v shl 1) or shiftCarry
|
||||
v = w.l6
|
||||
w.l6 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
w[7] = shiftCarry
|
||||
w.l7 = shiftCarry
|
||||
|
||||
// Pass 3: add diagonal products a[i]²
|
||||
// Use the original pattern: lo = a*a, hi = umulh(a,a) since the diagonal
|
||||
@@ -339,50 +339,50 @@ internal fun fieldSqrReduceFused(
|
||||
|
||||
dLo = a0 * a0
|
||||
dHi = umulhFused(a0, a0)
|
||||
w[0] = dLo
|
||||
s = w[1] + dHi
|
||||
c1 = if (uLtInline(s, w[1])) 1L else 0L
|
||||
w[1] = s
|
||||
w.l0 = dLo
|
||||
s = w.l1 + dHi
|
||||
c1 = if (uLtInline(s, w.l1)) 1L else 0L
|
||||
w.l1 = s
|
||||
var dCarry = c1
|
||||
|
||||
dLo = a1 * a1
|
||||
dHi = umulhFused(a1, a1)
|
||||
s = w[2] + dLo
|
||||
c1 = if (uLtInline(s, w[2])) 1L else 0L
|
||||
s = w.l2 + dLo
|
||||
c1 = if (uLtInline(s, w.l2)) 1L else 0L
|
||||
s += dCarry
|
||||
c2 = if (uLtInline(s, dCarry)) 1L else 0L
|
||||
w[2] = s
|
||||
prev = w[3] + dHi
|
||||
val c3a = if (uLtInline(prev, w[3])) 1L else 0L
|
||||
w.l2 = s
|
||||
prev = w.l3 + dHi
|
||||
val c3a = if (uLtInline(prev, w.l3)) 1L else 0L
|
||||
prev += c1 + c2
|
||||
val c4a = if (uLtInline(prev, c1 + c2)) 1L else 0L
|
||||
w[3] = prev
|
||||
w.l3 = prev
|
||||
dCarry = c3a + c4a
|
||||
|
||||
dLo = a2 * a2
|
||||
dHi = umulhFused(a2, a2)
|
||||
s = w[4] + dLo
|
||||
c1 = if (uLtInline(s, w[4])) 1L else 0L
|
||||
s = w.l4 + dLo
|
||||
c1 = if (uLtInline(s, w.l4)) 1L else 0L
|
||||
s += dCarry
|
||||
c2 = if (uLtInline(s, dCarry)) 1L else 0L
|
||||
w[4] = s
|
||||
prev = w[5] + dHi
|
||||
val c3b = if (uLtInline(prev, w[5])) 1L else 0L
|
||||
w.l4 = s
|
||||
prev = w.l5 + dHi
|
||||
val c3b = if (uLtInline(prev, w.l5)) 1L else 0L
|
||||
prev += c1 + c2
|
||||
val c4b = if (uLtInline(prev, c1 + c2)) 1L else 0L
|
||||
w[5] = prev
|
||||
w.l5 = prev
|
||||
dCarry = c3b + c4b
|
||||
|
||||
dLo = a3 * a3
|
||||
dHi = umulhFused(a3, a3)
|
||||
s = w[6] + dLo
|
||||
c1 = if (uLtInline(s, w[6])) 1L else 0L
|
||||
s = w.l6 + dLo
|
||||
c1 = if (uLtInline(s, w.l6)) 1L else 0L
|
||||
s += dCarry
|
||||
c2 = if (uLtInline(s, dCarry)) 1L else 0L
|
||||
w[6] = s
|
||||
prev = w[7] + dHi
|
||||
w.l6 = s
|
||||
prev = w.l7 + dHi
|
||||
prev += c1 + c2
|
||||
w[7] = prev
|
||||
w.l7 = prev
|
||||
|
||||
// Reduction
|
||||
reduceWideInline(out, w) { x, y -> umulhFused(x, y) }
|
||||
|
||||
+152
-152
@@ -67,10 +67,10 @@ package com.vitorpamplona.quartz.utils.secp256k1
|
||||
* Platform implementations call fieldMulReduceWith with the best available intrinsic.
|
||||
*/
|
||||
internal expect fun fieldMulReduce(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
w: Wide8,
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -78,9 +78,9 @@ internal expect fun fieldMulReduce(
|
||||
* Platform implementations call fieldSqrReduceWith with the best available intrinsic.
|
||||
*/
|
||||
internal expect fun fieldSqrReduce(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
w: Wide8,
|
||||
)
|
||||
|
||||
// P[0] constant for reduceSelf (duplicated here because inline functions can't
|
||||
@@ -101,21 +101,21 @@ private const val FIELD_P0 = -4294968273L // 0xFFFFFFFEFFFFFC2F
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
internal inline fun fieldMulReduceWith(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
w: Wide8,
|
||||
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]
|
||||
val a0 = a.l0
|
||||
val a1 = a.l1
|
||||
val a2 = a.l2
|
||||
val a3 = a.l3
|
||||
val b0 = b.l0
|
||||
val b1 = b.l1
|
||||
val b2 = b.l2
|
||||
val b3 = b.l3
|
||||
var lo: Long
|
||||
var hi: Long
|
||||
var prev: Long
|
||||
@@ -126,143 +126,143 @@ internal inline fun fieldMulReduceWith(
|
||||
|
||||
// Row 0: a0 × [b0,b1,b2,b3]
|
||||
lo = a0 * b0
|
||||
w[0] = lo
|
||||
w.l0 = lo
|
||||
carry = umulh(a0, b0)
|
||||
|
||||
lo = a0 * b1
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w[1] = s
|
||||
w.l1 = s
|
||||
carry = umulh(a0, b1) + c1
|
||||
|
||||
lo = a0 * b2
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w[2] = s
|
||||
w.l2 = s
|
||||
carry = umulh(a0, b2) + c1
|
||||
|
||||
lo = a0 * b3
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w[3] = s
|
||||
w[4] = umulh(a0, b3) + c1
|
||||
w.l3 = s
|
||||
w.l4 = umulh(a0, b3) + c1
|
||||
|
||||
// Row 1: a1 × [b0,b1,b2,b3]
|
||||
lo = a1 * b0
|
||||
hi = umulh(a1, b0)
|
||||
prev = w[1]
|
||||
prev = w.l1
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w[1] = s
|
||||
w.l1 = s
|
||||
carry = hi + c1
|
||||
|
||||
lo = a1 * b1
|
||||
hi = umulh(a1, b1)
|
||||
prev = w[2]
|
||||
prev = w.l2
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[2] = s
|
||||
w.l2 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a1 * b2
|
||||
hi = umulh(a1, b2)
|
||||
prev = w[3]
|
||||
prev = w.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[3] = s
|
||||
w.l3 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a1 * b3
|
||||
hi = umulh(a1, b3)
|
||||
prev = w[4]
|
||||
prev = w.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[4] = s
|
||||
w[5] = hi + c1 + c2
|
||||
w.l4 = s
|
||||
w.l5 = hi + c1 + c2
|
||||
|
||||
// Row 2: a2 × [b0,b1,b2,b3]
|
||||
lo = a2 * b0
|
||||
hi = umulh(a2, b0)
|
||||
prev = w[2]
|
||||
prev = w.l2
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w[2] = s
|
||||
w.l2 = s
|
||||
carry = hi + c1
|
||||
|
||||
lo = a2 * b1
|
||||
hi = umulh(a2, b1)
|
||||
prev = w[3]
|
||||
prev = w.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[3] = s
|
||||
w.l3 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a2 * b2
|
||||
hi = umulh(a2, b2)
|
||||
prev = w[4]
|
||||
prev = w.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[4] = s
|
||||
w.l4 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a2 * b3
|
||||
hi = umulh(a2, b3)
|
||||
prev = w[5]
|
||||
prev = w.l5
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[5] = s
|
||||
w[6] = hi + c1 + c2
|
||||
w.l5 = s
|
||||
w.l6 = hi + c1 + c2
|
||||
|
||||
// Row 3: a3 × [b0,b1,b2,b3]
|
||||
lo = a3 * b0
|
||||
hi = umulh(a3, b0)
|
||||
prev = w[3]
|
||||
prev = w.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w[3] = s
|
||||
w.l3 = s
|
||||
carry = hi + c1
|
||||
|
||||
lo = a3 * b1
|
||||
hi = umulh(a3, b1)
|
||||
prev = w[4]
|
||||
prev = w.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[4] = s
|
||||
w.l4 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a3 * b2
|
||||
hi = umulh(a3, b2)
|
||||
prev = w[5]
|
||||
prev = w.l5
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[5] = s
|
||||
w.l5 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a3 * b3
|
||||
hi = umulh(a3, b3)
|
||||
prev = w[6]
|
||||
prev = w.l6
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[6] = s
|
||||
w[7] = hi + c1 + c2
|
||||
w.l6 = s
|
||||
w.l7 = hi + c1 + c2
|
||||
|
||||
// === Stage 2: mod-p reduction (2^256 ≡ 2^32 + 977 mod p) ===
|
||||
reduceWideInline(out, w, umulh)
|
||||
@@ -275,15 +275,15 @@ internal inline fun fieldMulReduceWith(
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
internal inline fun fieldSqrReduceWith(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
w: Wide8,
|
||||
umulh: (Long, Long) -> Long,
|
||||
) {
|
||||
val a0 = a[0]
|
||||
val a1 = a[1]
|
||||
val a2 = a[2]
|
||||
val a3 = a[3]
|
||||
val a0 = a.l0
|
||||
val a1 = a.l1
|
||||
val a2 = a.l2
|
||||
val a3 = a.l3
|
||||
var lo: Long
|
||||
var hi: Long
|
||||
var prev: Long
|
||||
@@ -294,117 +294,117 @@ internal inline fun fieldSqrReduceWith(
|
||||
var v: Long
|
||||
|
||||
// Pass 1: cross-products a[i]*a[j] for i < j
|
||||
w[0] = 0L
|
||||
w.l0 = 0L
|
||||
lo = a0 * a1
|
||||
w[1] = lo
|
||||
w.l1 = lo
|
||||
carry = umulh(a0, a1)
|
||||
|
||||
lo = a0 * a2
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w[2] = s
|
||||
w.l2 = s
|
||||
carry = umulh(a0, a2) + c1
|
||||
|
||||
lo = a0 * a3
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
w[3] = s
|
||||
w[4] = umulh(a0, a3) + c1
|
||||
w.l3 = s
|
||||
w.l4 = umulh(a0, a3) + c1
|
||||
|
||||
lo = a1 * a2
|
||||
hi = umulh(a1, a2)
|
||||
prev = w[3]
|
||||
prev = w.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w[3] = s
|
||||
w.l3 = s
|
||||
carry = hi + c1
|
||||
|
||||
lo = a1 * a3
|
||||
hi = umulh(a1, a3)
|
||||
prev = w[4]
|
||||
prev = w.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
w[4] = s
|
||||
w[5] = hi + c1 + c2
|
||||
w.l4 = s
|
||||
w.l5 = hi + c1 + c2
|
||||
|
||||
lo = a2 * a3
|
||||
hi = umulh(a2, a3)
|
||||
prev = w[5]
|
||||
prev = w.l5
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
w[5] = s
|
||||
w[6] = hi + c1
|
||||
w.l5 = s
|
||||
w.l6 = hi + c1
|
||||
|
||||
// Pass 2: double all cross-products (shift left by 1 bit)
|
||||
v = w[1]
|
||||
w[1] = v shl 1
|
||||
v = w.l1
|
||||
w.l1 = v shl 1
|
||||
var shiftCarry = v ushr 63
|
||||
v = w[2]
|
||||
w[2] = (v shl 1) or shiftCarry
|
||||
v = w.l2
|
||||
w.l2 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = w[3]
|
||||
w[3] = (v shl 1) or shiftCarry
|
||||
v = w.l3
|
||||
w.l3 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = w[4]
|
||||
w[4] = (v shl 1) or shiftCarry
|
||||
v = w.l4
|
||||
w.l4 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = w[5]
|
||||
w[5] = (v shl 1) or shiftCarry
|
||||
v = w.l5
|
||||
w.l5 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = w[6]
|
||||
w[6] = (v shl 1) or shiftCarry
|
||||
v = w.l6
|
||||
w.l6 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
w[7] = shiftCarry
|
||||
w.l7 = shiftCarry
|
||||
|
||||
// Pass 3: add diagonal products a[i]²
|
||||
lo = a0 * a0
|
||||
hi = umulh(a0, a0)
|
||||
w[0] = lo
|
||||
s = w[1] + hi
|
||||
c1 = if (uLtInline(s, w[1])) 1L else 0L
|
||||
w[1] = s
|
||||
w.l0 = lo
|
||||
s = w.l1 + hi
|
||||
c1 = if (uLtInline(s, w.l1)) 1L else 0L
|
||||
w.l1 = s
|
||||
var dCarry = c1
|
||||
|
||||
lo = a1 * a1
|
||||
hi = umulh(a1, a1)
|
||||
s = w[2] + lo
|
||||
c1 = if (uLtInline(s, w[2])) 1L else 0L
|
||||
s = w.l2 + lo
|
||||
c1 = if (uLtInline(s, w.l2)) 1L else 0L
|
||||
s += dCarry
|
||||
c2 = if (uLtInline(s, dCarry)) 1L else 0L
|
||||
w[2] = s
|
||||
prev = w[3] + hi
|
||||
val c3a = if (uLtInline(prev, w[3])) 1L else 0L
|
||||
w.l2 = s
|
||||
prev = w.l3 + hi
|
||||
val c3a = if (uLtInline(prev, w.l3)) 1L else 0L
|
||||
prev += c1 + c2
|
||||
val c4a = if (uLtInline(prev, c1 + c2)) 1L else 0L
|
||||
w[3] = prev
|
||||
w.l3 = prev
|
||||
dCarry = c3a + c4a
|
||||
|
||||
lo = a2 * a2
|
||||
hi = umulh(a2, a2)
|
||||
s = w[4] + lo
|
||||
c1 = if (uLtInline(s, w[4])) 1L else 0L
|
||||
s = w.l4 + lo
|
||||
c1 = if (uLtInline(s, w.l4)) 1L else 0L
|
||||
s += dCarry
|
||||
c2 = if (uLtInline(s, dCarry)) 1L else 0L
|
||||
w[4] = s
|
||||
prev = w[5] + hi
|
||||
val c3b = if (uLtInline(prev, w[5])) 1L else 0L
|
||||
w.l4 = s
|
||||
prev = w.l5 + hi
|
||||
val c3b = if (uLtInline(prev, w.l5)) 1L else 0L
|
||||
prev += c1 + c2
|
||||
val c4b = if (uLtInline(prev, c1 + c2)) 1L else 0L
|
||||
w[5] = prev
|
||||
w.l5 = prev
|
||||
dCarry = c3b + c4b
|
||||
|
||||
lo = a3 * a3
|
||||
hi = umulh(a3, a3)
|
||||
s = w[6] + lo
|
||||
c1 = if (uLtInline(s, w[6])) 1L else 0L
|
||||
s = w.l6 + lo
|
||||
c1 = if (uLtInline(s, w.l6)) 1L else 0L
|
||||
s += dCarry
|
||||
c2 = if (uLtInline(s, dCarry)) 1L else 0L
|
||||
w[6] = s
|
||||
prev = w[7] + hi
|
||||
w.l6 = s
|
||||
prev = w.l7 + hi
|
||||
prev += c1 + c2
|
||||
w[7] = prev
|
||||
w.l7 = prev
|
||||
|
||||
// === Reduction ===
|
||||
reduceWideInline(out, w, umulh)
|
||||
@@ -417,8 +417,8 @@ internal inline fun fieldSqrReduceWith(
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
internal inline fun reduceWideInline(
|
||||
out: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
w: Wide8,
|
||||
umulh: (Long, Long) -> Long,
|
||||
) {
|
||||
val c = 4294968273L // 2^32 + 977
|
||||
@@ -430,84 +430,84 @@ internal inline fun reduceWideInline(
|
||||
var c2: Long
|
||||
|
||||
// Round 1: acc = lo + hi × C
|
||||
hcLo = w[4] * c
|
||||
hcHi = umulh(w[4], c)
|
||||
s1 = w[0] + hcLo
|
||||
c1 = if (uLtInline(s1, w[0])) 1L else 0L
|
||||
out[0] = s1
|
||||
hcLo = w.l4 * c
|
||||
hcHi = umulh(w.l4, c)
|
||||
s1 = w.l0 + hcLo
|
||||
c1 = if (uLtInline(s1, w.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
var carry = hcHi + c1
|
||||
|
||||
hcLo = w[5] * c
|
||||
hcHi = umulh(w[5], c)
|
||||
s1 = w[1] + hcLo
|
||||
c1 = if (uLtInline(s1, w[1])) 1L else 0L
|
||||
hcLo = w.l5 * c
|
||||
hcHi = umulh(w.l5, c)
|
||||
s1 = w.l1 + hcLo
|
||||
c1 = if (uLtInline(s1, w.l1)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[1] = s2
|
||||
out.l1 = s2
|
||||
carry = hcHi + c1 + c2
|
||||
|
||||
hcLo = w[6] * c
|
||||
hcHi = umulh(w[6], c)
|
||||
s1 = w[2] + hcLo
|
||||
c1 = if (uLtInline(s1, w[2])) 1L else 0L
|
||||
hcLo = w.l6 * c
|
||||
hcHi = umulh(w.l6, c)
|
||||
s1 = w.l2 + hcLo
|
||||
c1 = if (uLtInline(s1, w.l2)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[2] = s2
|
||||
out.l2 = s2
|
||||
carry = hcHi + c1 + c2
|
||||
|
||||
hcLo = w[7] * c
|
||||
hcHi = umulh(w[7], c)
|
||||
s1 = w[3] + hcLo
|
||||
c1 = if (uLtInline(s1, w[3])) 1L else 0L
|
||||
hcLo = w.l7 * c
|
||||
hcHi = umulh(w.l7, c)
|
||||
s1 = w.l3 + hcLo
|
||||
c1 = if (uLtInline(s1, w.l3)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[3] = s2
|
||||
out.l3 = 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 (uLtInline(s1, out[0])) 1L else 0L
|
||||
out[0] = s1
|
||||
s1 = out.l0 + ccLo
|
||||
c1 = if (uLtInline(s1, out.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
var prop = ccHi + c1
|
||||
if (prop != 0L) {
|
||||
s1 = out[1] + prop
|
||||
prop = if (uLtInline(s1, out[1])) 1L else 0L
|
||||
out[1] = s1
|
||||
s1 = out.l1 + prop
|
||||
prop = if (uLtInline(s1, out.l1)) 1L else 0L
|
||||
out.l1 = s1
|
||||
if (prop != 0L) {
|
||||
s1 = out[2] + prop
|
||||
prop = if (uLtInline(s1, out[2])) 1L else 0L
|
||||
out[2] = s1
|
||||
s1 = out.l2 + prop
|
||||
prop = if (uLtInline(s1, out.l2)) 1L else 0L
|
||||
out.l2 = s1
|
||||
if (prop != 0L) {
|
||||
s1 = out[3] + prop
|
||||
prop = if (uLtInline(s1, out[3])) 1L else 0L
|
||||
out[3] = s1
|
||||
s1 = out.l3 + prop
|
||||
prop = if (uLtInline(s1, out.l3)) 1L else 0L
|
||||
out.l3 = s1
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prop != 0L) {
|
||||
s1 = out[0] + c
|
||||
c1 = if (uLtInline(s1, out[0])) 1L else 0L
|
||||
out[0] = s1
|
||||
s1 = out.l0 + c
|
||||
c1 = if (uLtInline(s1, out.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
if (c1 != 0L) {
|
||||
out[1]++
|
||||
if (out[1] == 0L) {
|
||||
out[2]++
|
||||
if (out[2] == 0L) out[3]++
|
||||
out.l1++
|
||||
if (out.l1 == 0L) {
|
||||
out.l2++
|
||||
if (out.l2 == 0L) out.l3++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
if (out.l3 == -1L && out.l2 == -1L && out.l1 == -1L &&
|
||||
(out.l0 xor Long.MIN_VALUE) >= (FIELD_P0 xor Long.MIN_VALUE)
|
||||
) {
|
||||
out[0] -= FIELD_P0
|
||||
out[1] = 0L
|
||||
out[2] = 0L
|
||||
out[3] = 0L
|
||||
out.l0 -= FIELD_P0
|
||||
out.l1 = 0L
|
||||
out.l2 = 0L
|
||||
out.l3 = 0L
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ package com.vitorpamplona.quartz.utils.secp256k1
|
||||
|
||||
/**
|
||||
* Arithmetic modulo the secp256k1 field prime: p = 2^256 - 2^32 - 977.
|
||||
* Uses LongArray(4) limbs (4×64-bit).
|
||||
* Uses Fe4 limbs (4×64-bit).
|
||||
*
|
||||
* Hot-path mul/sqr accept a pre-fetched LongArray(8) wide buffer to avoid
|
||||
* Hot-path mul/sqr accept a pre-fetched Wide8 wide buffer to avoid
|
||||
* ThreadLocal.get() overhead (~20-30ns per call, 500+ calls per scalar mul).
|
||||
*
|
||||
* Key difference from C libsecp256k1: no lazy reduction / magnitude tracking.
|
||||
@@ -41,40 +41,40 @@ package com.vitorpamplona.quartz.utils.secp256k1
|
||||
internal object FieldP {
|
||||
// p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
|
||||
val P =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
-4294968273L, // 0xFFFFFFFEFFFFFC2F
|
||||
-1L, // 0xFFFFFFFFFFFFFFFF
|
||||
-1L, // 0xFFFFFFFFFFFFFFFF
|
||||
-1L, // 0xFFFFFFFFFFFFFFFF
|
||||
)
|
||||
|
||||
private val wide = ScratchLocal { LongArray(8) }
|
||||
private val wide = ScratchLocal { Wide8() }
|
||||
|
||||
// Pre-allocated scratch for inv/sqrt addition chains (11 field elements).
|
||||
// Avoids 11 LongArray(4) allocations per inv/sqrt call.
|
||||
private val chainScratch = ScratchLocal { Array(11) { LongArray(4) } }
|
||||
// Avoids 11 Fe4 allocations per inv/sqrt call.
|
||||
private val chainScratch = ScratchLocal { Array(11) { Fe4() } }
|
||||
|
||||
/** Get a thread-local wide buffer. Call once at the top-level entry point, then pass through. */
|
||||
fun getWide(): LongArray = wide.get()
|
||||
fun getWide(): Wide8 = wide.get()
|
||||
|
||||
// ==================== Core arithmetic ====================
|
||||
|
||||
fun add(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
) {
|
||||
val carry = U256.addTo(out, a, b)
|
||||
if (carry != 0) {
|
||||
// Overflow past 2^256: add 2^256 mod p = 2^32 + 977 = 0x1000003D1
|
||||
val s1 = out[0] + 4294968273L
|
||||
val c1 = if (uLtInline(s1, out[0])) 1L else 0L
|
||||
out[0] = s1
|
||||
val s1 = out.l0 + 4294968273L
|
||||
val c1 = if (uLtInline(s1, out.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
if (c1 != 0L) {
|
||||
out[1]++
|
||||
if (out[1] == 0L) {
|
||||
out[2]++
|
||||
if (out[2] == 0L) out[3]++
|
||||
out.l1++
|
||||
if (out.l1 == 0L) {
|
||||
out.l2++
|
||||
if (out.l2 == 0L) out.l3++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,30 +87,30 @@ internal object FieldP {
|
||||
* case needs work (subtract 1 with borrow propagation). ~500 calls/verify.
|
||||
*/
|
||||
fun sub(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
) {
|
||||
val borrow = U256.subTo(out, a, b)
|
||||
if (borrow != 0) {
|
||||
// Add P = [P0, -1, -1, -1].
|
||||
val s0 = out[0] + P0
|
||||
val c0 = if (uLtInline(s0, out[0])) 1L else 0L
|
||||
out[0] = s0
|
||||
val s0 = out.l0 + P0
|
||||
val c0 = if (uLtInline(s0, out.l0)) 1L else 0L
|
||||
out.l0 = 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]--
|
||||
if (out.l1 != 0L) {
|
||||
out.l1--
|
||||
} else {
|
||||
out[1] = -1L // 0-1 wraps
|
||||
if (out[2] != 0L) {
|
||||
out[2]--
|
||||
out.l1 = -1L // 0-1 wraps
|
||||
if (out.l2 != 0L) {
|
||||
out.l2--
|
||||
} else {
|
||||
out[2] = -1L
|
||||
out[3]--
|
||||
out.l2 = -1L
|
||||
out.l3--
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,9 +119,9 @@ internal object FieldP {
|
||||
|
||||
/** Multiply with ThreadLocal wide buffer (convenience for non-hot paths). */
|
||||
fun mul(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
) {
|
||||
val w = wide.get()
|
||||
fieldMulReduce(out, a, b, w)
|
||||
@@ -129,18 +129,18 @@ internal object FieldP {
|
||||
|
||||
/** Multiply with caller-provided wide buffer (hot path — no ThreadLocal lookup). */
|
||||
fun mul(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
fieldMulReduce(out, a, b, w)
|
||||
}
|
||||
|
||||
/** Square with ThreadLocal wide buffer (convenience for non-hot paths). */
|
||||
fun sqr(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
) {
|
||||
val w = wide.get()
|
||||
fieldSqrReduce(out, a, w)
|
||||
@@ -148,9 +148,9 @@ internal object FieldP {
|
||||
|
||||
/** Square with caller-provided wide buffer (hot path — no ThreadLocal lookup). */
|
||||
fun sqr(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
fieldSqrReduce(out, a, w)
|
||||
}
|
||||
@@ -158,28 +158,28 @@ internal object FieldP {
|
||||
/**
|
||||
* 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).
|
||||
* Avoids generic U256.subTo + P field reads (~260 calls/verify).
|
||||
*/
|
||||
fun neg(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
) {
|
||||
if (U256.isZero(a)) {
|
||||
out[0] = 0L
|
||||
out[1] = 0L
|
||||
out[2] = 0L
|
||||
out[3] = 0L
|
||||
if (a.isZero()) {
|
||||
out.l0 = 0L
|
||||
out.l1 = 0L
|
||||
out.l2 = 0L
|
||||
out.l3 = 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 (uLtInline(P0, a[0])) 1L else 0L
|
||||
// P - a: limb 0 is P0 - a.l0, limbs 1-3 are (-1) - a[i] = ~a[i]
|
||||
out.l0 = P0 - a.l0
|
||||
val borrow = if (uLtInline(P0, a.l0)) 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
|
||||
out.l1 = a.l1.inv() - borrow
|
||||
val b1 = if (a.l1 == -1L && borrow != 0L) 1L else 0L
|
||||
out.l2 = a.l2.inv() - b1
|
||||
val b2 = if (a.l2 == -1L && b1 != 0L) 1L else 0L
|
||||
out.l3 = a.l3.inv() - b2
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,10 +187,10 @@ internal object FieldP {
|
||||
* Unrolled, with P[1..3]=-1 inlined as `mask` (since -1 & mask = mask).
|
||||
*/
|
||||
fun half(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
) {
|
||||
val mask = -(a[0] and 1L) // all 1s if odd, all 0s if even
|
||||
val mask = -(a.l0 and 1L) // all 1s if odd, all 0s if even
|
||||
val p0 = P0 and mask // P[0] masked; P[1..3] are -1, so P[i]&mask = mask
|
||||
var s1: Long
|
||||
var s2: Long
|
||||
@@ -199,46 +199,46 @@ internal object FieldP {
|
||||
|
||||
// Conditional add: out = a + (P & mask), unrolled
|
||||
// Limb 0
|
||||
s1 = a[0] + p0
|
||||
c1 = if (uLtInline(s1, a[0])) 1L else 0L
|
||||
out[0] = s1
|
||||
s1 = a.l0 + p0
|
||||
c1 = if (uLtInline(s1, a.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
var carry = c1
|
||||
// Limb 1
|
||||
s1 = a[1] + mask
|
||||
c1 = if (uLtInline(s1, a[1])) 1L else 0L
|
||||
s1 = a.l1 + mask
|
||||
c1 = if (uLtInline(s1, a.l1)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[1] = s2
|
||||
out.l1 = s2
|
||||
carry = c1 + c2
|
||||
// Limb 2
|
||||
s1 = a[2] + mask
|
||||
c1 = if (uLtInline(s1, a[2])) 1L else 0L
|
||||
s1 = a.l2 + mask
|
||||
c1 = if (uLtInline(s1, a.l2)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[2] = s2
|
||||
out.l2 = s2
|
||||
carry = c1 + c2
|
||||
// Limb 3
|
||||
s1 = a[3] + mask
|
||||
c1 = if (uLtInline(s1, a[3])) 1L else 0L
|
||||
s1 = a.l3 + mask
|
||||
c1 = if (uLtInline(s1, a.l3)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[3] = s2
|
||||
out.l3 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
// Right-shift by 1 (unrolled)
|
||||
out[0] = (out[0] ushr 1) or (out[1] shl 63)
|
||||
out[1] = (out[1] ushr 1) or (out[2] shl 63)
|
||||
out[2] = (out[2] ushr 1) or (out[3] shl 63)
|
||||
out[3] = (out[3] ushr 1) or (carry shl 63)
|
||||
out.l0 = (out.l0 ushr 1) or (out.l1 shl 63)
|
||||
out.l1 = (out.l1 ushr 1) or (out.l2 shl 63)
|
||||
out.l2 = (out.l2 ushr 1) or (out.l3 shl 63)
|
||||
out.l3 = (out.l3 ushr 1) or (carry shl 63)
|
||||
}
|
||||
|
||||
// ==================== Inversion and square root (optimized addition chains) ====================
|
||||
|
||||
fun inv(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
) {
|
||||
require(!U256.isZero(a))
|
||||
require(!a.isZero())
|
||||
val w = wide.get()
|
||||
val cs = chainScratch.get()
|
||||
val x2 = cs[0]
|
||||
@@ -287,8 +287,8 @@ internal object FieldP {
|
||||
}
|
||||
|
||||
fun sqrt(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
): Boolean {
|
||||
val w = wide.get()
|
||||
val cs = chainScratch.get()
|
||||
@@ -336,48 +336,48 @@ internal object FieldP {
|
||||
// Verify: check that out² == a (mod p)
|
||||
// Reuse cs[0], cs[1] as scratch since we're done with the chain
|
||||
mul(cs[0], out, out, w) // cs[0] = out²
|
||||
U256.copyInto(cs[1], a)
|
||||
cs[1].copyFrom(a)
|
||||
reduceSelf(cs[1]) // cs[1] = a reduced
|
||||
return U256.cmp(cs[0], cs[1]) == 0
|
||||
}
|
||||
|
||||
private fun sqrN(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
n: Int,
|
||||
w: LongArray,
|
||||
w: Wide8,
|
||||
) {
|
||||
U256.copyInto(out, a)
|
||||
out.copyFrom(a)
|
||||
repeat(n) { sqr(out, out, w) }
|
||||
}
|
||||
|
||||
private fun sqrN(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
n: Int,
|
||||
) {
|
||||
U256.copyInto(out, a)
|
||||
out.copyFrom(a)
|
||||
repeat(n) { sqr(out, out) }
|
||||
}
|
||||
|
||||
// ==================== Reduction ====================
|
||||
|
||||
// P[0] cached as a constant to avoid array load in the hot reduceSelf path.
|
||||
// P[0] cached as a constant to avoid field load in the hot reduceSelf path.
|
||||
private const val P0 = -4294968273L // 0xFFFFFFFEFFFFFC2F
|
||||
|
||||
fun reduceSelf(a: LongArray) {
|
||||
fun reduceSelf(a: Fe4) {
|
||||
// Exploit P's structure: P = [P0, -1, -1, -1] where P[1..3] = 0xFFFFFFFFFFFFFFFF.
|
||||
// a >= P only if a[3]==a[2]==a[1]==-1 AND a[0] >= P[0]. The first check (a[3]==-1)
|
||||
// a >= P only if a.l3==a.l2==a.l1==-1 AND a.l0 >= P[0]. The first check (a.l3==-1)
|
||||
// fails >99.99% of the time for random field elements, making this a single branch.
|
||||
if (a[3] == -1L && a[2] == -1L && a[1] == -1L &&
|
||||
(a[0] xor Long.MIN_VALUE) >= (P0 xor Long.MIN_VALUE)
|
||||
if (a.l3 == -1L && a.l2 == -1L && a.l1 == -1L &&
|
||||
(a.l0 xor Long.MIN_VALUE) >= (P0 xor Long.MIN_VALUE)
|
||||
) {
|
||||
// Inline P subtraction: when a[1..3] = -1 and a[0] >= P0,
|
||||
// a - P = [a[0] - P0, 0, 0, 0] (no borrows since P[1..3] = -1).
|
||||
a[0] -= P0
|
||||
a[1] = 0L
|
||||
a[2] = 0L
|
||||
a[3] = 0L
|
||||
// Inline P subtraction: when a.l1..l3 = -1 and a.l0 >= P0,
|
||||
// a - P = [a.l0 - P0, 0, 0, 0] (no borrows since P[1..3] = -1).
|
||||
a.l0 -= P0
|
||||
a.l1 = 0L
|
||||
a.l2 = 0L
|
||||
a.l3 = 0L
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,8 +388,8 @@ internal object FieldP {
|
||||
* Three stages: fold 512→~260 bits, fold carry×C, final reduceSelf.
|
||||
*/
|
||||
fun reduceWide(
|
||||
out: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
val c = 4294968273L // 2^32 + 977
|
||||
var hcLo: Long
|
||||
@@ -402,77 +402,77 @@ internal object FieldP {
|
||||
// Round 1: acc = lo + hi × C (4 limbs, unrolled)
|
||||
|
||||
// Limb 0 (no carry input)
|
||||
hcLo = w[4] * c
|
||||
hcHi = unsignedMultiplyHigh(w[4], c)
|
||||
s1 = w[0] + hcLo
|
||||
c1 = if (uLtInline(s1, w[0])) 1L else 0L
|
||||
out[0] = s1
|
||||
hcLo = w.l4 * c
|
||||
hcHi = unsignedMultiplyHigh(w.l4, c)
|
||||
s1 = w.l0 + hcLo
|
||||
c1 = if (uLtInline(s1, w.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
var carry = hcHi + c1
|
||||
|
||||
// Limb 1
|
||||
hcLo = w[5] * c
|
||||
hcHi = unsignedMultiplyHigh(w[5], c)
|
||||
s1 = w[1] + hcLo
|
||||
c1 = if (uLtInline(s1, w[1])) 1L else 0L
|
||||
hcLo = w.l5 * c
|
||||
hcHi = unsignedMultiplyHigh(w.l5, c)
|
||||
s1 = w.l1 + hcLo
|
||||
c1 = if (uLtInline(s1, w.l1)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[1] = s2
|
||||
out.l1 = s2
|
||||
carry = hcHi + c1 + c2
|
||||
|
||||
// Limb 2
|
||||
hcLo = w[6] * c
|
||||
hcHi = unsignedMultiplyHigh(w[6], c)
|
||||
s1 = w[2] + hcLo
|
||||
c1 = if (uLtInline(s1, w[2])) 1L else 0L
|
||||
hcLo = w.l6 * c
|
||||
hcHi = unsignedMultiplyHigh(w.l6, c)
|
||||
s1 = w.l2 + hcLo
|
||||
c1 = if (uLtInline(s1, w.l2)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[2] = s2
|
||||
out.l2 = s2
|
||||
carry = hcHi + c1 + c2
|
||||
|
||||
// Limb 3
|
||||
hcLo = w[7] * c
|
||||
hcHi = unsignedMultiplyHigh(w[7], c)
|
||||
s1 = w[3] + hcLo
|
||||
c1 = if (uLtInline(s1, w[3])) 1L else 0L
|
||||
hcLo = w.l7 * c
|
||||
hcHi = unsignedMultiplyHigh(w.l7, c)
|
||||
s1 = w.l3 + hcLo
|
||||
c1 = if (uLtInline(s1, w.l3)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[3] = s2
|
||||
out.l3 = s2
|
||||
carry = hcHi + c1 + c2
|
||||
|
||||
// Round 2: if carry > 0, fold carry × C back in
|
||||
if (carry != 0L) {
|
||||
val ccLo = carry * c
|
||||
val ccHi = unsignedMultiplyHigh(carry, c)
|
||||
s1 = out[0] + ccLo
|
||||
c1 = if (uLtInline(s1, out[0])) 1L else 0L
|
||||
out[0] = s1
|
||||
s1 = out.l0 + ccLo
|
||||
c1 = if (uLtInline(s1, out.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
// Propagate carry (unrolled, with early exit)
|
||||
var prop = ccHi + c1
|
||||
if (prop != 0L) {
|
||||
s1 = out[1] + prop
|
||||
prop = if (uLtInline(s1, out[1])) 1L else 0L
|
||||
out[1] = s1
|
||||
s1 = out.l1 + prop
|
||||
prop = if (uLtInline(s1, out.l1)) 1L else 0L
|
||||
out.l1 = s1
|
||||
if (prop != 0L) {
|
||||
s1 = out[2] + prop
|
||||
prop = if (uLtInline(s1, out[2])) 1L else 0L
|
||||
out[2] = s1
|
||||
s1 = out.l2 + prop
|
||||
prop = if (uLtInline(s1, out.l2)) 1L else 0L
|
||||
out.l2 = s1
|
||||
if (prop != 0L) {
|
||||
s1 = out[3] + prop
|
||||
prop = if (uLtInline(s1, out[3])) 1L else 0L
|
||||
out[3] = s1
|
||||
s1 = out.l3 + prop
|
||||
prop = if (uLtInline(s1, out.l3)) 1L else 0L
|
||||
out.l3 = s1
|
||||
}
|
||||
}
|
||||
}
|
||||
// Overflow past 256 bits: 2^256 ≡ C (mod p)
|
||||
if (prop != 0L) {
|
||||
s1 = out[0] + c
|
||||
c1 = if (uLtInline(s1, out[0])) 1L else 0L
|
||||
out[0] = s1
|
||||
s1 = out.l0 + c
|
||||
c1 = if (uLtInline(s1, out.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
if (c1 != 0L) {
|
||||
out[1]++
|
||||
if (out[1] == 0L) {
|
||||
out[2]++
|
||||
if (out[2] == 0L) out[3]++
|
||||
out.l1++
|
||||
if (out.l1 == 0L) {
|
||||
out.l2++
|
||||
if (out.l2 == 0L) out.l3++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -484,56 +484,56 @@ internal object FieldP {
|
||||
// ==================== Convenience wrappers ====================
|
||||
|
||||
fun add(
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
): LongArray {
|
||||
val r = LongArray(4)
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
): Fe4 {
|
||||
val r = Fe4()
|
||||
add(r, a, b)
|
||||
return r
|
||||
}
|
||||
|
||||
fun sub(
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
): LongArray {
|
||||
val r = LongArray(4)
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
): Fe4 {
|
||||
val r = Fe4()
|
||||
sub(r, a, b)
|
||||
return r
|
||||
}
|
||||
|
||||
fun mul(
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
): LongArray {
|
||||
val r = LongArray(4)
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
): Fe4 {
|
||||
val r = Fe4()
|
||||
mul(r, a, b)
|
||||
return r
|
||||
}
|
||||
|
||||
fun sqr(a: LongArray): LongArray {
|
||||
val r = LongArray(4)
|
||||
fun sqr(a: Fe4): Fe4 {
|
||||
val r = Fe4()
|
||||
sqr(r, a)
|
||||
return r
|
||||
}
|
||||
|
||||
fun neg(a: LongArray): LongArray {
|
||||
val r = LongArray(4)
|
||||
fun neg(a: Fe4): Fe4 {
|
||||
val r = Fe4()
|
||||
neg(r, a)
|
||||
return r
|
||||
}
|
||||
|
||||
fun inv(a: LongArray): LongArray {
|
||||
val r = LongArray(4)
|
||||
fun inv(a: Fe4): Fe4 {
|
||||
val r = Fe4()
|
||||
inv(r, a)
|
||||
return r
|
||||
}
|
||||
|
||||
fun sqrt(a: LongArray): LongArray? {
|
||||
val r = LongArray(4)
|
||||
fun sqrt(a: Fe4): Fe4? {
|
||||
val r = Fe4()
|
||||
return if (sqrt(r, a)) r else null
|
||||
}
|
||||
|
||||
fun reduce(a: LongArray): LongArray {
|
||||
fun reduce(a: Fe4): Fe4 {
|
||||
val r = a.copyOf()
|
||||
reduceSelf(r)
|
||||
return r
|
||||
|
||||
@@ -43,7 +43,7 @@ internal object Glv {
|
||||
/** β: cube root of unity mod p. φ(x,y) = (β·x, y). */
|
||||
@JvmField
|
||||
val BETA =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
-4523465429756870162L,
|
||||
-7138124642204153451L,
|
||||
7954561588662645993L,
|
||||
@@ -53,13 +53,13 @@ internal object Glv {
|
||||
// ==================== GLV Scalar Decomposition ====================
|
||||
|
||||
class Split(
|
||||
@JvmField val k1: LongArray,
|
||||
@JvmField val k2: LongArray,
|
||||
@JvmField val k1: Fe4,
|
||||
@JvmField val k2: Fe4,
|
||||
@JvmField val negK1: Boolean,
|
||||
@JvmField val negK2: Boolean,
|
||||
)
|
||||
|
||||
fun splitScalar(k: LongArray): Split {
|
||||
fun splitScalar(k: Fe4): Split {
|
||||
val c1 = mulShift384(k, G1)
|
||||
val c2 = mulShift384(k, G2)
|
||||
val r2 = ScalarN.add(ScalarN.mul(c1, MINUS_B1), ScalarN.mul(c2, MINUS_B2))
|
||||
@@ -79,12 +79,12 @@ internal object Glv {
|
||||
* Eliminates ~26 LongArray allocations per call (called 2× per verify = ~52 allocs saved).
|
||||
*/
|
||||
fun splitScalarInto(
|
||||
outK1: LongArray,
|
||||
outK2: LongArray,
|
||||
k: LongArray,
|
||||
w: LongArray,
|
||||
t1: LongArray,
|
||||
t2: LongArray,
|
||||
outK1: Fe4,
|
||||
outK2: Fe4,
|
||||
k: Fe4,
|
||||
w: Wide8,
|
||||
t1: Fe4,
|
||||
t2: Fe4,
|
||||
): Split {
|
||||
// c1 = mulShift384(k, G1)
|
||||
mulShift384Into(t1, k, G1, w)
|
||||
@@ -110,19 +110,19 @@ internal object Glv {
|
||||
|
||||
/** Allocation-free mulShift384. */
|
||||
private fun mulShift384Into(
|
||||
out: LongArray,
|
||||
k: LongArray,
|
||||
g: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
k: Fe4,
|
||||
g: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
U256.mulWide(w, k, g)
|
||||
out[0] = w[6]
|
||||
out[1] = w[7]
|
||||
out[2] = 0L
|
||||
out[3] = 0L
|
||||
if (w[5] < 0) { // bit 63 of w[5] = bit 383 (rounding)
|
||||
out[0]++
|
||||
if (out[0] == 0L) out[1]++
|
||||
out.l0 = w.l6
|
||||
out.l1 = w.l7
|
||||
out.l2 = 0L
|
||||
out.l3 = 0L
|
||||
if (w.l5 < 0) { // bit 63 of w.l5 = bit 383 (rounding)
|
||||
out.l0++
|
||||
if (out.l0 == 0L) out.l1++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,13 +135,13 @@ internal object Glv {
|
||||
* The working copy is extended to handle carries past maxBits.
|
||||
*/
|
||||
fun wnaf(
|
||||
scalar: LongArray,
|
||||
scalar: Fe4,
|
||||
w: Int,
|
||||
maxBits: Int,
|
||||
): IntArray {
|
||||
val totalBits = maxBits + w
|
||||
val result = IntArray(totalBits)
|
||||
val s = LongArray(maxOf((totalBits + 63) / 64, scalar.size))
|
||||
val s = LongArray(maxOf((totalBits + 63) / 64, 4))
|
||||
wnafInto(result, s, scalar, w, maxBits)
|
||||
return result
|
||||
}
|
||||
@@ -153,7 +153,7 @@ internal object Glv {
|
||||
fun wnafInto(
|
||||
result: IntArray,
|
||||
sTmp: LongArray,
|
||||
scalar: LongArray,
|
||||
scalar: Fe4,
|
||||
w: Int,
|
||||
maxBits: Int,
|
||||
): Int {
|
||||
@@ -161,7 +161,7 @@ internal object Glv {
|
||||
// Clear output and copy scalar into scratch
|
||||
for (i in 0 until totalBits.coerceAtMost(result.size)) result[i] = 0
|
||||
for (i in sTmp.indices) sTmp[i] = 0
|
||||
scalar.copyInto(sTmp)
|
||||
scalar.copyIntoArray(sTmp)
|
||||
|
||||
var bit = 0
|
||||
var highBit = 0
|
||||
@@ -186,18 +186,18 @@ internal object Glv {
|
||||
|
||||
/** Multiply two 256-bit numbers, return result >> 384 (rounded). */
|
||||
private fun mulShift384(
|
||||
k: LongArray,
|
||||
g: LongArray,
|
||||
): LongArray {
|
||||
val wide = LongArray(8)
|
||||
k: Fe4,
|
||||
g: Fe4,
|
||||
): Fe4 {
|
||||
val wide = Wide8()
|
||||
U256.mulWide(wide, k, g)
|
||||
val result = LongArray(4)
|
||||
val result = Fe4()
|
||||
// 384 bits = 6 Long limbs. Result = wide[6..7], round at bit 383 (wide[5] bit 63)
|
||||
result[0] = wide[6]
|
||||
result[1] = wide[7]
|
||||
if (wide[5] < 0) { // bit 63 of wide[5] = bit 383
|
||||
result[0]++
|
||||
if (result[0] == 0L) result[1]++
|
||||
result.l0 = wide.l6
|
||||
result.l1 = wide.l7
|
||||
if (wide.l5 < 0) { // bit 63 of wide.l5 = bit 383
|
||||
result.l0++
|
||||
if (result.l0 == 0L) result.l1++
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -235,42 +235,42 @@ internal object Glv {
|
||||
// ==================== Constants (from libsecp256k1) ====================
|
||||
|
||||
private val MINUS_LAMBDA =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
-2247357714951666737L,
|
||||
-6304834983940376126L,
|
||||
6546514211138018212L,
|
||||
-6008836872998760673L,
|
||||
)
|
||||
private val G1 =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
-1687969588364726223L,
|
||||
4443515802769476223L,
|
||||
-1698823648040391915L,
|
||||
3496713202691238861L,
|
||||
)
|
||||
private val G2 =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
1545214808910233457L,
|
||||
2455034284347819718L,
|
||||
8022177200260244676L,
|
||||
-1998614352016537560L,
|
||||
)
|
||||
private val MINUS_B1 =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
8022177200260244675L,
|
||||
-1998614352016537560L,
|
||||
0L,
|
||||
0L,
|
||||
)
|
||||
private val MINUS_B2 =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
-2925706260434037204L,
|
||||
-8491525256057179027L,
|
||||
-2L,
|
||||
-1L,
|
||||
)
|
||||
private val N_HALF =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
-2312264954237214560L,
|
||||
6725966010171805725L,
|
||||
-1L,
|
||||
|
||||
@@ -39,47 +39,47 @@ package com.vitorpamplona.quartz.utils.secp256k1
|
||||
*/
|
||||
internal object KeyCodec {
|
||||
/** Curve constant b = 7 in y² = x³ + 7. */
|
||||
private val B = longArrayOf(7L, 0L, 0L, 0L)
|
||||
private val B = Fe4(7L, 0L, 0L, 0L)
|
||||
|
||||
/**
|
||||
* Lift an x-coordinate to a curve point with even y (BIP-340 convention).
|
||||
* Computes y = √(x³ + 7) mod p. Returns false if x is not a valid coordinate.
|
||||
*/
|
||||
fun liftX(
|
||||
outX: LongArray,
|
||||
outY: LongArray,
|
||||
x: LongArray,
|
||||
outX: Fe4,
|
||||
outY: Fe4,
|
||||
x: Fe4,
|
||||
): Boolean {
|
||||
if (U256.cmp(x, FieldP.P) >= 0) return false
|
||||
val t = LongArray(4)
|
||||
val t = Fe4()
|
||||
FieldP.sqr(t, x)
|
||||
FieldP.mul(t, t, x)
|
||||
FieldP.add(t, t, B) // t = x³ + 7
|
||||
if (!FieldP.sqrt(outY, t)) return false
|
||||
U256.copyInto(outX, x)
|
||||
if (outY[0] and 1L != 0L) FieldP.neg(outY, outY) // Ensure even y
|
||||
outX.copyFrom(x)
|
||||
if (outY.l0 and 1L != 0L) FieldP.neg(outY, outY) // Ensure even y
|
||||
return true
|
||||
}
|
||||
|
||||
/** liftX with caller-provided temp buffer (avoids 1 LongArray alloc). */
|
||||
/** liftX with caller-provided temp buffer (avoids 1 Fe4 alloc). */
|
||||
fun liftX(
|
||||
outX: LongArray,
|
||||
outY: LongArray,
|
||||
x: LongArray,
|
||||
tmp: LongArray,
|
||||
outX: Fe4,
|
||||
outY: Fe4,
|
||||
x: Fe4,
|
||||
tmp: Fe4,
|
||||
): Boolean {
|
||||
if (U256.cmp(x, FieldP.P) >= 0) return false
|
||||
FieldP.sqr(tmp, x)
|
||||
FieldP.mul(tmp, tmp, x)
|
||||
FieldP.add(tmp, tmp, B)
|
||||
if (!FieldP.sqrt(outY, tmp)) return false
|
||||
U256.copyInto(outX, x)
|
||||
if (outY[0] and 1L != 0L) FieldP.neg(outY, outY)
|
||||
outX.copyFrom(x)
|
||||
if (outY.l0 and 1L != 0L) FieldP.neg(outY, outY)
|
||||
return true
|
||||
}
|
||||
|
||||
/** Check if y-coordinate is even (LSB = 0). */
|
||||
fun hasEvenY(y: LongArray): Boolean = y[0] and 1L == 0L
|
||||
fun hasEvenY(y: Fe4): Boolean = y.l0 and 1L == 0L
|
||||
|
||||
/**
|
||||
* Parse a serialized public key (33 bytes compressed or 65 bytes uncompressed).
|
||||
@@ -88,20 +88,20 @@ internal object KeyCodec {
|
||||
*/
|
||||
fun parsePublicKey(
|
||||
pubkey: ByteArray,
|
||||
outX: LongArray,
|
||||
outY: LongArray,
|
||||
outX: Fe4,
|
||||
outY: Fe4,
|
||||
): Boolean =
|
||||
when (pubkey.size) {
|
||||
33 if (pubkey[0] == 0x02.toByte() || pubkey[0] == 0x03.toByte()) -> {
|
||||
val x = U256.fromBytes(pubkey.copyOfRange(1, 33))
|
||||
if (U256.cmp(x, FieldP.P) >= 0) return false
|
||||
val t = LongArray(4)
|
||||
val t = Fe4()
|
||||
FieldP.sqr(t, x)
|
||||
FieldP.mul(t, t, x)
|
||||
FieldP.add(t, t, B) // y² = x³ + 7
|
||||
if (!FieldP.sqrt(outY, t)) return false
|
||||
U256.copyInto(outX, x)
|
||||
val isOdd = outY[0] and 1L == 1L
|
||||
outX.copyFrom(x)
|
||||
val isOdd = outY.l0 and 1L == 1L
|
||||
if (isOdd != (pubkey[0] == 0x03.toByte())) FieldP.neg(outY, outY)
|
||||
true
|
||||
}
|
||||
@@ -109,16 +109,16 @@ internal object KeyCodec {
|
||||
65 if pubkey[0] == 0x04.toByte() -> {
|
||||
val x = U256.fromBytes(pubkey.copyOfRange(1, 33))
|
||||
val y = U256.fromBytes(pubkey.copyOfRange(33, 65))
|
||||
val y2 = LongArray(4)
|
||||
val x3p7 = LongArray(4)
|
||||
val t = LongArray(4)
|
||||
val y2 = Fe4()
|
||||
val x3p7 = Fe4()
|
||||
val t = Fe4()
|
||||
FieldP.sqr(y2, y)
|
||||
FieldP.sqr(t, x)
|
||||
FieldP.mul(x3p7, t, x)
|
||||
FieldP.add(x3p7, x3p7, B)
|
||||
if (U256.cmp(y2, x3p7) != 0) return false
|
||||
U256.copyInto(outX, x)
|
||||
U256.copyInto(outY, y)
|
||||
outX.copyFrom(x)
|
||||
outY.copyFrom(y)
|
||||
true
|
||||
}
|
||||
|
||||
@@ -129,8 +129,8 @@ internal object KeyCodec {
|
||||
|
||||
/** Serialize as 65-byte uncompressed: 04 || x (32 bytes) || y (32 bytes). */
|
||||
fun serializeUncompressed(
|
||||
x: LongArray,
|
||||
y: LongArray,
|
||||
x: Fe4,
|
||||
y: Fe4,
|
||||
): ByteArray {
|
||||
val r = ByteArray(65)
|
||||
r[0] = 0x04
|
||||
@@ -141,8 +141,8 @@ internal object KeyCodec {
|
||||
|
||||
/** Serialize as 33-byte compressed: 02/03 || x (32 bytes). */
|
||||
fun serializeCompressed(
|
||||
x: LongArray,
|
||||
y: LongArray,
|
||||
x: Fe4,
|
||||
y: Fe4,
|
||||
): ByteArray {
|
||||
val r = ByteArray(33)
|
||||
r[0] = if (hasEvenY(y)) 0x02 else 0x03
|
||||
|
||||
+48
-46
@@ -42,35 +42,37 @@ import kotlin.jvm.JvmField
|
||||
* is ~3-4ns faster per access on ART. On non-JVM targets, @JvmField is ignored.
|
||||
*/
|
||||
internal class MutablePoint(
|
||||
@JvmField val x: LongArray = LongArray(4),
|
||||
@JvmField val y: LongArray = LongArray(4),
|
||||
@JvmField val z: LongArray = LongArray(4),
|
||||
@JvmField val x: Fe4 = Fe4(),
|
||||
@JvmField val y: Fe4 = Fe4(),
|
||||
@JvmField val z: Fe4 = Fe4(),
|
||||
) {
|
||||
fun isInfinity(): Boolean = (z[0] or z[1] or z[2] or z[3]) == 0L
|
||||
fun isInfinity(): Boolean = z.isZero()
|
||||
|
||||
fun setInfinity() {
|
||||
for (i in 0 until 4) {
|
||||
x[i] = 0L
|
||||
z[i] = 0L
|
||||
}
|
||||
y[0] = 1L
|
||||
for (i in 1 until 4) y[i] = 0L
|
||||
x.setZero()
|
||||
y.l0 = 1L
|
||||
y.l1 = 0L
|
||||
y.l2 = 0L
|
||||
y.l3 = 0L
|
||||
z.setZero()
|
||||
}
|
||||
|
||||
fun copyFrom(other: MutablePoint) {
|
||||
other.x.copyInto(x, 0, 0, 4)
|
||||
other.y.copyInto(y, 0, 0, 4)
|
||||
other.z.copyInto(z, 0, 0, 4)
|
||||
x.copyFrom(other.x)
|
||||
y.copyFrom(other.y)
|
||||
z.copyFrom(other.z)
|
||||
}
|
||||
|
||||
fun setAffine(
|
||||
ax: LongArray,
|
||||
ay: LongArray,
|
||||
ax: Fe4,
|
||||
ay: Fe4,
|
||||
) {
|
||||
ax.copyInto(x, 0, 0, 4)
|
||||
ay.copyInto(y, 0, 0, 4)
|
||||
z[0] = 1L
|
||||
for (i in 1 until 4) z[i] = 0L
|
||||
x.copyFrom(ax)
|
||||
y.copyFrom(ay)
|
||||
z.l0 = 1L
|
||||
z.l1 = 0L
|
||||
z.l2 = 0L
|
||||
z.l3 = 0L
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +82,8 @@ internal class MutablePoint(
|
||||
* @JvmField: see MutablePoint for rationale (eliminates virtual getter calls).
|
||||
*/
|
||||
internal class AffinePoint(
|
||||
@JvmField val x: LongArray = LongArray(4),
|
||||
@JvmField val y: LongArray = LongArray(4),
|
||||
@JvmField val x: Fe4 = Fe4(),
|
||||
@JvmField val y: Fe4 = Fe4(),
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -94,7 +96,7 @@ internal class AffinePoint(
|
||||
* degenerate case, which returns immediately after the recursive call without
|
||||
* using the temps further.
|
||||
*
|
||||
* The wide buffer (LongArray(8)) is pre-fetched once per top-level operation and
|
||||
* The wide buffer (Wide8) is pre-fetched once per top-level operation and
|
||||
* passed through to FieldP.mul/sqr, avoiding ~500+ ThreadLocal.get() calls per
|
||||
* scalar multiplication (~20-30ns each on JVM).
|
||||
*
|
||||
@@ -104,11 +106,11 @@ internal class AffinePoint(
|
||||
* @JvmField compiles these to direct field reads. On non-JVM targets, ignored.
|
||||
*/
|
||||
internal class PointScratch {
|
||||
@JvmField val t = Array(12) { LongArray(4) }
|
||||
@JvmField val t = Array(12) { Fe4() }
|
||||
|
||||
@JvmField val dblCopy = MutablePoint()
|
||||
|
||||
@JvmField val w = LongArray(8)
|
||||
@JvmField val w = Wide8()
|
||||
|
||||
@JvmField val wnaf1 = IntArray(145)
|
||||
|
||||
@@ -122,7 +124,7 @@ internal class PointScratch {
|
||||
|
||||
@JvmField val mixTmp = MutablePoint()
|
||||
|
||||
@JvmField val mixNegY = LongArray(4)
|
||||
@JvmField val mixNegY = Fe4()
|
||||
|
||||
@JvmField val pOddJac = Array(8) { MutablePoint() }
|
||||
|
||||
@@ -134,43 +136,43 @@ internal class PointScratch {
|
||||
|
||||
@JvmField val p2 = MutablePoint()
|
||||
|
||||
@JvmField val cumZ = Array(8) { LongArray(4) }
|
||||
@JvmField val cumZ = Array(8) { Fe4() }
|
||||
|
||||
@JvmField val batchInv = LongArray(4)
|
||||
@JvmField val batchInv = Fe4()
|
||||
|
||||
@JvmField val batchZInv = LongArray(4)
|
||||
@JvmField val batchZInv = Fe4()
|
||||
|
||||
@JvmField val batchZInv2 = LongArray(4)
|
||||
@JvmField val batchZInv2 = Fe4()
|
||||
|
||||
@JvmField val batchZInv3 = LongArray(4)
|
||||
@JvmField val batchZInv3 = Fe4()
|
||||
|
||||
@JvmField val splitWide = LongArray(8)
|
||||
@JvmField val splitWide = Wide8()
|
||||
|
||||
@JvmField val splitT1 = LongArray(4)
|
||||
@JvmField val splitT1 = Fe4()
|
||||
|
||||
@JvmField val splitT2 = LongArray(4)
|
||||
@JvmField val splitT2 = Fe4()
|
||||
|
||||
@JvmField val splitK1 = LongArray(4)
|
||||
@JvmField val splitK1 = Fe4()
|
||||
|
||||
@JvmField val splitK2 = LongArray(4)
|
||||
@JvmField val splitK2 = Fe4()
|
||||
|
||||
@JvmField val zInv = LongArray(4)
|
||||
@JvmField val zInv = Fe4()
|
||||
|
||||
@JvmField val zInv2 = LongArray(4)
|
||||
@JvmField val zInv2 = Fe4()
|
||||
|
||||
@JvmField val zInv3 = LongArray(4)
|
||||
@JvmField val zInv3 = Fe4()
|
||||
|
||||
@JvmField val entryPx = LongArray(4)
|
||||
@JvmField val entryPx = Fe4()
|
||||
|
||||
@JvmField val entryPy = LongArray(4)
|
||||
@JvmField val entryPy = Fe4()
|
||||
|
||||
@JvmField val entryPoint = MutablePoint()
|
||||
|
||||
@JvmField val entryResult = MutablePoint()
|
||||
|
||||
@JvmField val entryTmp = LongArray(4)
|
||||
@JvmField val entryTmp = Fe4()
|
||||
|
||||
@JvmField val entryTmp2 = LongArray(4)
|
||||
@JvmField val entryTmp2 = Fe4()
|
||||
|
||||
// Pre-allocated byte buffers for sign/verify (eliminates ByteArray allocations).
|
||||
// hashBuf: reusable buffer for BIP-340 tagged hash inputs (prefix(64) + fields).
|
||||
@@ -183,10 +185,10 @@ internal class PointScratch {
|
||||
|
||||
@JvmField val bytesTmp2 = ByteArray(32)
|
||||
|
||||
// Scratch LongArray(4) for intermediate scalar results (avoids ScalarN alloc)
|
||||
@JvmField val scalarTmp1 = LongArray(4)
|
||||
// Scratch Fe4 for intermediate scalar results (avoids ScalarN alloc)
|
||||
@JvmField val scalarTmp1 = Fe4()
|
||||
|
||||
@JvmField val scalarTmp2 = LongArray(4)
|
||||
@JvmField val scalarTmp2 = Fe4()
|
||||
|
||||
@JvmField val scalarTmp3 = LongArray(4)
|
||||
@JvmField val scalarTmp3 = Fe4()
|
||||
}
|
||||
|
||||
+211
-136
@@ -21,7 +21,7 @@
|
||||
package com.vitorpamplona.quartz.utils.secp256k1
|
||||
|
||||
/**
|
||||
* Arithmetic modulo the secp256k1 group order n using LongArray(4) limbs.
|
||||
* Arithmetic modulo the secp256k1 group order n using Fe4 limbs.
|
||||
*
|
||||
* Provides both allocating (convenience) and in-place (hot-path) variants.
|
||||
* The in-place variants write results to caller-provided output arrays, avoiding
|
||||
@@ -29,7 +29,7 @@ package com.vitorpamplona.quartz.utils.secp256k1
|
||||
*/
|
||||
internal object ScalarN {
|
||||
val N =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
-4624529908474429119L,
|
||||
-4994812053365940165L,
|
||||
-2L,
|
||||
@@ -37,7 +37,7 @@ internal object ScalarN {
|
||||
)
|
||||
|
||||
private val N_COMPLEMENT =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
4624529908474429119L,
|
||||
4994812053365940164L,
|
||||
1L,
|
||||
@@ -45,18 +45,18 @@ internal object ScalarN {
|
||||
)
|
||||
|
||||
private val N_MINUS_2 =
|
||||
longArrayOf(
|
||||
Fe4(
|
||||
-4624529908474429121L,
|
||||
-4994812053365940165L,
|
||||
-2L,
|
||||
-1L,
|
||||
)
|
||||
|
||||
fun isValid(a: LongArray): Boolean = !U256.isZero(a) && U256.cmp(a, N) < 0
|
||||
fun isValid(a: Fe4): Boolean = !a.isZero() && U256.cmp(a, N) < 0
|
||||
|
||||
fun reduce(a: LongArray): LongArray =
|
||||
fun reduce(a: Fe4): Fe4 =
|
||||
if (U256.cmp(a, N) >= 0) {
|
||||
val r = LongArray(4)
|
||||
val r = Fe4()
|
||||
U256.subTo(r, a, N)
|
||||
r
|
||||
} else {
|
||||
@@ -65,30 +65,30 @@ internal object ScalarN {
|
||||
|
||||
/** Allocation-free reduce: out = a mod n. Safe for out === a. */
|
||||
fun reduceTo(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
) {
|
||||
if (U256.cmp(a, N) >= 0) {
|
||||
U256.subTo(out, a, N)
|
||||
} else if (out !== a) {
|
||||
U256.copyInto(out, a)
|
||||
out.copyFrom(a)
|
||||
}
|
||||
}
|
||||
|
||||
fun add(
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
): LongArray {
|
||||
val r = LongArray(4)
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
): Fe4 {
|
||||
val r = Fe4()
|
||||
addTo(r, a, b)
|
||||
return r
|
||||
}
|
||||
|
||||
/** In-place add: out = (a + b) mod n. */
|
||||
fun addTo(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
) {
|
||||
val carry = U256.addTo(out, a, b)
|
||||
if (carry != 0) U256.addTo(out, out, N_COMPLEMENT)
|
||||
@@ -96,60 +96,60 @@ internal object ScalarN {
|
||||
}
|
||||
|
||||
fun sub(
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
): LongArray {
|
||||
val r = LongArray(4)
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
): Fe4 {
|
||||
val r = Fe4()
|
||||
val borrow = U256.subTo(r, a, b)
|
||||
if (borrow != 0) U256.addTo(r, r, N)
|
||||
return r
|
||||
}
|
||||
|
||||
fun mul(
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
): LongArray {
|
||||
val w = LongArray(8)
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
): Fe4 {
|
||||
val w = Wide8()
|
||||
U256.mulWide(w, a, b)
|
||||
return reduceWide(w)
|
||||
}
|
||||
|
||||
/** In-place multiply: out = (a * b) mod n. Uses caller-provided wide buffer. */
|
||||
fun mulTo(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
U256.mulWide(w, a, b)
|
||||
reduceWideTo(out, w)
|
||||
}
|
||||
|
||||
fun neg(a: LongArray): LongArray {
|
||||
if (U256.isZero(a)) return LongArray(4)
|
||||
val r = LongArray(4)
|
||||
fun neg(a: Fe4): Fe4 {
|
||||
if (a.isZero()) return Fe4()
|
||||
val r = Fe4()
|
||||
U256.subTo(r, N, a)
|
||||
return r
|
||||
}
|
||||
|
||||
/** In-place negate: out = (-a) mod n. Safe for out === a. */
|
||||
fun negTo(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
) {
|
||||
if (U256.isZero(a)) {
|
||||
for (i in 0 until 4) out[i] = 0L
|
||||
if (a.isZero()) {
|
||||
out.setZero()
|
||||
} else {
|
||||
U256.subTo(out, N, a)
|
||||
}
|
||||
}
|
||||
|
||||
fun inv(a: LongArray): LongArray {
|
||||
require(!U256.isZero(a))
|
||||
fun inv(a: Fe4): Fe4 {
|
||||
require(!a.isZero())
|
||||
return powModN(a, N_MINUS_2)
|
||||
}
|
||||
|
||||
private fun reduceSelf(a: LongArray) {
|
||||
private fun reduceSelf(a: Fe4) {
|
||||
if (U256.cmp(a, N) >= 0) U256.subTo(a, a, N)
|
||||
}
|
||||
|
||||
@@ -157,8 +157,8 @@ internal object ScalarN {
|
||||
* Reduce 512-bit product mod n (allocating version).
|
||||
* Uses hi × 2^256 ≡ hi × N_COMPLEMENT (mod n). N_COMPLEMENT is ~129 bits.
|
||||
*/
|
||||
private fun reduceWide(w: LongArray): LongArray {
|
||||
val result = LongArray(4)
|
||||
private fun reduceWide(w: Wide8): Fe4 {
|
||||
val result = Fe4()
|
||||
reduceWideTo(result, w)
|
||||
return result
|
||||
}
|
||||
@@ -168,135 +168,210 @@ internal object ScalarN {
|
||||
* Reuses the wide buffer w as scratch (caller must not need it after this call).
|
||||
*/
|
||||
private fun reduceWideTo(
|
||||
out: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
// Split into lo (w[0..3]) and hi (w[4..7])
|
||||
val hasHi = w[4] != 0L || w[5] != 0L || w[6] != 0L || w[7] != 0L
|
||||
// Split into lo (w.l0..l3) and hi (w.l4..l7)
|
||||
val hasHi = w.l4 != 0L || w.l5 != 0L || w.l6 != 0L || w.l7 != 0L
|
||||
if (!hasHi) {
|
||||
for (i in 0 until 4) out[i] = w[i]
|
||||
out.l0 = w.l0
|
||||
out.l1 = w.l1
|
||||
out.l2 = w.l2
|
||||
out.l3 = w.l3
|
||||
reduceSelf(out)
|
||||
return
|
||||
}
|
||||
|
||||
// Round 1: lo + hi × N_COMPLEMENT
|
||||
// We reuse w[0..7] as scratch for hiTimesNC by saving lo first
|
||||
val lo0 = w[0]
|
||||
val lo1 = w[1]
|
||||
val lo2 = w[2]
|
||||
val lo3 = w[3]
|
||||
// Use `out` as temporary storage for hi limbs (avoids longArrayOf allocation)
|
||||
out[0] = w[4]
|
||||
out[1] = w[5]
|
||||
out[2] = w[6]
|
||||
out[3] = w[7]
|
||||
// We reuse w as scratch for hiTimesNC by saving lo first
|
||||
val lo0 = w.l0
|
||||
val lo1 = w.l1
|
||||
val lo2 = w.l2
|
||||
val lo3 = w.l3
|
||||
// Use `out` as temporary storage for hi limbs (avoids allocation)
|
||||
out.l0 = w.l4
|
||||
out.l1 = w.l5
|
||||
out.l2 = w.l6
|
||||
out.l3 = w.l7
|
||||
|
||||
val hiTimesNC = w // reuse w as scratch
|
||||
U256.mulWide(hiTimesNC, out, N_COMPLEMENT)
|
||||
U256.mulWide(w, out, N_COMPLEMENT)
|
||||
|
||||
// sum = hiTimesNC + lo
|
||||
var carry = 0L
|
||||
for (i in 0 until 8) {
|
||||
val loVal =
|
||||
if (i == 0) {
|
||||
lo0
|
||||
} else if (i == 1) {
|
||||
lo1
|
||||
} else if (i == 2) {
|
||||
lo2
|
||||
} else if (i == 3) {
|
||||
lo3
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
val s1 = hiTimesNC[i] + loVal
|
||||
val c1 = if (uLtInline(s1, hiTimesNC[i])) 1L else 0L
|
||||
val s2 = s1 + carry
|
||||
val c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
w[i] = s2
|
||||
carry = c1 + c2
|
||||
}
|
||||
|
||||
var s1 = w.l0 + lo0
|
||||
var c1 = if (uLtInline(s1, w.l0)) 1L else 0L
|
||||
var s2 = s1 + carry
|
||||
var c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
w.l0 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
s1 = w.l1 + lo1
|
||||
c1 = if (uLtInline(s1, w.l1)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
w.l1 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
s1 = w.l2 + lo2
|
||||
c1 = if (uLtInline(s1, w.l2)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
w.l2 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
s1 = w.l3 + lo3
|
||||
c1 = if (uLtInline(s1, w.l3)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
w.l3 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
s1 = w.l4 + 0L
|
||||
c1 = 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
w.l4 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
s1 = w.l5 + 0L
|
||||
c1 = 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
w.l5 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
s1 = w.l6 + 0L
|
||||
c1 = 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
w.l6 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
s1 = w.l7 + 0L
|
||||
c1 = 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
w.l7 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
// Check if round 2 needed
|
||||
val hasHi2 = w[4] != 0L || w[5] != 0L || w[6] != 0L || w[7] != 0L
|
||||
val hasHi2 = w.l4 != 0L || w.l5 != 0L || w.l6 != 0L || w.l7 != 0L
|
||||
if (!hasHi2) {
|
||||
for (i in 0 until 4) out[i] = w[i]
|
||||
out.l0 = w.l0
|
||||
out.l1 = w.l1
|
||||
out.l2 = w.l2
|
||||
out.l3 = w.l3
|
||||
reduceSelf(out)
|
||||
return
|
||||
}
|
||||
|
||||
// Round 2: reuse out for hi2 limbs (avoids longArrayOf allocation)
|
||||
out[0] = w[4]
|
||||
out[1] = w[5]
|
||||
out[2] = w[6]
|
||||
out[3] = w[7]
|
||||
val saved0 = w[0]
|
||||
val saved1 = w[1]
|
||||
val saved2 = w[2]
|
||||
val saved3 = w[3]
|
||||
val hi2NC = w
|
||||
U256.mulWide(hi2NC, out, N_COMPLEMENT)
|
||||
// Round 2: reuse out for hi2 limbs (avoids allocation)
|
||||
out.l0 = w.l4
|
||||
out.l1 = w.l5
|
||||
out.l2 = w.l6
|
||||
out.l3 = w.l7
|
||||
val saved0 = w.l0
|
||||
val saved1 = w.l1
|
||||
val saved2 = w.l2
|
||||
val saved3 = w.l3
|
||||
U256.mulWide(w, out, N_COMPLEMENT)
|
||||
|
||||
var c2 = 0L
|
||||
for (i in 0 until 4) {
|
||||
val loVal =
|
||||
if (i == 0) {
|
||||
saved0
|
||||
} else if (i == 1) {
|
||||
saved1
|
||||
} else if (i == 2) {
|
||||
saved2
|
||||
} else {
|
||||
saved3
|
||||
}
|
||||
val s1 = loVal + hi2NC[i]
|
||||
val c1 = if (uLtInline(s1, loVal)) 1L else 0L
|
||||
val s2 = s1 + c2
|
||||
val cc = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[i] = s2
|
||||
c2 = c1 + cc
|
||||
}
|
||||
var ov = c2 + hi2NC[4]
|
||||
for (i in 5 until 8) ov += hi2NC[i]
|
||||
var c2r = 0L
|
||||
|
||||
var loVal = saved0
|
||||
s1 = loVal + w.l0
|
||||
c1 = if (uLtInline(s1, loVal)) 1L else 0L
|
||||
s2 = s1 + c2r
|
||||
var cc = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out.l0 = s2
|
||||
c2r = c1 + cc
|
||||
|
||||
loVal = saved1
|
||||
s1 = loVal + w.l1
|
||||
c1 = if (uLtInline(s1, loVal)) 1L else 0L
|
||||
s2 = s1 + c2r
|
||||
cc = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out.l1 = s2
|
||||
c2r = c1 + cc
|
||||
|
||||
loVal = saved2
|
||||
s1 = loVal + w.l2
|
||||
c1 = if (uLtInline(s1, loVal)) 1L else 0L
|
||||
s2 = s1 + c2r
|
||||
cc = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out.l2 = s2
|
||||
c2r = c1 + cc
|
||||
|
||||
loVal = saved3
|
||||
s1 = loVal + w.l3
|
||||
c1 = if (uLtInline(s1, loVal)) 1L else 0L
|
||||
s2 = s1 + c2r
|
||||
cc = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out.l3 = s2
|
||||
c2r = c1 + cc
|
||||
|
||||
var ov = c2r + w.l4
|
||||
ov += w.l5
|
||||
ov += w.l6
|
||||
ov += w.l7
|
||||
if (ov != 0L) {
|
||||
val c0lo = ov * N_COMPLEMENT[0]
|
||||
val c0hi = unsignedMultiplyHigh(ov, N_COMPLEMENT[0])
|
||||
val c1lo = ov * N_COMPLEMENT[1]
|
||||
val c1hi = unsignedMultiplyHigh(ov, N_COMPLEMENT[1])
|
||||
val s0 = out[0] + c0lo
|
||||
val carry0 = if (uLtInline(s0, out[0])) 1L else 0L
|
||||
out[0] = s0
|
||||
val s1 = out[1] + c0hi + c1lo + carry0
|
||||
val carry1 = if (uLtInline(s1, out[1])) 1L else 0L
|
||||
out[1] = s1
|
||||
val s2 = out[2] + c1hi + ov + carry1
|
||||
val carry2 = if (uLtInline(s2, out[2])) 1L else 0L
|
||||
out[2] = s2
|
||||
out[3] += carry2
|
||||
val c0lo = ov * N_COMPLEMENT.l0
|
||||
val c0hi = unsignedMultiplyHigh(ov, N_COMPLEMENT.l0)
|
||||
val c1lo = ov * N_COMPLEMENT.l1
|
||||
val c1hi = unsignedMultiplyHigh(ov, N_COMPLEMENT.l1)
|
||||
val s0 = out.l0 + c0lo
|
||||
val carry0 = if (uLtInline(s0, out.l0)) 1L else 0L
|
||||
out.l0 = s0
|
||||
val s1r = out.l1 + c0hi + c1lo + carry0
|
||||
val carry1 = if (uLtInline(s1r, out.l1)) 1L else 0L
|
||||
out.l1 = s1r
|
||||
val s2r = out.l2 + c1hi + ov + carry1
|
||||
val carry2 = if (uLtInline(s2r, out.l2)) 1L else 0L
|
||||
out.l2 = s2r
|
||||
out.l3 += carry2
|
||||
}
|
||||
|
||||
while (U256.cmp(out, N) >= 0) U256.subTo(out, out, N)
|
||||
}
|
||||
|
||||
/** Test bit at position pos in an Fe4. */
|
||||
private fun testBit(
|
||||
a: Fe4,
|
||||
pos: Int,
|
||||
): Boolean {
|
||||
val limb = pos / 64
|
||||
val shift = pos % 64
|
||||
val v =
|
||||
when (limb) {
|
||||
0 -> a.l0
|
||||
1 -> a.l1
|
||||
2 -> a.l2
|
||||
3 -> a.l3
|
||||
else -> 0L
|
||||
}
|
||||
return (v ushr shift) and 1L == 1L
|
||||
}
|
||||
|
||||
private fun powModN(
|
||||
base: LongArray,
|
||||
exp: LongArray,
|
||||
): LongArray {
|
||||
val result = LongArray(4)
|
||||
base: Fe4,
|
||||
exp: Fe4,
|
||||
): Fe4 {
|
||||
val result = Fe4()
|
||||
val b = base.copyOf()
|
||||
var highBit = 255
|
||||
while (highBit >= 0 && !U256.testBit(exp, highBit)) highBit--
|
||||
while (highBit >= 0 && !testBit(exp, highBit)) highBit--
|
||||
if (highBit < 0) {
|
||||
result[0] = 1L
|
||||
result.l0 = 1L
|
||||
return result
|
||||
}
|
||||
U256.copyInto(result, b)
|
||||
result.copyFrom(b)
|
||||
for (i in highBit - 1 downTo 0) {
|
||||
val sq = mul(result, result)
|
||||
U256.copyInto(result, sq)
|
||||
if (U256.testBit(exp, i)) {
|
||||
result.copyFrom(sq)
|
||||
if (testBit(exp, i)) {
|
||||
val prod = mul(result, b)
|
||||
U256.copyInto(result, prod)
|
||||
result.copyFrom(prod)
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
+14
-14
@@ -84,8 +84,8 @@ object Secp256k1 {
|
||||
|
||||
private class CachedPubkey(
|
||||
val keyBytes: ByteArray, // 32-byte x-only pubkey (for equality check)
|
||||
val px: LongArray, // decompressed x (4 limbs)
|
||||
val py: LongArray, // decompressed y (4 limbs)
|
||||
val px: Fe4, // decompressed x (4 limbs)
|
||||
val py: Fe4, // decompressed y (4 limbs)
|
||||
)
|
||||
|
||||
private val pubkeyCache = arrayOfNulls<CachedPubkey>(PUBKEY_CACHE_SIZE)
|
||||
@@ -96,8 +96,8 @@ object Secp256k1 {
|
||||
* On cache miss, computes sqrt and stores the result.
|
||||
*/
|
||||
private fun liftXCached(
|
||||
outX: LongArray,
|
||||
outY: LongArray,
|
||||
outX: Fe4,
|
||||
outY: Fe4,
|
||||
pub: ByteArray,
|
||||
): Boolean {
|
||||
// Hash the pubkey bytes to a cache slot (use first 4 bytes as index)
|
||||
@@ -110,8 +110,8 @@ object Secp256k1 {
|
||||
val cached = pubkeyCache[slot]
|
||||
if (cached != null && cached.keyBytes.contentEquals(pub)) {
|
||||
// Cache hit — copy pre-computed coordinates
|
||||
cached.px.copyInto(outX, 0, 0, 4)
|
||||
cached.py.copyInto(outY, 0, 0, 4)
|
||||
outX.copyFrom(cached.px)
|
||||
outY.copyFrom(cached.py)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -312,14 +312,14 @@ object Secp256k1 {
|
||||
*/
|
||||
private fun signSchnorrInternal(
|
||||
data: ByteArray,
|
||||
d0: LongArray,
|
||||
d0: Fe4,
|
||||
pBytes: ByteArray,
|
||||
pubKeyHasEvenY: Boolean,
|
||||
auxrand: ByteArray?,
|
||||
): ByteArray {
|
||||
val sc = ECPoint.getScratch()
|
||||
|
||||
val d =
|
||||
val d: Fe4 =
|
||||
if (pubKeyHasEvenY) {
|
||||
d0
|
||||
} else {
|
||||
@@ -358,7 +358,7 @@ object Secp256k1 {
|
||||
U256.fromBytesInto(sc.scalarTmp1, sc.bytesTmp2, 0)
|
||||
ScalarN.reduceTo(sc.scalarTmp1, sc.scalarTmp1)
|
||||
val k0 = sc.scalarTmp1
|
||||
require(!U256.isZero(k0))
|
||||
require(!k0.isZero())
|
||||
|
||||
// R = k0·G
|
||||
ECPoint.mulG(sc.entryResult, k0, sc)
|
||||
@@ -366,7 +366,7 @@ object Secp256k1 {
|
||||
val ry = sc.entryPy
|
||||
check(ECPoint.toAffine(sc.entryResult, rx, ry, sc))
|
||||
|
||||
val k =
|
||||
val k: Fe4 =
|
||||
if (KeyCodec.hasEvenY(ry)) {
|
||||
k0
|
||||
} else {
|
||||
@@ -525,7 +525,7 @@ object Secp256k1 {
|
||||
tweak: ByteArray,
|
||||
): ByteArray {
|
||||
require(seckey.size == 32 && tweak.size == 32)
|
||||
// Use thread-local scratch to avoid 2 intermediate LongArray(4) allocations.
|
||||
// Use thread-local scratch to avoid 2 intermediate Fe4() allocations.
|
||||
// Old path: fromBytes (alloc) + fromBytes (alloc) + add (alloc) + toBytes (alloc) = 4 allocs
|
||||
// New path: fromBytesInto (scratch) + fromBytesInto (scratch) + addTo (scratch) + toBytes = 1 alloc
|
||||
val sc = ECPoint.getScratch()
|
||||
@@ -535,7 +535,7 @@ object Secp256k1 {
|
||||
U256.fromBytesInto(a, seckey, 0)
|
||||
U256.fromBytesInto(b, tweak, 0)
|
||||
ScalarN.addTo(r, a, b)
|
||||
require(!U256.isZero(r) && U256.cmp(r, ScalarN.N) < 0)
|
||||
require(!r.isZero() && U256.cmp(r, ScalarN.N) < 0)
|
||||
return U256.toBytes(r)
|
||||
}
|
||||
|
||||
@@ -657,8 +657,8 @@ object Secp256k1 {
|
||||
if (!liftXCached(px, py, pub)) return false
|
||||
|
||||
// Accumulators for the scalar sums
|
||||
val sSum = LongArray(4) // Σ sᵢ mod n
|
||||
val eSum = LongArray(4) // Σ eᵢ mod n
|
||||
val sSum = Fe4() // Σ sᵢ mod n
|
||||
val eSum = Fe4() // Σ eᵢ mod n
|
||||
|
||||
// Accumulator for R point sum (Jacobian)
|
||||
val rSum = MutablePoint()
|
||||
|
||||
@@ -24,8 +24,8 @@ package com.vitorpamplona.quartz.utils.secp256k1
|
||||
// 256-BIT UNSIGNED INTEGER ARITHMETIC FOR secp256k1 (4×64-bit limbs)
|
||||
// =====================================================================================
|
||||
//
|
||||
// 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.
|
||||
// Numbers are stored as Fe4 (4 named Long fields) in little-endian order. Each Long
|
||||
// holds 64 bits treated as unsigned. Field l0 is the least significant limb.
|
||||
//
|
||||
// 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
|
||||
@@ -97,26 +97,25 @@ internal inline fun uLtInline(
|
||||
* Raw 256-bit unsigned integer arithmetic using 4×64-bit limbs.
|
||||
*/
|
||||
internal object U256 {
|
||||
fun isZero(a: LongArray): Boolean = (a[0] or a[1] or a[2] or a[3]) == 0L
|
||||
fun isZero(a: Fe4): Boolean = a.isZero()
|
||||
|
||||
/** Unsigned comparison. Returns -1 if a < b, 0 if equal, 1 if a > b. */
|
||||
fun cmp(
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
): Int {
|
||||
for (i in 3 downTo 0) {
|
||||
if (a[i] != b[i]) {
|
||||
return if (uLtInline(a[i], b[i])) -1 else 1
|
||||
}
|
||||
}
|
||||
if (a.l3 != b.l3) return if (uLtInline(a.l3, b.l3)) -1 else 1
|
||||
if (a.l2 != b.l2) return if (uLtInline(a.l2, b.l2)) -1 else 1
|
||||
if (a.l1 != b.l1) return if (uLtInline(a.l1, b.l1)) -1 else 1
|
||||
if (a.l0 != b.l0) return if (uLtInline(a.l0, b.l0)) -1 else 1
|
||||
return 0
|
||||
}
|
||||
|
||||
/** out = a + b. Returns carry (0 or 1). Safe for aliasing. Unrolled for ART JIT. */
|
||||
fun addTo(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
): Int {
|
||||
var s1: Long
|
||||
var s2: Long
|
||||
@@ -124,33 +123,33 @@ internal object U256 {
|
||||
var c2: Long
|
||||
|
||||
// Limb 0 (no carry input)
|
||||
s1 = a[0] + b[0]
|
||||
c1 = if (uLtInline(s1, a[0])) 1L else 0L
|
||||
out[0] = s1
|
||||
s1 = a.l0 + b.l0
|
||||
c1 = if (uLtInline(s1, a.l0)) 1L else 0L
|
||||
out.l0 = s1
|
||||
var carry = c1
|
||||
|
||||
// Limb 1
|
||||
s1 = a[1] + b[1]
|
||||
c1 = if (uLtInline(s1, a[1])) 1L else 0L
|
||||
s1 = a.l1 + b.l1
|
||||
c1 = if (uLtInline(s1, a.l1)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[1] = s2
|
||||
out.l1 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
// Limb 2
|
||||
s1 = a[2] + b[2]
|
||||
c1 = if (uLtInline(s1, a[2])) 1L else 0L
|
||||
s1 = a.l2 + b.l2
|
||||
c1 = if (uLtInline(s1, a.l2)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[2] = s2
|
||||
out.l2 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
// Limb 3
|
||||
s1 = a[3] + b[3]
|
||||
c1 = if (uLtInline(s1, a[3])) 1L else 0L
|
||||
s1 = a.l3 + b.l3
|
||||
c1 = if (uLtInline(s1, a.l3)) 1L else 0L
|
||||
s2 = s1 + carry
|
||||
c2 = if (uLtInline(s2, s1)) 1L else 0L
|
||||
out[3] = s2
|
||||
out.l3 = s2
|
||||
carry = c1 + c2
|
||||
|
||||
return carry.toInt()
|
||||
@@ -158,9 +157,9 @@ internal object U256 {
|
||||
|
||||
/** out = a - b. Returns borrow (0 or 1). Safe for aliasing. Unrolled for ART JIT. */
|
||||
fun subTo(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
): Int {
|
||||
var d1: Long
|
||||
var d2: Long
|
||||
@@ -168,58 +167,58 @@ internal object U256 {
|
||||
var c2: Long
|
||||
|
||||
// Limb 0 (no borrow input)
|
||||
d1 = a[0] - b[0]
|
||||
c1 = if (uLtInline(a[0], b[0])) 1L else 0L
|
||||
out[0] = d1
|
||||
d1 = a.l0 - b.l0
|
||||
c1 = if (uLtInline(a.l0, b.l0)) 1L else 0L
|
||||
out.l0 = d1
|
||||
var borrow = c1
|
||||
|
||||
// Limb 1
|
||||
d1 = a[1] - b[1]
|
||||
c1 = if (uLtInline(a[1], b[1])) 1L else 0L
|
||||
d1 = a.l1 - b.l1
|
||||
c1 = if (uLtInline(a.l1, b.l1)) 1L else 0L
|
||||
d2 = d1 - borrow
|
||||
c2 = if (uLtInline(d1, borrow)) 1L else 0L
|
||||
out[1] = d2
|
||||
out.l1 = d2
|
||||
borrow = c1 + c2
|
||||
|
||||
// Limb 2
|
||||
d1 = a[2] - b[2]
|
||||
c1 = if (uLtInline(a[2], b[2])) 1L else 0L
|
||||
d1 = a.l2 - b.l2
|
||||
c1 = if (uLtInline(a.l2, b.l2)) 1L else 0L
|
||||
d2 = d1 - borrow
|
||||
c2 = if (uLtInline(d1, borrow)) 1L else 0L
|
||||
out[2] = d2
|
||||
out.l2 = d2
|
||||
borrow = c1 + c2
|
||||
|
||||
// Limb 3
|
||||
d1 = a[3] - b[3]
|
||||
c1 = if (uLtInline(a[3], b[3])) 1L else 0L
|
||||
d1 = a.l3 - b.l3
|
||||
c1 = if (uLtInline(a.l3, b.l3)) 1L else 0L
|
||||
d2 = d1 - borrow
|
||||
c2 = if (uLtInline(d1, borrow)) 1L else 0L
|
||||
out[3] = d2
|
||||
out.l3 = d2
|
||||
borrow = c1 + c2
|
||||
|
||||
return borrow.toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* 4×4 schoolbook multiplication: out = a × b (512-bit result in LongArray(8)).
|
||||
* 4×4 schoolbook multiplication: out = a × b (512-bit result in Wide8).
|
||||
*
|
||||
* Fully unrolled: all 16 products are explicit, eliminating loop control overhead
|
||||
* and array bounds checks. This significantly helps ART JIT on Android, which is
|
||||
* less aggressive at loop optimization than HotSpot. Called ~1,900× per verify.
|
||||
*/
|
||||
fun mulWide(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
out: Wide8,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
) {
|
||||
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]
|
||||
val a0 = a.l0
|
||||
val a1 = a.l1
|
||||
val a2 = a.l2
|
||||
val a3 = a.l3
|
||||
val b0 = b.l0
|
||||
val b1 = b.l1
|
||||
val b2 = b.l2
|
||||
val b3 = b.l3
|
||||
var lo: Long
|
||||
var hi: Long
|
||||
var prev: Long
|
||||
@@ -230,158 +229,158 @@ internal object U256 {
|
||||
|
||||
// Row 0: a0 × [b0,b1,b2,b3] → out[0..4] (out starts empty, no prev accumulation)
|
||||
lo = a0 * b0
|
||||
out[0] = lo
|
||||
out.l0 = lo
|
||||
carry = unsignedMultiplyHigh(a0, b0)
|
||||
|
||||
lo = a0 * b1
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
out[1] = s
|
||||
out.l1 = s
|
||||
carry = unsignedMultiplyHigh(a0, b1) + c1
|
||||
|
||||
lo = a0 * b2
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
out[2] = s
|
||||
out.l2 = s
|
||||
carry = unsignedMultiplyHigh(a0, b2) + c1
|
||||
|
||||
lo = a0 * b3
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
out[3] = s
|
||||
out[4] = unsignedMultiplyHigh(a0, b3) + c1
|
||||
out.l3 = s
|
||||
out.l4 = unsignedMultiplyHigh(a0, b3) + c1
|
||||
|
||||
// Row 1: a1 × [b0,b1,b2,b3] accumulated into out[1..5]
|
||||
lo = a1 * b0
|
||||
hi = unsignedMultiplyHigh(a1, b0)
|
||||
prev = out[1]
|
||||
prev = out.l1
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
out[1] = s
|
||||
out.l1 = s
|
||||
carry = hi + c1
|
||||
|
||||
lo = a1 * b1
|
||||
hi = unsignedMultiplyHigh(a1, b1)
|
||||
prev = out[2]
|
||||
prev = out.l2
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
out[2] = s
|
||||
out.l2 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a1 * b2
|
||||
hi = unsignedMultiplyHigh(a1, b2)
|
||||
prev = out[3]
|
||||
prev = out.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
out[3] = s
|
||||
out.l3 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a1 * b3
|
||||
hi = unsignedMultiplyHigh(a1, b3)
|
||||
prev = out[4]
|
||||
prev = out.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
out[4] = s
|
||||
out[5] = hi + c1 + c2
|
||||
out.l4 = s
|
||||
out.l5 = hi + c1 + c2
|
||||
|
||||
// Row 2: a2 × [b0,b1,b2,b3] accumulated into out[2..6]
|
||||
lo = a2 * b0
|
||||
hi = unsignedMultiplyHigh(a2, b0)
|
||||
prev = out[2]
|
||||
prev = out.l2
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
out[2] = s
|
||||
out.l2 = s
|
||||
carry = hi + c1
|
||||
|
||||
lo = a2 * b1
|
||||
hi = unsignedMultiplyHigh(a2, b1)
|
||||
prev = out[3]
|
||||
prev = out.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
out[3] = s
|
||||
out.l3 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a2 * b2
|
||||
hi = unsignedMultiplyHigh(a2, b2)
|
||||
prev = out[4]
|
||||
prev = out.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
out[4] = s
|
||||
out.l4 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a2 * b3
|
||||
hi = unsignedMultiplyHigh(a2, b3)
|
||||
prev = out[5]
|
||||
prev = out.l5
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
out[5] = s
|
||||
out[6] = hi + c1 + c2
|
||||
out.l5 = s
|
||||
out.l6 = hi + c1 + c2
|
||||
|
||||
// Row 3: a3 × [b0,b1,b2,b3] accumulated into out[3..7]
|
||||
lo = a3 * b0
|
||||
hi = unsignedMultiplyHigh(a3, b0)
|
||||
prev = out[3]
|
||||
prev = out.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
out[3] = s
|
||||
out.l3 = s
|
||||
carry = hi + c1
|
||||
|
||||
lo = a3 * b1
|
||||
hi = unsignedMultiplyHigh(a3, b1)
|
||||
prev = out[4]
|
||||
prev = out.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
out[4] = s
|
||||
out.l4 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a3 * b2
|
||||
hi = unsignedMultiplyHigh(a3, b2)
|
||||
prev = out[5]
|
||||
prev = out.l5
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
out[5] = s
|
||||
out.l5 = s
|
||||
carry = hi + c1 + c2
|
||||
|
||||
lo = a3 * b3
|
||||
hi = unsignedMultiplyHigh(a3, b3)
|
||||
prev = out[6]
|
||||
prev = out.l6
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
out[6] = s
|
||||
out[7] = hi + c1 + c2
|
||||
out.l6 = s
|
||||
out.l7 = hi + c1 + c2
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedicated squaring: out = a² (512-bit result in LongArray(8)).
|
||||
* Dedicated squaring: out = a² (512-bit result in Wide8).
|
||||
* Exploits symmetry: 6 cross-products doubled + 4 diagonal = 10 multiplyHigh calls.
|
||||
* Fully unrolled for ART JIT optimization.
|
||||
*/
|
||||
fun sqrWide(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
out: Wide8,
|
||||
a: Fe4,
|
||||
) {
|
||||
val a0 = a[0]
|
||||
val a1 = a[1]
|
||||
val a2 = a[2]
|
||||
val a3 = a[3]
|
||||
val a0 = a.l0
|
||||
val a1 = a.l1
|
||||
val a2 = a.l2
|
||||
val a3 = a.l3
|
||||
var lo: Long
|
||||
var hi: Long
|
||||
var prev: Long
|
||||
@@ -394,214 +393,299 @@ internal object U256 {
|
||||
// Pass 1: cross-products a[i]*a[j] for i < j (single, before doubling)
|
||||
|
||||
// Row i=0: a0 × [a1, a2, a3] → out[1..4]
|
||||
out[0] = 0L
|
||||
out.l0 = 0L
|
||||
lo = a0 * a1
|
||||
out[1] = lo
|
||||
out.l1 = lo
|
||||
carry = unsignedMultiplyHigh(a0, a1)
|
||||
|
||||
lo = a0 * a2
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
out[2] = s
|
||||
out.l2 = s
|
||||
carry = unsignedMultiplyHigh(a0, a2) + c1
|
||||
|
||||
lo = a0 * a3
|
||||
s = lo + carry
|
||||
c1 = if (uLtInline(s, lo)) 1L else 0L
|
||||
out[3] = s
|
||||
out[4] = unsignedMultiplyHigh(a0, a3) + c1
|
||||
out.l3 = s
|
||||
out.l4 = unsignedMultiplyHigh(a0, a3) + c1
|
||||
|
||||
// Row i=1: a1 × [a2, a3] → accumulated into out[3..5]
|
||||
lo = a1 * a2
|
||||
hi = unsignedMultiplyHigh(a1, a2)
|
||||
prev = out[3]
|
||||
prev = out.l3
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
out[3] = s
|
||||
out.l3 = s
|
||||
carry = hi + c1
|
||||
|
||||
lo = a1 * a3
|
||||
hi = unsignedMultiplyHigh(a1, a3)
|
||||
prev = out[4]
|
||||
prev = out.l4
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
s += carry
|
||||
c2 = if (uLtInline(s, carry)) 1L else 0L
|
||||
out[4] = s
|
||||
out[5] = hi + c1 + c2
|
||||
out.l4 = s
|
||||
out.l5 = hi + c1 + c2
|
||||
|
||||
// Row i=2: a2 × [a3] → accumulated into out[5..6]
|
||||
lo = a2 * a3
|
||||
hi = unsignedMultiplyHigh(a2, a3)
|
||||
prev = out[5]
|
||||
prev = out.l5
|
||||
s = prev + lo
|
||||
c1 = if (uLtInline(s, prev)) 1L else 0L
|
||||
out[5] = s
|
||||
out[6] = hi + c1
|
||||
out.l5 = s
|
||||
out.l6 = hi + c1
|
||||
|
||||
// Pass 2: double all cross-products (shift left by 1 bit)
|
||||
v = out[1]
|
||||
out[1] = v shl 1
|
||||
v = out.l1
|
||||
out.l1 = v shl 1
|
||||
var shiftCarry = v ushr 63
|
||||
v = out[2]
|
||||
out[2] = (v shl 1) or shiftCarry
|
||||
v = out.l2
|
||||
out.l2 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = out[3]
|
||||
out[3] = (v shl 1) or shiftCarry
|
||||
v = out.l3
|
||||
out.l3 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = out[4]
|
||||
out[4] = (v shl 1) or shiftCarry
|
||||
v = out.l4
|
||||
out.l4 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = out[5]
|
||||
out[5] = (v shl 1) or shiftCarry
|
||||
v = out.l5
|
||||
out.l5 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
v = out[6]
|
||||
out[6] = (v shl 1) or shiftCarry
|
||||
v = out.l6
|
||||
out.l6 = (v shl 1) or shiftCarry
|
||||
shiftCarry = v ushr 63
|
||||
out[7] = shiftCarry
|
||||
out.l7 = shiftCarry
|
||||
|
||||
// Pass 3: add diagonal products a[i]²
|
||||
|
||||
// i=0: a0², pos=0
|
||||
lo = a0 * a0
|
||||
hi = unsignedMultiplyHigh(a0, a0)
|
||||
out[0] = lo // out[0] was 0
|
||||
s = out[1] + hi
|
||||
c1 = if (uLtInline(s, out[1])) 1L else 0L
|
||||
out[1] = s
|
||||
out.l0 = lo // out.l0 was 0
|
||||
s = out.l1 + hi
|
||||
c1 = if (uLtInline(s, out.l1)) 1L else 0L
|
||||
out.l1 = s
|
||||
var dCarry = c1
|
||||
|
||||
// i=1: a1², pos=2
|
||||
lo = a1 * a1
|
||||
hi = unsignedMultiplyHigh(a1, a1)
|
||||
s = out[2] + lo
|
||||
c1 = if (uLtInline(s, out[2])) 1L else 0L
|
||||
s = out.l2 + lo
|
||||
c1 = if (uLtInline(s, out.l2)) 1L else 0L
|
||||
s += dCarry
|
||||
c2 = if (uLtInline(s, dCarry)) 1L else 0L
|
||||
out[2] = s
|
||||
prev = out[3] + hi
|
||||
val c3a = if (uLtInline(prev, out[3])) 1L else 0L
|
||||
out.l2 = s
|
||||
prev = out.l3 + hi
|
||||
val c3a = if (uLtInline(prev, out.l3)) 1L else 0L
|
||||
prev += c1 + c2
|
||||
val c4a = if (uLtInline(prev, c1 + c2)) 1L else 0L
|
||||
out[3] = prev
|
||||
out.l3 = prev
|
||||
dCarry = c3a + c4a
|
||||
|
||||
// i=2: a2², pos=4
|
||||
lo = a2 * a2
|
||||
hi = unsignedMultiplyHigh(a2, a2)
|
||||
s = out[4] + lo
|
||||
c1 = if (uLtInline(s, out[4])) 1L else 0L
|
||||
s = out.l4 + lo
|
||||
c1 = if (uLtInline(s, out.l4)) 1L else 0L
|
||||
s += dCarry
|
||||
c2 = if (uLtInline(s, dCarry)) 1L else 0L
|
||||
out[4] = s
|
||||
prev = out[5] + hi
|
||||
val c3b = if (uLtInline(prev, out[5])) 1L else 0L
|
||||
out.l4 = s
|
||||
prev = out.l5 + hi
|
||||
val c3b = if (uLtInline(prev, out.l5)) 1L else 0L
|
||||
prev += c1 + c2
|
||||
val c4b = if (uLtInline(prev, c1 + c2)) 1L else 0L
|
||||
out[5] = prev
|
||||
out.l5 = prev
|
||||
dCarry = c3b + c4b
|
||||
|
||||
// i=3: a3², pos=6
|
||||
lo = a3 * a3
|
||||
hi = unsignedMultiplyHigh(a3, a3)
|
||||
s = out[6] + lo
|
||||
c1 = if (uLtInline(s, out[6])) 1L else 0L
|
||||
s = out.l6 + lo
|
||||
c1 = if (uLtInline(s, out.l6)) 1L else 0L
|
||||
s += dCarry
|
||||
c2 = if (uLtInline(s, dCarry)) 1L else 0L
|
||||
out[6] = s
|
||||
prev = out[7] + hi
|
||||
val c3c = if (uLtInline(prev, out[7])) 1L else 0L
|
||||
out.l6 = s
|
||||
prev = out.l7 + hi
|
||||
val c3c = if (uLtInline(prev, out.l7)) 1L else 0L
|
||||
prev += c1 + c2
|
||||
out[7] = prev
|
||||
out.l7 = prev
|
||||
}
|
||||
|
||||
// ==================== Serialization ====================
|
||||
|
||||
/** Decode big-endian 32 bytes into LongArray(4) little-endian limbs. */
|
||||
fun fromBytes(bytes: ByteArray): LongArray = fromBytes(bytes, 0)
|
||||
/** Decode big-endian 32 bytes into Fe4 little-endian limbs. */
|
||||
fun fromBytes(bytes: ByteArray): Fe4 = fromBytes(bytes, 0)
|
||||
|
||||
fun fromBytes(
|
||||
bytes: ByteArray,
|
||||
offset: Int,
|
||||
): LongArray {
|
||||
val r = LongArray(4)
|
||||
): Fe4 {
|
||||
val r = Fe4()
|
||||
fromBytesInto(r, bytes, offset)
|
||||
return r
|
||||
}
|
||||
|
||||
/** Decode big-endian 32 bytes into a pre-allocated LongArray(4). */
|
||||
/** Decode big-endian 32 bytes into a pre-allocated Fe4. */
|
||||
fun fromBytesInto(
|
||||
out: LongArray,
|
||||
out: Fe4,
|
||||
bytes: ByteArray,
|
||||
offset: Int,
|
||||
) {
|
||||
for (i in 0 until 4) {
|
||||
val o = offset + 24 - i * 8
|
||||
out[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)
|
||||
}
|
||||
var o: Int
|
||||
|
||||
o = offset + 24
|
||||
out.l0 = ((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)
|
||||
|
||||
o = offset + 16
|
||||
out.l1 = ((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)
|
||||
|
||||
o = offset + 8
|
||||
out.l2 = ((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)
|
||||
|
||||
o = offset
|
||||
out.l3 = ((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)
|
||||
}
|
||||
|
||||
fun toBytes(a: LongArray): ByteArray {
|
||||
fun toBytes(a: Fe4): ByteArray {
|
||||
val r = ByteArray(32)
|
||||
toBytesInto(a, r, 0)
|
||||
return r
|
||||
}
|
||||
|
||||
fun toBytesInto(
|
||||
a: LongArray,
|
||||
a: Fe4,
|
||||
dest: ByteArray,
|
||||
offset: Int,
|
||||
) {
|
||||
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()
|
||||
}
|
||||
var o: Int
|
||||
|
||||
// Limb 0 (least significant) → bytes at offset+24..offset+31
|
||||
o = offset + 24
|
||||
dest[o] = (a.l0 ushr 56).toByte()
|
||||
dest[o + 1] = (a.l0 ushr 48).toByte()
|
||||
dest[o + 2] = (a.l0 ushr 40).toByte()
|
||||
dest[o + 3] = (a.l0 ushr 32).toByte()
|
||||
dest[o + 4] = (a.l0 ushr 24).toByte()
|
||||
dest[o + 5] = (a.l0 ushr 16).toByte()
|
||||
dest[o + 6] = (a.l0 ushr 8).toByte()
|
||||
dest[o + 7] = a.l0.toByte()
|
||||
|
||||
// Limb 1 → bytes at offset+16..offset+23
|
||||
o = offset + 16
|
||||
dest[o] = (a.l1 ushr 56).toByte()
|
||||
dest[o + 1] = (a.l1 ushr 48).toByte()
|
||||
dest[o + 2] = (a.l1 ushr 40).toByte()
|
||||
dest[o + 3] = (a.l1 ushr 32).toByte()
|
||||
dest[o + 4] = (a.l1 ushr 24).toByte()
|
||||
dest[o + 5] = (a.l1 ushr 16).toByte()
|
||||
dest[o + 6] = (a.l1 ushr 8).toByte()
|
||||
dest[o + 7] = a.l1.toByte()
|
||||
|
||||
// Limb 2 → bytes at offset+8..offset+15
|
||||
o = offset + 8
|
||||
dest[o] = (a.l2 ushr 56).toByte()
|
||||
dest[o + 1] = (a.l2 ushr 48).toByte()
|
||||
dest[o + 2] = (a.l2 ushr 40).toByte()
|
||||
dest[o + 3] = (a.l2 ushr 32).toByte()
|
||||
dest[o + 4] = (a.l2 ushr 24).toByte()
|
||||
dest[o + 5] = (a.l2 ushr 16).toByte()
|
||||
dest[o + 6] = (a.l2 ushr 8).toByte()
|
||||
dest[o + 7] = a.l2.toByte()
|
||||
|
||||
// Limb 3 (most significant) → bytes at offset+0..offset+7
|
||||
o = offset
|
||||
dest[o] = (a.l3 ushr 56).toByte()
|
||||
dest[o + 1] = (a.l3 ushr 48).toByte()
|
||||
dest[o + 2] = (a.l3 ushr 40).toByte()
|
||||
dest[o + 3] = (a.l3 ushr 32).toByte()
|
||||
dest[o + 4] = (a.l3 ushr 24).toByte()
|
||||
dest[o + 5] = (a.l3 ushr 16).toByte()
|
||||
dest[o + 6] = (a.l3 ushr 8).toByte()
|
||||
dest[o + 7] = a.l3.toByte()
|
||||
}
|
||||
|
||||
// ==================== Bit manipulation ====================
|
||||
|
||||
/** Extract 4-bit nibble at position pos (0 = lowest nibble). */
|
||||
fun getNibble(
|
||||
a: LongArray,
|
||||
a: Fe4,
|
||||
pos: Int,
|
||||
): Int {
|
||||
val limb = pos / 16
|
||||
val shift = (pos % 16) * 4
|
||||
return ((a[limb] ushr shift) and 0xF).toInt()
|
||||
val v =
|
||||
when (limb) {
|
||||
0 -> a.l0
|
||||
1 -> a.l1
|
||||
2 -> a.l2
|
||||
else -> a.l3
|
||||
}
|
||||
return ((v ushr shift) and 0xF).toInt()
|
||||
}
|
||||
|
||||
/** Test if bit at position pos is set. Called ~2,800× per mulG (comb table lookup). */
|
||||
fun testBit(
|
||||
a: LongArray,
|
||||
a: Fe4,
|
||||
pos: Int,
|
||||
): Boolean = (a[pos / 64] ushr (pos % 64)) and 1L == 1L
|
||||
): Boolean {
|
||||
val limb = pos / 64
|
||||
val shift = pos % 64
|
||||
val v =
|
||||
when (limb) {
|
||||
0 -> a.l0
|
||||
1 -> a.l1
|
||||
2 -> a.l2
|
||||
else -> a.l3
|
||||
}
|
||||
return (v ushr shift) and 1L == 1L
|
||||
}
|
||||
|
||||
fun xorTo(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
) {
|
||||
for (i in 0 until 4) out[i] = a[i] xor b[i]
|
||||
out.l0 = a.l0 xor b.l0
|
||||
out.l1 = a.l1 xor b.l1
|
||||
out.l2 = a.l2 xor b.l2
|
||||
out.l3 = a.l3 xor b.l3
|
||||
}
|
||||
|
||||
fun copyInto(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
) {
|
||||
a.copyInto(out, 0, 0, 4)
|
||||
out.copyFrom(a)
|
||||
}
|
||||
}
|
||||
|
||||
+38
-34
@@ -31,14 +31,14 @@ import kotlin.test.assertTrue
|
||||
class FieldPTest {
|
||||
private fun hex(s: String) = U256.fromBytes(s.hexToByteArray())
|
||||
|
||||
private fun toHex(a: LongArray) = U256.toBytes(a).toHexKey()
|
||||
private fun toHex(a: Fe4) = U256.toBytes(a).toHexKey()
|
||||
|
||||
// ==================== Basic identities ====================
|
||||
|
||||
@Test
|
||||
fun addZeroIdentity() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val zero = LongArray(4)
|
||||
val zero = Fe4()
|
||||
assertEquals(toHex(a), toHex(FieldP.add(a, zero)))
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class FieldPTest {
|
||||
fun subSelfIsZero() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val result = FieldP.sub(a, a)
|
||||
assertTrue(U256.isZero(result))
|
||||
assertTrue(result.isZero())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -61,7 +61,7 @@ class FieldPTest {
|
||||
@Test
|
||||
fun mulOneIdentity() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val one = longArrayOf(1L, 0L, 0L, 0L)
|
||||
val one = Fe4(1L, 0L, 0L, 0L)
|
||||
assertEquals(toHex(a), toHex(FieldP.mul(a, one)))
|
||||
}
|
||||
|
||||
@@ -71,9 +71,9 @@ class FieldPTest {
|
||||
fun addNearP() {
|
||||
// (p - 1) + 1 = p ≡ 0 (mod p)
|
||||
val pMinus1 = hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e")
|
||||
val one = longArrayOf(1L, 0L, 0L, 0L)
|
||||
val one = Fe4(1L, 0L, 0L, 0L)
|
||||
val result = FieldP.add(pMinus1, one)
|
||||
assertTrue(U256.isZero(result))
|
||||
assertTrue(result.isZero())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -88,8 +88,8 @@ class FieldPTest {
|
||||
@Test
|
||||
fun subUnderflow() {
|
||||
// 0 - 1 ≡ p - 1 (mod p)
|
||||
val zero = LongArray(4)
|
||||
val one = longArrayOf(1L, 0L, 0L, 0L)
|
||||
val zero = Fe4()
|
||||
val one = Fe4(1L, 0L, 0L, 0L)
|
||||
val result = FieldP.sub(zero, one)
|
||||
val expected = hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e") // p-1
|
||||
assertEquals(toHex(expected), toHex(result))
|
||||
@@ -105,14 +105,14 @@ class FieldPTest {
|
||||
|
||||
@Test
|
||||
fun negZeroIsZero() {
|
||||
assertTrue(U256.isZero(FieldP.neg(LongArray(4))))
|
||||
assertTrue(U256.isZero(FieldP.neg(Fe4())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addNegIsZero() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val result = FieldP.add(a, FieldP.neg(a))
|
||||
assertTrue(U256.isZero(result))
|
||||
assertTrue(result.isZero())
|
||||
}
|
||||
|
||||
// ==================== Multiplication ====================
|
||||
@@ -148,13 +148,13 @@ class FieldPTest {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val aInv = FieldP.inv(a)
|
||||
val product = FieldP.mul(a, aInv)
|
||||
val one = longArrayOf(1L, 0L, 0L, 0L)
|
||||
val one = Fe4(1L, 0L, 0L, 0L)
|
||||
assertEquals(toHex(one), toHex(product))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invOfOne() {
|
||||
val one = longArrayOf(1L, 0L, 0L, 0L)
|
||||
val one = Fe4(1L, 0L, 0L, 0L)
|
||||
assertEquals(toHex(one), toHex(FieldP.inv(one)))
|
||||
}
|
||||
|
||||
@@ -169,29 +169,33 @@ class FieldPTest {
|
||||
|
||||
@Test
|
||||
fun halfOfEven() {
|
||||
val out = LongArray(4)
|
||||
val four = longArrayOf(4L, 0L, 0L, 0L)
|
||||
val out = Fe4()
|
||||
val four = Fe4(4L, 0L, 0L, 0L)
|
||||
FieldP.half(out, four)
|
||||
assertEquals(2L, out[0])
|
||||
for (i in 1 until 4) assertEquals(0L, out[i])
|
||||
assertEquals(2L, out.l0)
|
||||
assertEquals(0L, out.l1)
|
||||
assertEquals(0L, out.l2)
|
||||
assertEquals(0L, out.l3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun halfOfOdd() {
|
||||
// half(1) = (1 + p) / 2 = (p + 1) / 2
|
||||
val out = LongArray(4)
|
||||
val one = longArrayOf(1L, 0L, 0L, 0L)
|
||||
val out = Fe4()
|
||||
val one = Fe4(1L, 0L, 0L, 0L)
|
||||
FieldP.half(out, one)
|
||||
// Verify: 2 * half(1) = 1 mod p
|
||||
val doubled = FieldP.add(out, out)
|
||||
assertEquals(1L, doubled[0])
|
||||
for (i in 1 until 4) assertEquals(0L, doubled[i])
|
||||
assertEquals(1L, doubled.l0)
|
||||
assertEquals(0L, doubled.l1)
|
||||
assertEquals(0L, doubled.l2)
|
||||
assertEquals(0L, doubled.l3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun halfThenDoubleRoundTrips() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val out = LongArray(4)
|
||||
val out = Fe4()
|
||||
FieldP.half(out, a)
|
||||
val doubled = FieldP.add(out, out)
|
||||
assertEquals(toHex(a), toHex(doubled))
|
||||
@@ -212,7 +216,7 @@ class FieldPTest {
|
||||
@Test
|
||||
fun sqrtOfNonResidue() {
|
||||
// 3 is not a quadratic residue mod p (for secp256k1's p)
|
||||
val three = longArrayOf(3, 0L, 0L, 0L)
|
||||
val three = Fe4(3, 0L, 0L, 0L)
|
||||
assertNull(FieldP.sqrt(three))
|
||||
}
|
||||
|
||||
@@ -222,7 +226,7 @@ class FieldPTest {
|
||||
val gx = ECPoint.GX
|
||||
val gy = ECPoint.GY
|
||||
val x3 = FieldP.mul(FieldP.sqr(gx), gx)
|
||||
val y2 = FieldP.add(x3, longArrayOf(7, 0L, 0L, 0L))
|
||||
val y2 = FieldP.add(x3, Fe4(7, 0L, 0L, 0L))
|
||||
val root = FieldP.sqrt(y2)!!
|
||||
// root should be gy or -gy
|
||||
val isGy = U256.cmp(root, gy) == 0
|
||||
@@ -239,7 +243,7 @@ class FieldPTest {
|
||||
val result = FieldP.mul(pMinus1, pMinus1)
|
||||
assertTrue(U256.cmp(result, FieldP.P) < 0, "Result should be < p")
|
||||
// (p-1)² ≡ 1 (mod p)
|
||||
val one = longArrayOf(1L, 0L, 0L, 0L)
|
||||
val one = Fe4(1L, 0L, 0L, 0L)
|
||||
assertEquals(toHex(one), toHex(result))
|
||||
}
|
||||
|
||||
@@ -249,24 +253,24 @@ class FieldPTest {
|
||||
fun inPlaceAdd() {
|
||||
val a = hex("0000000000000000000000000000000000000000000000000000000000000005")
|
||||
val b = hex("0000000000000000000000000000000000000000000000000000000000000003")
|
||||
val out = LongArray(4)
|
||||
val out = Fe4()
|
||||
FieldP.add(out, a, b)
|
||||
assertEquals(8L, out[0])
|
||||
assertEquals(8L, out.l0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun inPlaceSqr() {
|
||||
val a = hex("0000000000000000000000000000000000000000000000000000000000000005")
|
||||
val out = LongArray(4)
|
||||
val out = Fe4()
|
||||
FieldP.sqr(out, a)
|
||||
assertEquals(25L, out[0]) // 5² = 25
|
||||
assertEquals(25L, out.l0) // 5² = 25
|
||||
}
|
||||
|
||||
@Test
|
||||
fun halfOfPMinus1() {
|
||||
// half(p-1) should equal (p-1)/2
|
||||
val pMinus1 = hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e")
|
||||
val out = LongArray(4)
|
||||
val out = Fe4()
|
||||
FieldP.half(out, pMinus1)
|
||||
// Verify: 2 * half(p-1) = p-1
|
||||
val doubled = FieldP.add(out, out)
|
||||
@@ -275,23 +279,23 @@ class FieldPTest {
|
||||
|
||||
@Test
|
||||
fun invOfTwo() {
|
||||
val two = longArrayOf(2, 0L, 0L, 0L)
|
||||
val two = Fe4(2, 0L, 0L, 0L)
|
||||
val inv2 = FieldP.inv(two)
|
||||
val product = FieldP.mul(two, inv2)
|
||||
val one = longArrayOf(1L, 0L, 0L, 0L)
|
||||
val one = Fe4(1L, 0L, 0L, 0L)
|
||||
assertEquals(toHex(one), toHex(product))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sqrtOfZero() {
|
||||
val zero = LongArray(4)
|
||||
val zero = Fe4()
|
||||
val root = FieldP.sqrt(zero)
|
||||
assertTrue(root != null && U256.isZero(root))
|
||||
assertTrue(root != null && root.isZero())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sqrtOfOne() {
|
||||
val one = longArrayOf(1L, 0L, 0L, 0L)
|
||||
val one = Fe4(1L, 0L, 0L, 0L)
|
||||
val root = FieldP.sqrt(one)!!
|
||||
assertEquals(toHex(one), toHex(root))
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import kotlin.test.assertTrue
|
||||
|
||||
/** Comprehensive tests for GLV endomorphism and wNAF encoding. */
|
||||
class GlvTest {
|
||||
private fun toHex(a: LongArray) = U256.toBytes(a).toHexKey()
|
||||
private fun toHex(a: Fe4) = U256.toBytes(a).toHexKey()
|
||||
|
||||
private fun hex(s: String) = U256.fromBytes(s.hexToByteArray())
|
||||
|
||||
@@ -49,7 +49,7 @@ class GlvTest {
|
||||
|
||||
@Test
|
||||
fun splitScalarZero() {
|
||||
val split = Glv.splitScalar(LongArray(4))
|
||||
val split = Glv.splitScalar(Fe4())
|
||||
assertTrue(U256.isZero(split.k1) && U256.isZero(split.k2))
|
||||
}
|
||||
|
||||
@@ -68,11 +68,11 @@ class GlvTest {
|
||||
// Both k₁ and k₂ should be ~128 bits (fit in 4 limbs)
|
||||
val k = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val split = Glv.splitScalar(k)
|
||||
// Upper 4 limbs should be zero for a proper 128-bit half-scalar
|
||||
for (i in 2 until 4) {
|
||||
assertEquals(0, split.k1[i], "k1 limb $i should be 0")
|
||||
assertEquals(0, split.k2[i], "k2 limb $i should be 0")
|
||||
}
|
||||
// Upper 2 limbs should be zero for a proper 128-bit half-scalar
|
||||
assertEquals(0L, split.k1.l2, "k1 limb 2 should be 0")
|
||||
assertEquals(0L, split.k1.l3, "k1 limb 3 should be 0")
|
||||
assertEquals(0L, split.k2.l2, "k2 limb 2 should be 0")
|
||||
assertEquals(0L, split.k2.l3, "k2 limb 3 should be 0")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -105,7 +105,7 @@ class GlvTest {
|
||||
// β³ ≡ 1 (mod p) — the defining property of the cube root of unity
|
||||
val b2 = FieldP.sqr(Glv.BETA)
|
||||
val b3 = FieldP.mul(b2, Glv.BETA)
|
||||
val one = longArrayOf(1L, 0L, 0L, 0L, 0, 0, 0, 0)
|
||||
val one = Fe4(1L, 0L, 0L, 0L)
|
||||
assertEquals(toHex(one), toHex(b3))
|
||||
}
|
||||
|
||||
@@ -114,8 +114,8 @@ class GlvTest {
|
||||
// λ·G should equal (β·Gx, Gy)
|
||||
val result = MutablePoint()
|
||||
ECPoint.mulG(result, LAMBDA)
|
||||
val rx = LongArray(4)
|
||||
val ry = LongArray(4)
|
||||
val rx = Fe4()
|
||||
val ry = Fe4()
|
||||
ECPoint.toAffine(result, rx, ry)
|
||||
assertEquals(toHex(FieldP.mul(ECPoint.GX, Glv.BETA)), toHex(rx))
|
||||
assertEquals(toHex(ECPoint.GY), toHex(ry))
|
||||
@@ -126,9 +126,10 @@ class GlvTest {
|
||||
@Test
|
||||
fun wnafReconstructionSmall() {
|
||||
// wNAF digits should reconstruct to the original scalar
|
||||
val k = longArrayOf(17L, 0L, 0L, 0L) // 17 = 10001 in binary
|
||||
val k = Fe4(17L, 0L, 0L, 0L) // 17 = 10001 in binary
|
||||
val digits = Glv.wnaf(k, 5, 256)
|
||||
assertEquals(k[0], reconstructWnaf(digits)[0])
|
||||
val reconstructed = reconstructWnaf(digits)
|
||||
assertEquals(0, U256.cmp(k, reconstructed))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -136,7 +137,7 @@ class GlvTest {
|
||||
val k = hex("e907831f80848d1069a5371b402410364bdf1c5f8307b0084c55f1ce2dca8215")
|
||||
val digits = Glv.wnaf(k, 5, 256)
|
||||
val reconstructed = reconstructWnaf(digits)
|
||||
for (i in 0 until 4) assertEquals(k[i], reconstructed[i], "Limb $i mismatch")
|
||||
assertEquals(0, U256.cmp(k, reconstructed), "wNAF reconstruction mismatch")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -146,7 +147,7 @@ class GlvTest {
|
||||
val k = hex("944946c56e0d133f326db0b0645544a04bfcc5a0f447ad6d0227958414d8ba73")
|
||||
val digits = Glv.wnaf(k, 5, 256)
|
||||
val reconstructed = reconstructWnaf(digits)
|
||||
for (i in 0 until 4) assertEquals(k[i], reconstructed[i], "Limb $i mismatch for carry test")
|
||||
assertEquals(0, U256.cmp(k, reconstructed), "wNAF carry overflow reconstruction mismatch")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -179,10 +180,10 @@ class GlvTest {
|
||||
@Test
|
||||
fun wnafSmallMaxBits() {
|
||||
// wNAF with maxBits=129 (used for GLV half-scalars)
|
||||
val k = longArrayOf(-7296712173568108936L, 2459565876494606609L, 0L, 0L)
|
||||
val k = Fe4(-7296712173568108936L, 2459565876494606609L, 0L, 0L)
|
||||
val digits = Glv.wnaf(k, 5, 129)
|
||||
val reconstructed = reconstructWnaf(digits)
|
||||
for (i in 0 until 4) assertEquals(k[i], reconstructed[i], "Limb $i mismatch for 129-bit wNAF")
|
||||
assertEquals(0, U256.cmp(k, reconstructed), "129-bit wNAF reconstruction mismatch")
|
||||
}
|
||||
|
||||
// ==================== Integration: GLV mulDoubleG ====================
|
||||
@@ -192,16 +193,16 @@ class GlvTest {
|
||||
// s·G + 0·P = s·G
|
||||
val s = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val p = MutablePoint()
|
||||
ECPoint.mulG(p, longArrayOf(2L, 0L, 0L, 0L, 0, 0, 0, 0))
|
||||
ECPoint.mulG(p, Fe4(2L, 0L, 0L, 0L))
|
||||
val combined = MutablePoint()
|
||||
ECPoint.mulDoubleG(combined, s, p, LongArray(4))
|
||||
val cx = LongArray(4)
|
||||
val cy = LongArray(4)
|
||||
ECPoint.mulDoubleG(combined, s, p, Fe4())
|
||||
val cx = Fe4()
|
||||
val cy = Fe4()
|
||||
ECPoint.toAffine(combined, cx, cy)
|
||||
val direct = MutablePoint()
|
||||
ECPoint.mulG(direct, s)
|
||||
val dx = LongArray(4)
|
||||
val dy = LongArray(4)
|
||||
val dx = Fe4()
|
||||
val dy = Fe4()
|
||||
ECPoint.toAffine(direct, dx, dy)
|
||||
assertEquals(toHex(dx), toHex(cx))
|
||||
}
|
||||
@@ -209,37 +210,38 @@ class GlvTest {
|
||||
// ==================== Helpers ====================
|
||||
|
||||
/** Reconstruct a scalar from wNAF digits using Horner's method. */
|
||||
private fun reconstructWnaf(digits: IntArray): LongArray {
|
||||
var acc = LongArray(4)
|
||||
private fun reconstructWnaf(digits: IntArray): Fe4 {
|
||||
var acc = Fe4()
|
||||
for (bit in digits.size - 1 downTo 0) {
|
||||
// Double: acc = acc * 2 (unsigned shift left by 1)
|
||||
val doubled = LongArray(4)
|
||||
var shiftCarry = 0L
|
||||
for (j in 0 until 4) {
|
||||
doubled[j] = (acc[j] shl 1) or shiftCarry
|
||||
shiftCarry = acc[j] ushr 63
|
||||
}
|
||||
val doubled = Fe4()
|
||||
doubled.l0 = (acc.l0 shl 1)
|
||||
doubled.l1 = (acc.l1 shl 1) or (acc.l0 ushr 63)
|
||||
doubled.l2 = (acc.l2 shl 1) or (acc.l1 ushr 63)
|
||||
doubled.l3 = (acc.l3 shl 1) or (acc.l2 ushr 63)
|
||||
acc = doubled
|
||||
// Add digit
|
||||
val d = digits[bit]
|
||||
if (d > 0) {
|
||||
val s = acc[0] + d.toLong()
|
||||
val c = if (s.toULong() < acc[0].toULong()) 1L else 0L
|
||||
acc[0] = s
|
||||
val s = acc.l0 + d.toLong()
|
||||
val c = if (s.toULong() < acc.l0.toULong()) 1L else 0L
|
||||
acc.l0 = s
|
||||
if (c != 0L) {
|
||||
for (j in 1 until 4) {
|
||||
acc[j]++
|
||||
if (acc[j] != 0L) break
|
||||
acc.l1++
|
||||
if (acc.l1 == 0L) {
|
||||
acc.l2++
|
||||
if (acc.l2 == 0L) acc.l3++
|
||||
}
|
||||
}
|
||||
} else if (d < 0) {
|
||||
val s = acc[0] - (-d).toLong()
|
||||
val b = if (acc[0].toULong() < (-d).toULong()) 1L else 0L
|
||||
acc[0] = s
|
||||
val s = acc.l0 - (-d).toLong()
|
||||
val b = if (acc.l0.toULong() < (-d).toULong()) 1L else 0L
|
||||
acc.l0 = s
|
||||
if (b != 0L) {
|
||||
for (j in 1 until 4) {
|
||||
acc[j]--
|
||||
if (acc[j] != -1L) break
|
||||
acc.l1--
|
||||
if (acc.l1 == -1L) {
|
||||
acc.l2--
|
||||
if (acc.l2 == -1L) acc.l3--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-30
@@ -28,14 +28,14 @@ import kotlin.test.assertTrue
|
||||
|
||||
/** Tests for KeyCodec: key parsing, serialization, liftX, hasEvenY. */
|
||||
class KeyCodecTest {
|
||||
private fun toHex(a: LongArray) = U256.toBytes(a).toHexKey()
|
||||
private fun toHex(a: Fe4) = U256.toBytes(a).toHexKey()
|
||||
|
||||
// ==================== liftX ====================
|
||||
|
||||
@Test
|
||||
fun liftXGenerator() {
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertTrue(KeyCodec.liftX(x, y, ECPoint.GX))
|
||||
assertEquals(toHex(ECPoint.GX), toHex(x))
|
||||
assertTrue(KeyCodec.hasEvenY(y))
|
||||
@@ -44,25 +44,25 @@ class KeyCodecTest {
|
||||
@Test
|
||||
fun liftXInvalidFieldElement() {
|
||||
// p itself is not a valid x
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertFalse(KeyCodec.liftX(x, y, FieldP.P))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun liftXNotOnCurve() {
|
||||
// x=2: y² = 8+7 = 15. 15 is not a quadratic residue mod p.
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val two = longArrayOf(2, 0L, 0L, 0L)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
val two = Fe4(2, 0L, 0L, 0L)
|
||||
// This may or may not be on the curve — just check it doesn't crash
|
||||
KeyCodec.liftX(x, y, two) // result doesn't matter, just no exception
|
||||
}
|
||||
|
||||
@Test
|
||||
fun liftXAlwaysReturnsEvenY() {
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertTrue(KeyCodec.liftX(x, y, ECPoint.GX))
|
||||
assertTrue(KeyCodec.hasEvenY(y), "liftX should always return even y")
|
||||
}
|
||||
@@ -71,14 +71,14 @@ class KeyCodecTest {
|
||||
|
||||
@Test
|
||||
fun hasEvenYForEvenValue() {
|
||||
assertTrue(KeyCodec.hasEvenY(longArrayOf(2, 0L, 0L, 0L)))
|
||||
assertTrue(KeyCodec.hasEvenY(longArrayOf(0, 0L, 0L, 0L)))
|
||||
assertTrue(KeyCodec.hasEvenY(Fe4(2, 0L, 0L, 0L)))
|
||||
assertTrue(KeyCodec.hasEvenY(Fe4(0, 0L, 0L, 0L)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasEvenYForOddValue() {
|
||||
assertFalse(KeyCodec.hasEvenY(longArrayOf(1, 0L, 0L, 0L)))
|
||||
assertFalse(KeyCodec.hasEvenY(longArrayOf(3, 0L, 0L, 0L)))
|
||||
assertFalse(KeyCodec.hasEvenY(Fe4(1, 0L, 0L, 0L)))
|
||||
assertFalse(KeyCodec.hasEvenY(Fe4(3, 0L, 0L, 0L)))
|
||||
}
|
||||
|
||||
// ==================== parsePublicKey ====================
|
||||
@@ -87,8 +87,8 @@ class KeyCodecTest {
|
||||
fun parseCompressedEvenY() {
|
||||
val compressed = KeyCodec.serializeCompressed(ECPoint.GX, ECPoint.GY)
|
||||
assertEquals(0x02.toByte(), compressed[0])
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertTrue(KeyCodec.parsePublicKey(compressed, x, y))
|
||||
assertEquals(toHex(ECPoint.GX), toHex(x))
|
||||
assertEquals(toHex(ECPoint.GY), toHex(y))
|
||||
@@ -99,8 +99,8 @@ class KeyCodecTest {
|
||||
val negGy = FieldP.neg(ECPoint.GY)
|
||||
val compressed = KeyCodec.serializeCompressed(ECPoint.GX, negGy)
|
||||
assertEquals(0x03.toByte(), compressed[0])
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertTrue(KeyCodec.parsePublicKey(compressed, x, y))
|
||||
assertEquals(toHex(ECPoint.GX), toHex(x))
|
||||
assertEquals(toHex(negGy), toHex(y))
|
||||
@@ -110,8 +110,8 @@ class KeyCodecTest {
|
||||
fun parseUncompressed() {
|
||||
val uncompressed = KeyCodec.serializeUncompressed(ECPoint.GX, ECPoint.GY)
|
||||
assertEquals(0x04.toByte(), uncompressed[0])
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertTrue(KeyCodec.parsePublicKey(uncompressed, x, y))
|
||||
assertEquals(toHex(ECPoint.GX), toHex(x))
|
||||
assertEquals(toHex(ECPoint.GY), toHex(y))
|
||||
@@ -119,8 +119,8 @@ class KeyCodecTest {
|
||||
|
||||
@Test
|
||||
fun parseInvalidSizes() {
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertFalse(KeyCodec.parsePublicKey(ByteArray(0), x, y))
|
||||
assertFalse(KeyCodec.parsePublicKey(ByteArray(10), x, y))
|
||||
assertFalse(KeyCodec.parsePublicKey(ByteArray(32), x, y))
|
||||
@@ -131,8 +131,8 @@ class KeyCodecTest {
|
||||
|
||||
@Test
|
||||
fun parseInvalidPrefix() {
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertFalse(KeyCodec.parsePublicKey(ByteArray(33), x, y)) // prefix 0x00
|
||||
assertFalse(KeyCodec.parsePublicKey(ByteArray(65), x, y)) // prefix 0x00
|
||||
}
|
||||
@@ -144,8 +144,8 @@ class KeyCodecTest {
|
||||
fake[0] = 0x04
|
||||
fake[1] = 0x01 // x = 1 (padded)
|
||||
fake[33] = 0x01 // y = 1 (padded) — 1² ≠ 1³ + 7
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertFalse(KeyCodec.parsePublicKey(fake, x, y))
|
||||
}
|
||||
|
||||
@@ -154,8 +154,8 @@ class KeyCodecTest {
|
||||
@Test
|
||||
fun compressDecompressRoundTrip() {
|
||||
val compressed = KeyCodec.serializeCompressed(ECPoint.GX, ECPoint.GY)
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertTrue(KeyCodec.parsePublicKey(compressed, x, y))
|
||||
val recompressed = KeyCodec.serializeCompressed(x, y)
|
||||
assertEquals(compressed.toList(), recompressed.toList())
|
||||
@@ -164,8 +164,8 @@ class KeyCodecTest {
|
||||
@Test
|
||||
fun uncompressedRoundTrip() {
|
||||
val uncompressed = KeyCodec.serializeUncompressed(ECPoint.GX, ECPoint.GY)
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertTrue(KeyCodec.parsePublicKey(uncompressed, x, y))
|
||||
val reser = KeyCodec.serializeUncompressed(x, y)
|
||||
assertEquals(uncompressed.toList(), reser.toList())
|
||||
|
||||
+56
-56
@@ -31,7 +31,7 @@ import kotlin.test.assertTrue
|
||||
class PointTest {
|
||||
private fun hex(s: String) = U256.fromBytes(s.hexToByteArray())
|
||||
|
||||
private fun toHex(a: LongArray) = U256.toBytes(a).toHexKey()
|
||||
private fun toHex(a: Fe4) = U256.toBytes(a).toHexKey()
|
||||
|
||||
// ==================== Generator point ====================
|
||||
|
||||
@@ -39,7 +39,7 @@ class PointTest {
|
||||
fun generatorIsOnCurve() {
|
||||
// y² = x³ + 7
|
||||
val x3 = FieldP.mul(FieldP.sqr(ECPoint.GX), ECPoint.GX)
|
||||
val y2expected = FieldP.add(x3, longArrayOf(7, 0L, 0L, 0L))
|
||||
val y2expected = FieldP.add(x3, Fe4(7, 0L, 0L, 0L))
|
||||
val y2actual = FieldP.sqr(ECPoint.GY)
|
||||
assertEquals(toHex(y2expected), toHex(y2actual))
|
||||
}
|
||||
@@ -53,16 +53,16 @@ class PointTest {
|
||||
p.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
val doubled = MutablePoint()
|
||||
ECPoint.doublePoint(doubled, p)
|
||||
val dx = LongArray(4)
|
||||
val dy = LongArray(4)
|
||||
val dx = Fe4()
|
||||
val dy = Fe4()
|
||||
ECPoint.toAffine(doubled, dx, dy)
|
||||
|
||||
// 2·G via scalar multiplication
|
||||
val two = longArrayOf(2, 0L, 0L, 0L)
|
||||
val two = Fe4(2, 0L, 0L, 0L)
|
||||
val mulResult = MutablePoint()
|
||||
ECPoint.mulG(mulResult, two)
|
||||
val mx = LongArray(4)
|
||||
val my = LongArray(4)
|
||||
val mx = Fe4()
|
||||
val my = Fe4()
|
||||
ECPoint.toAffine(mulResult, mx, my)
|
||||
|
||||
assertEquals(toHex(mx), toHex(dx))
|
||||
@@ -75,15 +75,15 @@ class PointTest {
|
||||
val p = MutablePoint()
|
||||
p.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
ECPoint.doublePoint(p, p)
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
ECPoint.toAffine(p, x, y)
|
||||
|
||||
val two = longArrayOf(2, 0L, 0L, 0L)
|
||||
val two = Fe4(2, 0L, 0L, 0L)
|
||||
val expected = MutablePoint()
|
||||
ECPoint.mulG(expected, two)
|
||||
val ex = LongArray(4)
|
||||
val ey = LongArray(4)
|
||||
val ex = Fe4()
|
||||
val ey = Fe4()
|
||||
ECPoint.toAffine(expected, ex, ey)
|
||||
|
||||
assertEquals(toHex(ex), toHex(x))
|
||||
@@ -106,14 +106,14 @@ class PointTest {
|
||||
g.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
val sum = MutablePoint()
|
||||
ECPoint.addPoints(sum, g, g)
|
||||
val sx = LongArray(4)
|
||||
val sy = LongArray(4)
|
||||
val sx = Fe4()
|
||||
val sy = Fe4()
|
||||
ECPoint.toAffine(sum, sx, sy)
|
||||
|
||||
val doubled = MutablePoint()
|
||||
ECPoint.doublePoint(doubled, g)
|
||||
val dx = LongArray(4)
|
||||
val dy = LongArray(4)
|
||||
val dx = Fe4()
|
||||
val dy = Fe4()
|
||||
ECPoint.toAffine(doubled, dx, dy)
|
||||
|
||||
assertEquals(toHex(dx), toHex(sx))
|
||||
@@ -129,15 +129,15 @@ class PointTest {
|
||||
|
||||
val result = MutablePoint()
|
||||
ECPoint.addPoints(result, g, inf)
|
||||
val rx = LongArray(4)
|
||||
val ry = LongArray(4)
|
||||
val rx = Fe4()
|
||||
val ry = Fe4()
|
||||
ECPoint.toAffine(result, rx, ry)
|
||||
assertEquals(toHex(ECPoint.GX), toHex(rx))
|
||||
|
||||
val result2 = MutablePoint()
|
||||
ECPoint.addPoints(result2, inf, g)
|
||||
val r2x = LongArray(4)
|
||||
val r2y = LongArray(4)
|
||||
val r2x = Fe4()
|
||||
val r2y = Fe4()
|
||||
ECPoint.toAffine(result2, r2x, r2y)
|
||||
assertEquals(toHex(ECPoint.GX), toHex(r2x))
|
||||
}
|
||||
@@ -160,15 +160,15 @@ class PointTest {
|
||||
@Test
|
||||
fun addMixedMatchesFull() {
|
||||
// addMixed should produce the same result as addPoints when q is affine
|
||||
val three = longArrayOf(3, 0L, 0L, 0L)
|
||||
val three = Fe4(3, 0L, 0L, 0L)
|
||||
val p = MutablePoint()
|
||||
ECPoint.mulG(p, three) // 3G in Jacobian (z ≠ 1)
|
||||
|
||||
// Add G as affine
|
||||
val mixed = MutablePoint()
|
||||
ECPoint.addMixed(mixed, p, ECPoint.GX, ECPoint.GY)
|
||||
val mx = LongArray(4)
|
||||
val my = LongArray(4)
|
||||
val mx = Fe4()
|
||||
val my = Fe4()
|
||||
ECPoint.toAffine(mixed, mx, my)
|
||||
|
||||
// Add G as Jacobian
|
||||
@@ -176,8 +176,8 @@ class PointTest {
|
||||
gJac.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
val full = MutablePoint()
|
||||
ECPoint.addPoints(full, p, gJac)
|
||||
val fx = LongArray(4)
|
||||
val fy = LongArray(4)
|
||||
val fx = Fe4()
|
||||
val fy = Fe4()
|
||||
ECPoint.toAffine(full, fx, fy)
|
||||
|
||||
assertEquals(toHex(fx), toHex(mx))
|
||||
@@ -190,8 +190,8 @@ class PointTest {
|
||||
inf.setInfinity()
|
||||
val result = MutablePoint()
|
||||
ECPoint.addMixed(result, inf, ECPoint.GX, ECPoint.GY)
|
||||
val rx = LongArray(4)
|
||||
val ry = LongArray(4)
|
||||
val rx = Fe4()
|
||||
val ry = Fe4()
|
||||
ECPoint.toAffine(result, rx, ry)
|
||||
assertEquals(toHex(ECPoint.GX), toHex(rx))
|
||||
}
|
||||
@@ -200,11 +200,11 @@ class PointTest {
|
||||
|
||||
@Test
|
||||
fun mulGByOne() {
|
||||
val one = longArrayOf(1, 0L, 0L, 0L)
|
||||
val one = Fe4(1, 0L, 0L, 0L)
|
||||
val result = MutablePoint()
|
||||
ECPoint.mulG(result, one)
|
||||
val rx = LongArray(4)
|
||||
val ry = LongArray(4)
|
||||
val rx = Fe4()
|
||||
val ry = Fe4()
|
||||
ECPoint.toAffine(result, rx, ry)
|
||||
assertEquals(toHex(ECPoint.GX), toHex(rx))
|
||||
assertEquals(toHex(ECPoint.GY), toHex(ry))
|
||||
@@ -212,7 +212,7 @@ class PointTest {
|
||||
|
||||
@Test
|
||||
fun mulGByZeroIsInfinity() {
|
||||
val zero = LongArray(4)
|
||||
val zero = Fe4()
|
||||
val result = MutablePoint()
|
||||
ECPoint.mulG(result, zero)
|
||||
assertTrue(result.isInfinity())
|
||||
@@ -232,16 +232,16 @@ class PointTest {
|
||||
val k = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val gResult = MutablePoint()
|
||||
ECPoint.mulG(gResult, k)
|
||||
val gx = LongArray(4)
|
||||
val gy = LongArray(4)
|
||||
val gx = Fe4()
|
||||
val gy = Fe4()
|
||||
ECPoint.toAffine(gResult, gx, gy)
|
||||
|
||||
val g = MutablePoint()
|
||||
g.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
val mResult = MutablePoint()
|
||||
ECPoint.mul(mResult, g, k)
|
||||
val mx = LongArray(4)
|
||||
val my = LongArray(4)
|
||||
val mx = Fe4()
|
||||
val my = Fe4()
|
||||
ECPoint.toAffine(mResult, mx, my)
|
||||
|
||||
assertEquals(toHex(mx), toHex(gx))
|
||||
@@ -255,14 +255,14 @@ class PointTest {
|
||||
val e = hex("3982f19bef1615bccfbb05e321c10e1d4cba3df0e841c2e41eeb6016347653c3")
|
||||
|
||||
val p = MutablePoint()
|
||||
val two = longArrayOf(2, 0L, 0L, 0L)
|
||||
val two = Fe4(2, 0L, 0L, 0L)
|
||||
ECPoint.mulG(p, two) // P = 2·G
|
||||
|
||||
// Combined
|
||||
val combined = MutablePoint()
|
||||
ECPoint.mulDoubleG(combined, s, p, e)
|
||||
val cx = LongArray(4)
|
||||
val cy = LongArray(4)
|
||||
val cx = Fe4()
|
||||
val cy = Fe4()
|
||||
ECPoint.toAffine(combined, cx, cy)
|
||||
|
||||
// Separate
|
||||
@@ -272,8 +272,8 @@ class PointTest {
|
||||
ECPoint.mul(eP, p, e)
|
||||
val sep = MutablePoint()
|
||||
ECPoint.addPoints(sep, sG, eP)
|
||||
val sx = LongArray(4)
|
||||
val sy = LongArray(4)
|
||||
val sx = Fe4()
|
||||
val sy = Fe4()
|
||||
ECPoint.toAffine(sep, sx, sy)
|
||||
|
||||
assertEquals(toHex(sx), toHex(cx))
|
||||
@@ -284,8 +284,8 @@ class PointTest {
|
||||
|
||||
@Test
|
||||
fun liftXGenerator() {
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertTrue(KeyCodec.liftX(x, y, ECPoint.GX))
|
||||
assertEquals(toHex(ECPoint.GX), toHex(x))
|
||||
// liftX returns even y
|
||||
@@ -295,8 +295,8 @@ class PointTest {
|
||||
@Test
|
||||
fun liftXInvalidX() {
|
||||
// p itself is not a valid x coordinate
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertFalse(KeyCodec.liftX(x, y, FieldP.P))
|
||||
}
|
||||
|
||||
@@ -305,8 +305,8 @@ class PointTest {
|
||||
@Test
|
||||
fun compressDecompressRoundTrip() {
|
||||
val compressed = KeyCodec.serializeCompressed(ECPoint.GX, ECPoint.GY)
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertTrue(KeyCodec.parsePublicKey(compressed, x, y))
|
||||
assertEquals(toHex(ECPoint.GX), toHex(x))
|
||||
assertEquals(toHex(ECPoint.GY), toHex(y))
|
||||
@@ -315,8 +315,8 @@ class PointTest {
|
||||
@Test
|
||||
fun uncompressedRoundTrip() {
|
||||
val uncompressed = KeyCodec.serializeUncompressed(ECPoint.GX, ECPoint.GY)
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertTrue(KeyCodec.parsePublicKey(uncompressed, x, y))
|
||||
assertEquals(toHex(ECPoint.GX), toHex(x))
|
||||
assertEquals(toHex(ECPoint.GY), toHex(y))
|
||||
@@ -324,8 +324,8 @@ class PointTest {
|
||||
|
||||
@Test
|
||||
fun parseInvalidKey() {
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertFalse(KeyCodec.parsePublicKey(ByteArray(10), x, y))
|
||||
assertFalse(KeyCodec.parsePublicKey(ByteArray(33), x, y)) // wrong prefix (0x00)
|
||||
}
|
||||
@@ -337,13 +337,13 @@ class PointTest {
|
||||
p.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
val result = MutablePoint()
|
||||
ECPoint.addMixed(result, p, ECPoint.GX, ECPoint.GY)
|
||||
val rx = LongArray(4)
|
||||
val ry = LongArray(4)
|
||||
val rx = Fe4()
|
||||
val ry = Fe4()
|
||||
ECPoint.toAffine(result, rx, ry)
|
||||
val doubled = MutablePoint()
|
||||
ECPoint.doublePoint(doubled, p)
|
||||
val dx = LongArray(4)
|
||||
val dy = LongArray(4)
|
||||
val dx = Fe4()
|
||||
val dy = Fe4()
|
||||
ECPoint.toAffine(doubled, dx, dy)
|
||||
assertEquals(toHex(dx), toHex(rx))
|
||||
}
|
||||
@@ -364,8 +364,8 @@ class PointTest {
|
||||
val pubkey = Secp256k1.pubkeyCreate(privKeyBytes)
|
||||
val compressed = Secp256k1.pubKeyCompress(pubkey)
|
||||
assertEquals(0x03.toByte(), compressed[0]) // Odd y → 03 prefix
|
||||
val x = LongArray(4)
|
||||
val y = LongArray(4)
|
||||
val x = Fe4()
|
||||
val y = Fe4()
|
||||
assertTrue(KeyCodec.parsePublicKey(compressed, x, y))
|
||||
// Round-trip: compress again should give same result
|
||||
val recompressed = KeyCodec.serializeCompressed(x, y)
|
||||
|
||||
+11
-11
@@ -31,7 +31,7 @@ import kotlin.test.assertTrue
|
||||
class ScalarNTest {
|
||||
private fun hex(s: String) = U256.fromBytes(s.hexToByteArray())
|
||||
|
||||
private fun toHex(a: LongArray) = U256.toBytes(a).toHexKey()
|
||||
private fun toHex(a: Fe4) = U256.toBytes(a).toHexKey()
|
||||
|
||||
// ==================== isValid ====================
|
||||
|
||||
@@ -42,7 +42,7 @@ class ScalarNTest {
|
||||
|
||||
@Test
|
||||
fun isValidZero() {
|
||||
assertFalse(ScalarN.isValid(LongArray(4)))
|
||||
assertFalse(ScalarN.isValid(Fe4()))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -62,7 +62,7 @@ class ScalarNTest {
|
||||
@Test
|
||||
fun addZeroIdentity() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
assertEquals(toHex(a), toHex(ScalarN.add(a, LongArray(4))))
|
||||
assertEquals(toHex(a), toHex(ScalarN.add(a, Fe4())))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -95,7 +95,7 @@ class ScalarNTest {
|
||||
@Test
|
||||
fun mulOneIdentity() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val one = longArrayOf(1, 0L, 0L, 0L)
|
||||
val one = Fe4(1, 0L, 0L, 0L)
|
||||
assertEquals(toHex(a), toHex(ScalarN.mul(a, one)))
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class ScalarNTest {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val aInv = ScalarN.inv(a)
|
||||
val product = ScalarN.mul(a, aInv)
|
||||
val one = longArrayOf(1, 0L, 0L, 0L)
|
||||
val one = Fe4(1, 0L, 0L, 0L)
|
||||
assertEquals(toHex(one), toHex(product))
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ class ScalarNTest {
|
||||
fun addNearN() {
|
||||
// (n-1) + 1 should wrap to 0
|
||||
val nMinus1 = hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")
|
||||
val one = longArrayOf(1, 0L, 0L, 0L)
|
||||
val one = Fe4(1, 0L, 0L, 0L)
|
||||
assertTrue(U256.isZero(ScalarN.add(nMinus1, one)))
|
||||
}
|
||||
|
||||
@@ -141,9 +141,9 @@ class ScalarNTest {
|
||||
fun addNearNWrap() {
|
||||
// (n-1) + 2 should give 1
|
||||
val nMinus1 = hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")
|
||||
val two = longArrayOf(2, 0L, 0L, 0L)
|
||||
val two = Fe4(2, 0L, 0L, 0L)
|
||||
val result = ScalarN.add(nMinus1, two)
|
||||
val one = longArrayOf(1, 0L, 0L, 0L)
|
||||
val one = Fe4(1, 0L, 0L, 0L)
|
||||
assertEquals(toHex(one), toHex(result))
|
||||
}
|
||||
|
||||
@@ -152,19 +152,19 @@ class ScalarNTest {
|
||||
// (n-1) * (n-1) ≡ 1 mod n (since (n-1) ≡ -1 and (-1)² = 1)
|
||||
val nMinus1 = hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")
|
||||
val result = ScalarN.mul(nMinus1, nMinus1)
|
||||
val one = longArrayOf(1, 0L, 0L, 0L)
|
||||
val one = Fe4(1, 0L, 0L, 0L)
|
||||
assertEquals(toHex(one), toHex(result))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun negOfZeroIsZero() {
|
||||
assertTrue(U256.isZero(ScalarN.neg(LongArray(4))))
|
||||
assertTrue(U256.isZero(ScalarN.neg(Fe4())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reduceOfN() {
|
||||
val result = ScalarN.reduce(ScalarN.N.copyOf())
|
||||
assertTrue(U256.isZero(result))
|
||||
assertTrue(result.isZero())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -31,18 +31,18 @@ import kotlin.test.assertTrue
|
||||
class U256Test {
|
||||
private fun hex(s: String) = U256.fromBytes(s.hexToByteArray())
|
||||
|
||||
private fun toHex(a: LongArray) = U256.toBytes(a).toHexKey()
|
||||
private fun toHex(a: Fe4) = U256.toBytes(a).toHexKey()
|
||||
|
||||
// ==================== isZero / cmp ====================
|
||||
|
||||
@Test
|
||||
fun isZeroTrue() = assertTrue(U256.isZero(LongArray(4)))
|
||||
fun isZeroTrue() = assertTrue(U256.isZero(Fe4()))
|
||||
|
||||
@Test
|
||||
fun isZeroFalse() = assertFalse(U256.isZero(longArrayOf(1, 0L, 0L, 0L)))
|
||||
fun isZeroFalse() = assertFalse(U256.isZero(Fe4(1, 0L, 0L, 0L)))
|
||||
|
||||
@Test
|
||||
fun isZeroHighBit() = assertFalse(U256.isZero(longArrayOf(0L, 0L, 0L, 1L)))
|
||||
fun isZeroHighBit() = assertFalse(U256.isZero(Fe4(0L, 0L, 0L, 1L)))
|
||||
|
||||
@Test
|
||||
fun cmpEqual() = assertEquals(0, U256.cmp(hex("0000000000000000000000000000000000000000000000000000000000000001"), hex("0000000000000000000000000000000000000000000000000000000000000001")))
|
||||
@@ -57,7 +57,7 @@ class U256Test {
|
||||
|
||||
@Test
|
||||
fun addSimple() {
|
||||
val out = LongArray(4)
|
||||
val out = Fe4()
|
||||
val carry = U256.addTo(out, hex("0000000000000000000000000000000000000000000000000000000000000001"), hex("0000000000000000000000000000000000000000000000000000000000000002"))
|
||||
assertEquals("0000000000000000000000000000000000000000000000000000000000000003", toHex(out))
|
||||
assertEquals(0, carry)
|
||||
@@ -65,7 +65,7 @@ class U256Test {
|
||||
|
||||
@Test
|
||||
fun addOverflow() {
|
||||
val out = LongArray(4)
|
||||
val out = Fe4()
|
||||
val carry = U256.addTo(out, hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), hex("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
assertEquals("0000000000000000000000000000000000000000000000000000000000000000", toHex(out))
|
||||
assertEquals(1, carry)
|
||||
@@ -73,14 +73,14 @@ class U256Test {
|
||||
|
||||
@Test
|
||||
fun addLimbCarry() {
|
||||
val out = LongArray(4)
|
||||
val out = Fe4()
|
||||
U256.addTo(out, hex("00000000000000000000000000000000000000000000000000000000ffffffff"), hex("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
assertEquals("0000000000000000000000000000000000000000000000000000000100000000", toHex(out))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subSimple() {
|
||||
val out = LongArray(4)
|
||||
val out = Fe4()
|
||||
val borrow = U256.subTo(out, hex("0000000000000000000000000000000000000000000000000000000000000003"), hex("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
assertEquals("0000000000000000000000000000000000000000000000000000000000000002", toHex(out))
|
||||
assertEquals(0, borrow)
|
||||
@@ -88,7 +88,7 @@ class U256Test {
|
||||
|
||||
@Test
|
||||
fun subUnderflow() {
|
||||
val out = LongArray(4)
|
||||
val out = Fe4()
|
||||
val borrow = U256.subTo(out, hex("0000000000000000000000000000000000000000000000000000000000000000"), hex("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
assertEquals("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", toHex(out))
|
||||
assertEquals(1, borrow)
|
||||
@@ -98,50 +98,73 @@ class U256Test {
|
||||
|
||||
@Test
|
||||
fun mulWideSmall() {
|
||||
val out = LongArray(8)
|
||||
val out = Wide8()
|
||||
U256.mulWide(out, hex("0000000000000000000000000000000000000000000000000000000000000003"), hex("0000000000000000000000000000000000000000000000000000000000000007"))
|
||||
// 3 * 7 = 21 = 0x15
|
||||
assertEquals(0x15L, out[0])
|
||||
for (i in 1 until 8) assertEquals(0L, out[i])
|
||||
assertEquals(0x15L, out.l0)
|
||||
assertEquals(0L, out.l1)
|
||||
assertEquals(0L, out.l2)
|
||||
assertEquals(0L, out.l3)
|
||||
assertEquals(0L, out.l4)
|
||||
assertEquals(0L, out.l5)
|
||||
assertEquals(0L, out.l6)
|
||||
assertEquals(0L, out.l7)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mulWideLarge() {
|
||||
// (2^128 - 1)² consistency: mulWide and sqrWide should match
|
||||
val out1 = LongArray(8)
|
||||
val out2 = LongArray(8)
|
||||
val out1 = Wide8()
|
||||
val out2 = Wide8()
|
||||
val maxHalf = hex("00000000000000000000000000000000ffffffffffffffffffffffffffffffff")
|
||||
U256.mulWide(out1, maxHalf, maxHalf)
|
||||
U256.sqrWide(out2, maxHalf)
|
||||
for (i in 0 until 8) assertEquals(out1[i], out2[i], "Limb $i mismatch")
|
||||
assertEquals(out1.l0, out2.l0, "Limb 0 mismatch")
|
||||
assertEquals(out1.l1, out2.l1, "Limb 1 mismatch")
|
||||
assertEquals(out1.l2, out2.l2, "Limb 2 mismatch")
|
||||
assertEquals(out1.l3, out2.l3, "Limb 3 mismatch")
|
||||
assertEquals(out1.l4, out2.l4, "Limb 4 mismatch")
|
||||
assertEquals(out1.l5, out2.l5, "Limb 5 mismatch")
|
||||
assertEquals(out1.l6, out2.l6, "Limb 6 mismatch")
|
||||
assertEquals(out1.l7, out2.l7, "Limb 7 mismatch")
|
||||
// Lowest limb is 1 (from +1 in (2^128-1)² = 2^256 - 2^129 + 1)
|
||||
assertEquals(1L, out1[0])
|
||||
assertEquals(1L, out1.l0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sqrWideMatchesMulWide() {
|
||||
// sqrWide(a) should produce the same result as mulWide(a, a)
|
||||
val a = hex("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530")
|
||||
val mulResult = LongArray(8)
|
||||
val mulResult = Wide8()
|
||||
U256.mulWide(mulResult, a, a)
|
||||
val sqrResult = LongArray(8)
|
||||
val sqrResult = Wide8()
|
||||
U256.sqrWide(sqrResult, a)
|
||||
for (i in 0 until 8) {
|
||||
assertEquals(mulResult[i], sqrResult[i], "Limb $i mismatch")
|
||||
}
|
||||
assertEquals(mulResult.l0, sqrResult.l0, "Limb 0 mismatch")
|
||||
assertEquals(mulResult.l1, sqrResult.l1, "Limb 1 mismatch")
|
||||
assertEquals(mulResult.l2, sqrResult.l2, "Limb 2 mismatch")
|
||||
assertEquals(mulResult.l3, sqrResult.l3, "Limb 3 mismatch")
|
||||
assertEquals(mulResult.l4, sqrResult.l4, "Limb 4 mismatch")
|
||||
assertEquals(mulResult.l5, sqrResult.l5, "Limb 5 mismatch")
|
||||
assertEquals(mulResult.l6, sqrResult.l6, "Limb 6 mismatch")
|
||||
assertEquals(mulResult.l7, sqrResult.l7, "Limb 7 mismatch")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sqrWideMaxValue() {
|
||||
// (2^256 - 1)^2 should match mulWide
|
||||
val maxVal = hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
val mulResult = LongArray(8)
|
||||
val mulResult = Wide8()
|
||||
U256.mulWide(mulResult, maxVal, maxVal)
|
||||
val sqrResult = LongArray(8)
|
||||
val sqrResult = Wide8()
|
||||
U256.sqrWide(sqrResult, maxVal)
|
||||
for (i in 0 until 8) {
|
||||
assertEquals(mulResult[i], sqrResult[i], "Limb $i mismatch for max value sqr")
|
||||
}
|
||||
assertEquals(mulResult.l0, sqrResult.l0, "Limb 0 mismatch for max value sqr")
|
||||
assertEquals(mulResult.l1, sqrResult.l1, "Limb 1 mismatch for max value sqr")
|
||||
assertEquals(mulResult.l2, sqrResult.l2, "Limb 2 mismatch for max value sqr")
|
||||
assertEquals(mulResult.l3, sqrResult.l3, "Limb 3 mismatch for max value sqr")
|
||||
assertEquals(mulResult.l4, sqrResult.l4, "Limb 4 mismatch for max value sqr")
|
||||
assertEquals(mulResult.l5, sqrResult.l5, "Limb 5 mismatch for max value sqr")
|
||||
assertEquals(mulResult.l6, sqrResult.l6, "Limb 6 mismatch for max value sqr")
|
||||
assertEquals(mulResult.l7, sqrResult.l7, "Limb 7 mismatch for max value sqr")
|
||||
}
|
||||
|
||||
// ==================== fromBytes / toBytes ====================
|
||||
@@ -158,7 +181,7 @@ class U256Test {
|
||||
@Test
|
||||
fun bytesZero() {
|
||||
val limbs = U256.fromBytes(ByteArray(32))
|
||||
assertTrue(U256.isZero(limbs))
|
||||
assertTrue(limbs.isZero())
|
||||
}
|
||||
|
||||
// ==================== getNibble ====================
|
||||
@@ -200,7 +223,7 @@ class U256Test {
|
||||
|
||||
@Test
|
||||
fun xorBasic() {
|
||||
val out = LongArray(4)
|
||||
val out = Fe4()
|
||||
U256.xorTo(out, hex("ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00"), hex("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"))
|
||||
assertEquals("f00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00f", toHex(out))
|
||||
}
|
||||
@@ -220,9 +243,12 @@ class U256Test {
|
||||
@Test
|
||||
fun copyIntoTest() {
|
||||
val src = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val dst = LongArray(4)
|
||||
val dst = Fe4()
|
||||
U256.copyInto(dst, src)
|
||||
for (i in 0 until 4) assertEquals(src[i], dst[i])
|
||||
assertEquals(src.l0, dst.l0)
|
||||
assertEquals(src.l1, dst.l1)
|
||||
assertEquals(src.l2, dst.l2)
|
||||
assertEquals(src.l3, dst.l3)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -232,6 +258,9 @@ class U256Test {
|
||||
val expected = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
U256.toBytesInto(expected, fullArray, 32)
|
||||
val decoded = U256.fromBytes(fullArray, 32)
|
||||
for (i in 0 until 4) assertEquals(expected[i], decoded[i])
|
||||
assertEquals(expected.l0, decoded.l0)
|
||||
assertEquals(expected.l1, decoded.l1)
|
||||
assertEquals(expected.l2, decoded.l2)
|
||||
assertEquals(expected.l3, decoded.l3)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -38,19 +38,19 @@ package com.vitorpamplona.quartz.utils.secp256k1
|
||||
* Benchmark confirmed: unfused path is equal or faster on HotSpot JVM 21.
|
||||
*/
|
||||
internal actual fun fieldMulReduce(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
U256.mulWide(w, a, b)
|
||||
FieldP.reduceWide(out, w)
|
||||
}
|
||||
|
||||
internal actual fun fieldSqrReduce(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
U256.sqrWide(w, a)
|
||||
FieldP.reduceWide(out, w)
|
||||
|
||||
+37
-188
@@ -24,233 +24,82 @@ import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Benchmark comparing LongArray(4) vs Fe4 (struct with 4 @JvmField Long fields)
|
||||
* for secp256k1 field element operations.
|
||||
* Field-level micro-benchmark for the Fe4-based secp256k1 implementation.
|
||||
*
|
||||
* The hypothesis: Fe4 eliminates array bounds checks (laload/lastore → getfield/putfield),
|
||||
* which should measurably improve performance on platforms where the JIT cannot reliably
|
||||
* eliminate bounds checks for parameter arrays (Android ART, Kotlin/Native LLVM).
|
||||
* Measures the core field operations using Fe4 struct types (no array bounds checks).
|
||||
* Compare against the pre-migration LongArray baseline to verify the performance gain.
|
||||
*
|
||||
* On JVM HotSpot C2, the difference may be smaller because C2 can profile array sizes
|
||||
* and eliminate constant-index bounds checks. But even on HotSpot, the Fe4 approach
|
||||
* avoids the array length field load + comparison that precedes each array access.
|
||||
* Pre-migration baseline (LongArray, HotSpot C2):
|
||||
* FieldP.mul: 40 ns/op
|
||||
* FieldP.sqr: 48 ns/op
|
||||
* FieldP.add: 9 ns/op
|
||||
* FieldP.sub: 10 ns/op
|
||||
*
|
||||
* Run with: ./gradlew :quartz:jvmTest --tests "*.Fe4Benchmark"
|
||||
*/
|
||||
class Fe4Benchmark {
|
||||
// Test vectors: secp256k1 generator point coordinates
|
||||
private val aBytes = hexToBytes("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798")
|
||||
private val bBytes = hexToBytes("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8")
|
||||
|
||||
// LongArray representations
|
||||
private val aArr = U256.fromBytes(aBytes)
|
||||
private val bArr = U256.fromBytes(bBytes)
|
||||
private val outArr = LongArray(4)
|
||||
private val wArr = LongArray(8)
|
||||
|
||||
// Fe4 representations (same values)
|
||||
private val aFe4 = Fe4(aArr[0], aArr[1], aArr[2], aArr[3])
|
||||
private val bFe4 = Fe4(bArr[0], bArr[1], bArr[2], bArr[3])
|
||||
private val outFe4 = Fe4()
|
||||
private val wFe8 = Wide8()
|
||||
private val a = U256.fromBytes(aBytes)
|
||||
private val b = U256.fromBytes(bBytes)
|
||||
private val out = Fe4()
|
||||
private val w = Wide8()
|
||||
|
||||
private data class BenchResult(
|
||||
val name: String,
|
||||
val arrayNanos: Long,
|
||||
val fe4Nanos: Long,
|
||||
val nanos: Long,
|
||||
val iterations: Int,
|
||||
) {
|
||||
val arrayNsPerOp get() = arrayNanos / iterations
|
||||
val fe4NsPerOp get() = fe4Nanos / iterations
|
||||
val speedup get() = arrayNanos.toDouble() / fe4Nanos.toDouble()
|
||||
|
||||
override fun toString(): String {
|
||||
val pct = ((speedup - 1.0) * 100).let { if (it >= 0) "+%.1f%%".format(it) else "%.1f%%".format(it) }
|
||||
return String.format(
|
||||
" %-20s LongArray: %,8d ns/op Fe4: %,8d ns/op %s",
|
||||
name,
|
||||
arrayNsPerOp,
|
||||
fe4NsPerOp,
|
||||
pct,
|
||||
)
|
||||
}
|
||||
val nsPerOp get() = nanos / iterations
|
||||
}
|
||||
|
||||
private inline fun bench(
|
||||
name: String,
|
||||
warmup: Int,
|
||||
iterations: Int,
|
||||
crossinline arrayOp: () -> Unit,
|
||||
crossinline fe4Op: () -> Unit,
|
||||
crossinline op: () -> Unit,
|
||||
): BenchResult {
|
||||
// Warmup both generously (C2 compiles at ~10K invocations)
|
||||
repeat(warmup) { arrayOp() }
|
||||
repeat(warmup) { fe4Op() }
|
||||
|
||||
// Run 3 rounds, alternating order, take best of each to reduce noise
|
||||
var bestArr = Long.MAX_VALUE
|
||||
var bestFe4 = Long.MAX_VALUE
|
||||
repeat(warmup) { op() }
|
||||
var best = Long.MAX_VALUE
|
||||
for (round in 0 until 3) {
|
||||
if (round % 2 == 0) {
|
||||
// LongArray first
|
||||
val arrStart = System.nanoTime()
|
||||
repeat(iterations) { arrayOp() }
|
||||
bestArr = minOf(bestArr, System.nanoTime() - arrStart)
|
||||
|
||||
val fe4Start = System.nanoTime()
|
||||
repeat(iterations) { fe4Op() }
|
||||
bestFe4 = minOf(bestFe4, System.nanoTime() - fe4Start)
|
||||
} else {
|
||||
// Fe4 first
|
||||
val fe4Start = System.nanoTime()
|
||||
repeat(iterations) { fe4Op() }
|
||||
bestFe4 = minOf(bestFe4, System.nanoTime() - fe4Start)
|
||||
|
||||
val arrStart = System.nanoTime()
|
||||
repeat(iterations) { arrayOp() }
|
||||
bestArr = minOf(bestArr, System.nanoTime() - arrStart)
|
||||
}
|
||||
val start = System.nanoTime()
|
||||
repeat(iterations) { op() }
|
||||
best = minOf(best, System.nanoTime() - start)
|
||||
}
|
||||
|
||||
return BenchResult(name, bestArr, bestFe4, iterations)
|
||||
return BenchResult(name, best, iterations)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun benchmarkFieldOps() {
|
||||
// Verify correctness first: both implementations must produce identical results
|
||||
verifySameResults()
|
||||
|
||||
val results = mutableListOf<BenchResult>()
|
||||
|
||||
// --- FieldP.mul (the hottest operation: ~1900 calls/verify) ---
|
||||
// Use out as input to next iteration to create data dependency chain
|
||||
aArr.copyInto(outArr)
|
||||
aFe4.copyFrom(Fe4(aArr[0], aArr[1], aArr[2], aArr[3]))
|
||||
outFe4.copyFrom(aFe4)
|
||||
results +=
|
||||
bench(
|
||||
"FieldP.mul",
|
||||
10000,
|
||||
200000,
|
||||
arrayOp = { FieldP.mul(outArr, outArr, bArr, wArr) },
|
||||
fe4Op = { Fe4FieldP.mul(outFe4, outFe4, bFe4, wFe8) },
|
||||
)
|
||||
out.copyFrom(a)
|
||||
results += bench("FieldP.mul", 10000, 200000) { FieldP.mul(out, out, b, w) }
|
||||
|
||||
// --- FieldP.sqr (second hottest: ~1900 calls/verify) ---
|
||||
aArr.copyInto(outArr)
|
||||
outFe4.copyFrom(aFe4)
|
||||
results +=
|
||||
bench(
|
||||
"FieldP.sqr",
|
||||
10000,
|
||||
200000,
|
||||
arrayOp = { FieldP.sqr(outArr, outArr, wArr) },
|
||||
fe4Op = { Fe4FieldP.sqr(outFe4, outFe4, wFe8) },
|
||||
)
|
||||
out.copyFrom(a)
|
||||
results += bench("FieldP.sqr", 10000, 200000) { FieldP.sqr(out, out, w) }
|
||||
|
||||
// --- FieldP.add (~750 calls/verify) ---
|
||||
aArr.copyInto(outArr)
|
||||
outFe4.copyFrom(aFe4)
|
||||
results +=
|
||||
bench(
|
||||
"FieldP.add",
|
||||
10000,
|
||||
500000,
|
||||
arrayOp = { FieldP.add(outArr, outArr, bArr) },
|
||||
fe4Op = { Fe4FieldP.add(outFe4, outFe4, bFe4) },
|
||||
)
|
||||
out.copyFrom(a)
|
||||
results += bench("FieldP.add", 10000, 500000) { FieldP.add(out, out, b) }
|
||||
|
||||
// --- FieldP.sub (~500 calls/verify) ---
|
||||
aArr.copyInto(outArr)
|
||||
outFe4.copyFrom(aFe4)
|
||||
results +=
|
||||
bench(
|
||||
"FieldP.sub",
|
||||
10000,
|
||||
500000,
|
||||
arrayOp = { FieldP.sub(outArr, outArr, bArr) },
|
||||
fe4Op = { Fe4FieldP.sub(outFe4, outFe4, bFe4) },
|
||||
)
|
||||
out.copyFrom(a)
|
||||
results += bench("FieldP.sub", 10000, 500000) { FieldP.sub(out, out, b) }
|
||||
|
||||
// --- U256.mulWide (raw wide multiply, no reduction) ---
|
||||
results +=
|
||||
bench(
|
||||
"U256.mulWide",
|
||||
10000,
|
||||
200000,
|
||||
arrayOp = { U256.mulWide(wArr, aArr, bArr) },
|
||||
fe4Op = { Fe4U256.mulWide(wFe8, aFe4, bFe4) },
|
||||
)
|
||||
results += bench("U256.mulWide", 10000, 200000) { U256.mulWide(w, a, b) }
|
||||
|
||||
// --- U256.sqrWide (raw wide square) ---
|
||||
results +=
|
||||
bench(
|
||||
"U256.sqrWide",
|
||||
10000,
|
||||
200000,
|
||||
arrayOp = { U256.sqrWide(wArr, aArr) },
|
||||
fe4Op = { Fe4U256.sqrWide(wFe8, aFe4) },
|
||||
)
|
||||
results += bench("U256.sqrWide", 10000, 200000) { U256.sqrWide(w, a) }
|
||||
|
||||
// Print results
|
||||
println()
|
||||
println("=".repeat(80))
|
||||
println("Fe4 vs LongArray Benchmark: JVM21/HotSpot C2")
|
||||
println("=".repeat(80))
|
||||
println(" Hypothesis: Fe4 (named fields) eliminates array bounds checks,")
|
||||
println(" producing faster code especially on ART/Native where JIT is weaker.")
|
||||
println("-".repeat(80))
|
||||
println("=".repeat(70))
|
||||
println("Fe4 Field Micro-Benchmarks: JVM21/HotSpot C2 (post-migration)")
|
||||
println("=".repeat(70))
|
||||
for (r in results) {
|
||||
println(r)
|
||||
println(" ${r.name.padEnd(20)} ${r.nsPerOp.toString().padStart(8)} ns/op")
|
||||
}
|
||||
println("=".repeat(80))
|
||||
println()
|
||||
println(" Bytecode analysis:")
|
||||
println(" LongArray access: aload + iconst + laload (3 insns + implicit bounds check)")
|
||||
println(" Fe4 field access: aload + getfield (2 insns, no check)")
|
||||
println()
|
||||
println(" U256.class: 150 laload/lastore operations (each bounds-checked)")
|
||||
println(" FieldP.class: 119 laload/lastore operations")
|
||||
println(" FieldMulFusedKt: 195 laload/lastore operations")
|
||||
println(" Total: 464 bounds checks in 3 core files")
|
||||
println()
|
||||
println("=".repeat(70))
|
||||
|
||||
// Prevent dead code elimination
|
||||
assertTrue(outArr[0] != Long.MIN_VALUE || outFe4.l0 != Long.MIN_VALUE)
|
||||
}
|
||||
|
||||
private fun verifySameResults() {
|
||||
// Test mul
|
||||
FieldP.mul(outArr, aArr, bArr, wArr)
|
||||
Fe4FieldP.mul(outFe4, aFe4, bFe4, wFe8)
|
||||
assertFe4EqualsArray("mul", outArr, outFe4)
|
||||
|
||||
// Test sqr
|
||||
FieldP.sqr(outArr, aArr, wArr)
|
||||
Fe4FieldP.sqr(outFe4, aFe4, wFe8)
|
||||
assertFe4EqualsArray("sqr", outArr, outFe4)
|
||||
|
||||
// Test add
|
||||
FieldP.add(outArr, aArr, bArr)
|
||||
Fe4FieldP.add(outFe4, aFe4, bFe4)
|
||||
assertFe4EqualsArray("add", outArr, outFe4)
|
||||
|
||||
// Test sub
|
||||
FieldP.sub(outArr, aArr, bArr)
|
||||
Fe4FieldP.sub(outFe4, aFe4, bFe4)
|
||||
assertFe4EqualsArray("sub", outArr, outFe4)
|
||||
}
|
||||
|
||||
private fun assertFe4EqualsArray(
|
||||
op: String,
|
||||
arr: LongArray,
|
||||
fe: Fe4,
|
||||
) {
|
||||
assertTrue(
|
||||
arr[0] == fe.l0 && arr[1] == fe.l1 && arr[2] == fe.l2 && arr[3] == fe.l3,
|
||||
"$op: LongArray[${arr[0]},${arr[1]},${arr[2]},${arr[3]}] != " +
|
||||
"Fe4[${fe.l0},${fe.l1},${fe.l2},${fe.l3}]",
|
||||
)
|
||||
assertTrue(out.l0 != Long.MIN_VALUE)
|
||||
}
|
||||
|
||||
private fun hexToBytes(hex: String): ByteArray {
|
||||
|
||||
+7
-7
@@ -220,35 +220,35 @@ class Secp256k1NativeBenchmark {
|
||||
// out of the loop (dead code elimination risk with constant inputs).
|
||||
val a = U256.fromBytes(hexToBytes("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"))
|
||||
val b = U256.fromBytes(hexToBytes("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8"))
|
||||
val out = LongArray(4)
|
||||
val w = LongArray(8)
|
||||
val out = Fe4()
|
||||
val w = Wide8()
|
||||
|
||||
val results = mutableListOf<BenchResult>()
|
||||
|
||||
// --- FieldP.mul (the hottest operation) ---
|
||||
// Uses `out` as both output and next input to create a data dependency chain.
|
||||
a.copyInto(out)
|
||||
out.copyFrom(a)
|
||||
results +=
|
||||
bench("FieldP.mul", 5000, 100000) {
|
||||
FieldP.mul(out, out, b, w)
|
||||
}
|
||||
|
||||
// --- FieldP.sqr ---
|
||||
a.copyInto(out)
|
||||
out.copyFrom(a)
|
||||
results +=
|
||||
bench("FieldP.sqr", 5000, 100000) {
|
||||
FieldP.sqr(out, out, w)
|
||||
}
|
||||
|
||||
// --- FieldP.add ---
|
||||
a.copyInto(out)
|
||||
out.copyFrom(a)
|
||||
results +=
|
||||
bench("FieldP.add", 5000, 500000) {
|
||||
FieldP.add(out, out, b)
|
||||
}
|
||||
|
||||
// --- FieldP.sub ---
|
||||
a.copyInto(out)
|
||||
out.copyFrom(a)
|
||||
results +=
|
||||
bench("FieldP.sub", 5000, 500000) {
|
||||
FieldP.sub(out, out, b)
|
||||
@@ -277,7 +277,7 @@ class Secp256k1NativeBenchmark {
|
||||
printResults("secp256k1 Field Micro-Benchmarks: Kotlin/Native (LLVM AOT) on linuxX64", results)
|
||||
|
||||
// Use sinks to prevent dead code elimination of the entire benchmark
|
||||
assertTrue(hiSink != Long.MIN_VALUE || ltSink != Long.MIN_VALUE || out[0] != Long.MIN_VALUE)
|
||||
assertTrue(hiSink != Long.MIN_VALUE || ltSink != Long.MIN_VALUE || out.l0 != Long.MIN_VALUE)
|
||||
}
|
||||
|
||||
private fun hexToBytes(hex: String): ByteArray {
|
||||
|
||||
+7
-7
@@ -25,18 +25,18 @@ package com.vitorpamplona.quartz.utils.secp256k1
|
||||
* Kotlin/Native compiles ahead-of-time, so inline + direct call is optimal.
|
||||
*/
|
||||
internal actual fun fieldMulReduce(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
b: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
fieldMulReduceFused(out, a, b, w)
|
||||
}
|
||||
|
||||
internal actual fun fieldSqrReduce(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
w: LongArray,
|
||||
out: Fe4,
|
||||
a: Fe4,
|
||||
w: Wide8,
|
||||
) {
|
||||
fieldSqrReduceFused(out, a, w)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user