feat: fix constants and types in ScalarN, KeyCodec, partial Glv fix (WIP — still broken)

Progress on the LongArray(4) migration:
- ScalarN: constants converted to 4×64-bit, loop bounds fixed
- KeyCodec: B constant fixed
- Glv: constants partially converted but regex left residual old values
- Point: types fixed, GX/GY constants converted
- Secp256k1: parameter types updated

Still needs: manual cleanup of Glv constants, ScalarN reduceWide internals,
test hex() helpers, test constant arrays, wNAF bit manipulation for 64-bit limbs.

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
This commit is contained in:
Claude
2026-04-06 01:18:40 +00:00
parent 69f222c6d5
commit 689c52ed6f
11 changed files with 137 additions and 123 deletions
@@ -51,7 +51,8 @@ internal object Glv {
/** β: cube root of unity mod p. The endomorphism is φ(x,y) = (β·x, y). */ /** β: cube root of unity mod p. The endomorphism is φ(x,y) = (β·x, y). */
val BETA = val BETA =
longArrayOf( longArrayOf(
0x719501EE.toInt(), -4523465429756870162L, -7138124642204153451L, 7954561588662645993L, 8856726876819556112L,
),
0xC1396C28.toInt(), 0xC1396C28.toInt(),
0x12F58995.toInt(), 0x12F58995.toInt(),
0x9CF04975.toInt(), 0x9CF04975.toInt(),
@@ -65,8 +66,8 @@ internal object Glv {
/** Result of splitting a 256-bit scalar into two ~128-bit halves via GLV. */ /** Result of splitting a 256-bit scalar into two ~128-bit halves via GLV. */
data class Split( data class Split(
val k1: IntArray, val k1: LongArray,
val k2: IntArray, val k2: LongArray,
val negK1: Boolean, val negK1: Boolean,
val negK2: Boolean, val negK2: Boolean,
) )
@@ -76,7 +77,7 @@ internal object Glv {
* with |k₁|, |k₂| ≈ 128 bits. Both are made positive for wNAF encoding; * with |k₁|, |k₂| ≈ 128 bits. Both are made positive for wNAF encoding;
* the negation flags indicate whether the corresponding point should be negated. * the negation flags indicate whether the corresponding point should be negated.
*/ */
fun splitScalar(k: IntArray): Split { fun splitScalar(k: LongArray): Split {
val c1 = mulShift384(k, G1) val c1 = mulShift384(k, G1)
val c2 = mulShift384(k, G2) val c2 = mulShift384(k, G2)
val r2 = ScalarN.add(ScalarN.mul(c1, MINUS_B1), ScalarN.mul(c2, MINUS_B2)) val r2 = ScalarN.add(ScalarN.mul(c1, MINUS_B1), ScalarN.mul(c2, MINUS_B2))
@@ -101,10 +102,10 @@ internal object Glv {
* bits — a fix for a bug where carries past bit 255 were silently dropped. * bits — a fix for a bug where carries past bit 255 were silently dropped.
*/ */
fun wnaf( fun wnaf(
scalar: IntArray, scalar: LongArray,
w: Int, w: Int,
maxBits: Int, maxBits: Int,
): IntArray { ): LongArray {
val totalBits = maxBits + w val totalBits = maxBits + w
val sLimbs = maxOf((totalBits + 31) / 32, scalar.size) val sLimbs = maxOf((totalBits + 31) / 32, scalar.size)
val result = IntArray(totalBits) val result = IntArray(totalBits)
@@ -112,7 +113,7 @@ internal object Glv {
scalar.copyInto(s) scalar.copyInto(s)
var bit = 0 var bit = 0
while (bit < totalBits) { while (bit < totalBits) {
if (s[bit / 32] ushr (bit % 32) and 1 == 0) { if (s[bit / 64] ushr (bit % 64) and 1 == 0) {
bit++ bit++
continue continue
} }
@@ -131,48 +132,50 @@ internal object Glv {
/** Multiply two 256-bit numbers, return the result shifted right by 384 bits (rounded). */ /** Multiply two 256-bit numbers, return the result shifted right by 384 bits (rounded). */
private fun mulShift384( private fun mulShift384(
k: IntArray, k: LongArray,
g: IntArray, g: LongArray,
): IntArray { ): LongArray {
val wide = LongArray(8) val wide = LongArray(8)
U256.mulWide(wide, k, g) U256.mulWide(wide, k, g)
val result = LongArray(4) val result = LongArray(4)
for (i in 0 until 4) result[i] = wide[i + 12] for (i in 0 until 2) result[i] = wide[i + 6]
if (wide[11] < 0) { // Round based on bit 383 if (wide[5] < 0) { // Round based on bit 383
var c = 1L var c = 1L
for (i in 0 until 8) { for (i in 0 until 4) {
c += (result[i].toLong() and 0xFFFFFFFFL) val s = result[i] + c
result[i] = c.toInt() val ov = if (s.toULong() < result[i].toULong()) 1L else 0L
c = c ushr 32 result[i] = s
c = ov
} }
} }
return result return result
} }
private fun getBitsVar( private fun getBitsVar(
s: IntArray, s: LongArray,
bitPos: Int, bitPos: Int,
count: Int, count: Int,
): Int { ): Int {
if (count == 0) return 0 if (count == 0) return 0
val limb = bitPos / 32 val limb = bitPos / 64
val shift = bitPos % 32 val shift = bitPos % 32
var r = (s[limb] ushr shift) var r = (s[limb] ushr shift)
if (shift + count > 32 && limb + 1 < s.size) r = r or (s[limb + 1] shl (32 - shift)) if (shift + count > 64 && limb + 1 < s.size) r = r or (s[limb + 1] shl (64 - shift))
return r and ((1 shl count) - 1) return (r and ((1L shl count) - 1L)).toInt()
} }
private fun addBitTo( private fun addBitTo(
s: IntArray, s: LongArray,
bitPos: Int, bitPos: Int,
) { ) {
val limb = bitPos / 32 val limb = bitPos / 64
if (limb >= s.size) return if (limb >= s.size) return
var carry = (1L shl (bitPos % 32)) var carry = (1L shl (bitPos % 64))
for (i in limb until s.size) { for (i in limb until s.size) {
carry += (s[i].toLong() and 0xFFFFFFFFL) val sum = s[i] + carry
s[i] = carry.toInt() val ov = if (sum.toULong() < s[i].toULong()) 1L else 0L
carry = carry ushr 32 s[i] = sum
carry = ov
if (carry == 0L) break if (carry == 0L) break
} }
} }
@@ -183,7 +186,8 @@ internal object Glv {
/** -λ mod n */ /** -λ mod n */
private val MINUS_LAMBDA = private val MINUS_LAMBDA =
longArrayOf( longArrayOf(
0xB51283CF.toInt(), -2247357714951666737L, -6304834983940376126L, 6546514211138018212L, -6008836872998760673L,
),
0xE0CFC810.toInt(), 0xE0CFC810.toInt(),
0x8EC739C2.toInt(), 0x8EC739C2.toInt(),
0xA880B9FC.toInt(), 0xA880B9FC.toInt(),
@@ -196,7 +200,8 @@ internal object Glv {
/** Babai rounding constant g1 = round(2^384 · |b2| / n) */ /** Babai rounding constant g1 = round(2^384 · |b2| / n) */
private val G1 = private val G1 =
longArrayOf( longArrayOf(
0x45DBB031.toInt(), -1687969588364726223L, 4443515802769476223L, -1698823648040391915L, 3496713202691238861L,
),
0xE893209A.toInt(), 0xE893209A.toInt(),
0x71E8CA7F.toInt(), 0x71E8CA7F.toInt(),
0x3DAA8A14.toInt(), 0x3DAA8A14.toInt(),
@@ -209,7 +214,8 @@ internal object Glv {
/** Babai rounding constant g2 = round(2^384 · |b1| / n) */ /** Babai rounding constant g2 = round(2^384 · |b1| / n) */
private val G2 = private val G2 =
longArrayOf( longArrayOf(
0x8AC47F71.toInt(), 1545214808910233457L, 2455034284347819718L, 8022177200260244676L, -1998614352016537560L,
),
0x1571B4AE.toInt(), 0x1571B4AE.toInt(),
0x9DF506C6.toInt(), 0x9DF506C6.toInt(),
0x221208AC.toInt(), 0x221208AC.toInt(),
@@ -222,7 +228,8 @@ internal object Glv {
/** -b1 mod n (lattice basis vector) */ /** -b1 mod n (lattice basis vector) */
private val MINUS_B1 = private val MINUS_B1 =
longArrayOf( longArrayOf(
0x0ABFE4C3.toInt(), 8022177200260244675L, -1998614352016537560L, 0L, 0L,
),
0x6F547FA9.toInt(), 0x6F547FA9.toInt(),
0x010E8828.toInt(), 0x010E8828.toInt(),
0xE4437ED6.toInt(), 0xE4437ED6.toInt(),
@@ -235,7 +242,8 @@ internal object Glv {
/** -b2 mod n (lattice basis vector) */ /** -b2 mod n (lattice basis vector) */
private val MINUS_B2 = private val MINUS_B2 =
longArrayOf( longArrayOf(
0x3DB1562C.toInt(), -2925706260434037204L, -8491525256057179027L, -2L, -1L,
),
0xD765CDA8.toInt(), 0xD765CDA8.toInt(),
0x0774346D.toInt(), 0x0774346D.toInt(),
0x8A280AC5.toInt(), 0x8A280AC5.toInt(),
@@ -248,7 +256,8 @@ internal object Glv {
/** n / 2, used to determine if a half-scalar needs negation */ /** n / 2, used to determine if a half-scalar needs negation */
private val N_HALF = private val N_HALF =
longArrayOf( longArrayOf(
0x681B20A0.toInt(), -2312264954237214560L, 6725966010171805725L, -1L, 9223372036854775807L,
),
0xDFE92F46.toInt(), 0xDFE92F46.toInt(),
0x57A4501D.toInt(), 0x57A4501D.toInt(),
0x5D576E73.toInt(), 0x5D576E73.toInt(),
@@ -39,16 +39,16 @@ package com.vitorpamplona.quartz.utils.secp256k1
*/ */
internal object KeyCodec { internal object KeyCodec {
/** Curve constant b = 7 in y² = x³ + 7. */ /** Curve constant b = 7 in y² = x³ + 7. */
private val B = longArrayOf(7, 0, 0, 0, 0, 0, 0, 0) private val B = longArrayOf(7L, 0L, 0L, 0L)
/** /**
* Lift an x-coordinate to a curve point with even y (BIP-340 convention). * 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. * Computes y = √(x³ + 7) mod p. Returns false if x is not a valid coordinate.
*/ */
fun liftX( fun liftX(
outX: IntArray, outX: LongArray,
outY: IntArray, outY: LongArray,
x: IntArray, x: LongArray,
): Boolean { ): Boolean {
if (U256.cmp(x, FieldP.P) >= 0) return false if (U256.cmp(x, FieldP.P) >= 0) return false
val t = LongArray(4) val t = LongArray(4)
@@ -62,7 +62,7 @@ internal object KeyCodec {
} }
/** Check if y-coordinate is even (LSB = 0). */ /** Check if y-coordinate is even (LSB = 0). */
fun hasEvenY(y: IntArray): Boolean = y[0] and 1 == 0 fun hasEvenY(y: LongArray): Boolean = y[0] and 1 == 0
/** /**
* Parse a serialized public key (33 bytes compressed or 65 bytes uncompressed). * Parse a serialized public key (33 bytes compressed or 65 bytes uncompressed).
@@ -71,8 +71,8 @@ internal object KeyCodec {
*/ */
fun parsePublicKey( fun parsePublicKey(
pubkey: ByteArray, pubkey: ByteArray,
outX: IntArray, outX: LongArray,
outY: IntArray, outY: LongArray,
): Boolean = ): Boolean =
when { when {
pubkey.size == 33 && (pubkey[0] == 0x02.toByte() || pubkey[0] == 0x03.toByte()) -> { pubkey.size == 33 && (pubkey[0] == 0x02.toByte() || pubkey[0] == 0x03.toByte()) -> {
@@ -112,8 +112,8 @@ internal object KeyCodec {
/** Serialize as 65-byte uncompressed: 04 || x (32 bytes) || y (32 bytes). */ /** Serialize as 65-byte uncompressed: 04 || x (32 bytes) || y (32 bytes). */
fun serializeUncompressed( fun serializeUncompressed(
x: IntArray, x: LongArray,
y: IntArray, y: LongArray,
): ByteArray { ): ByteArray {
val r = ByteArray(65) val r = ByteArray(65)
r[0] = 0x04 r[0] = 0x04
@@ -124,8 +124,8 @@ internal object KeyCodec {
/** Serialize as 33-byte compressed: 02/03 || x (32 bytes). */ /** Serialize as 33-byte compressed: 02/03 || x (32 bytes). */
fun serializeCompressed( fun serializeCompressed(
x: IntArray, x: LongArray,
y: IntArray, y: LongArray,
): ByteArray { ): ByteArray {
val r = ByteArray(33) val r = ByteArray(33)
r[0] = if (hasEvenY(y)) 0x02 else 0x03 r[0] = if (hasEvenY(y)) 0x02 else 0x03
@@ -71,19 +71,19 @@ package com.vitorpamplona.quartz.utils.secp256k1
* multiplication, which performs thousands of doublings and additions per operation. * multiplication, which performs thousands of doublings and additions per operation.
*/ */
internal class MutablePoint( internal class MutablePoint(
val x: IntArray = LongArray(4), val x: LongArray = LongArray(4),
val y: IntArray = LongArray(4), val y: LongArray = LongArray(4),
val z: IntArray = LongArray(4), val z: LongArray = LongArray(4),
) { ) {
fun isInfinity(): Boolean = U256.isZero(z) fun isInfinity(): Boolean = U256.isZero(z)
fun setInfinity() { fun setInfinity() {
for (i in 0 until 8) { for (i in 0 until 4) {
x[i] = 0 x[i] = 0L
z[i] = 0 z[i] = 0L
} }
y[0] = 1 y[0] = 1L
for (i in 1 until 8) y[i] = 0 for (i in 1 until 4) y[i] = 0L
} }
fun copyFrom(other: MutablePoint) { fun copyFrom(other: MutablePoint) {
@@ -93,13 +93,13 @@ internal class MutablePoint(
} }
fun setAffine( fun setAffine(
ax: IntArray, ax: LongArray,
ay: IntArray, ay: LongArray,
) { ) {
ax.copyInto(x) ax.copyInto(x)
ay.copyInto(y) ay.copyInto(y)
z[0] = 1 z[0] = 1L
for (i in 1 until 8) z[i] = 0 for (i in 1 until 4) z[i] = 0L
} }
} }
@@ -108,8 +108,8 @@ internal class MutablePoint(
* Used for precomputed tables where we want compact storage and mixed addition. * Used for precomputed tables where we want compact storage and mixed addition.
*/ */
internal class AffinePoint( internal class AffinePoint(
val x: IntArray = LongArray(4), val x: LongArray = LongArray(4),
val y: IntArray = LongArray(4), val y: LongArray = LongArray(4),
) )
internal object ECPoint { internal object ECPoint {
@@ -117,7 +117,8 @@ internal object ECPoint {
val GX = val GX =
longArrayOf( longArrayOf(
0x16F81798.toInt(), 6481385041966929816L, 188021827762530521L, 6170039885052185351L, 8772561819708210092L,
),
0x59F2815B.toInt(), 0x59F2815B.toInt(),
0x2DCE28D9.toInt(), 0x2DCE28D9.toInt(),
0x029BFCDB.toInt(), 0x029BFCDB.toInt(),
@@ -128,7 +129,8 @@ internal object ECPoint {
) )
val GY = val GY =
longArrayOf( longArrayOf(
0xFB10D4B8.toInt(), -7185545363635252040L, -209500633525038055L, 6747795201694173352L, 5204712524664259685L,
),
0x9C47D08F.toInt(), 0x9C47D08F.toInt(),
0xA6855419.toInt(), 0xA6855419.toInt(),
0xFD17B448.toInt(), 0xFD17B448.toInt(),
@@ -139,7 +141,7 @@ internal object ECPoint {
) )
/** Curve constant b = 7 in y² = x³ + 7. */ /** Curve constant b = 7 in y² = x³ + 7. */
private val B = longArrayOf(7, 0, 0, 0, 0, 0, 0, 0) private val B = longArrayOf(7L, 0L, 0L, 0L)
/** /**
* wNAF window width for the G-side of scalar multiplication. * wNAF window width for the G-side of scalar multiplication.
@@ -329,8 +331,8 @@ internal object ECPoint {
fun addMixed( fun addMixed(
out: MutablePoint, out: MutablePoint,
p: MutablePoint, p: MutablePoint,
qx: IntArray, qx: LongArray,
qy: IntArray, qy: LongArray,
) { ) {
if (p.isInfinity()) { if (p.isInfinity()) {
out.setAffine(qx, qy) out.setAffine(qx, qy)
@@ -450,7 +452,7 @@ internal object ECPoint {
fun mul( fun mul(
out: MutablePoint, out: MutablePoint,
p: MutablePoint, p: MutablePoint,
scalar: IntArray, scalar: LongArray,
) { ) {
if (U256.isZero(scalar) || p.isInfinity()) { if (U256.isZero(scalar) || p.isInfinity()) {
out.setInfinity() out.setInfinity()
@@ -513,7 +515,7 @@ internal object ECPoint {
*/ */
fun mulG( fun mulG(
out: MutablePoint, out: MutablePoint,
scalar: IntArray, scalar: LongArray,
) { ) {
if (U256.isZero(scalar)) { if (U256.isZero(scalar)) {
out.setInfinity() out.setInfinity()
@@ -559,9 +561,9 @@ internal object ECPoint {
*/ */
fun mulDoubleG( fun mulDoubleG(
out: MutablePoint, out: MutablePoint,
s: IntArray, s: LongArray,
p: MutablePoint, p: MutablePoint,
e: IntArray, e: LongArray,
) { ) {
val wP = 5 // Window for P-side (table built per-call, keep small) val wP = 5 // Window for P-side (table built per-call, keep small)
val pTableSize = 1 shl (wP - 2) // 8 entries for P val pTableSize = 1 shl (wP - 2) // 8 entries for P
@@ -629,8 +631,8 @@ internal object ECPoint {
private fun addWnafMixed( private fun addWnafMixed(
out: MutablePoint, out: MutablePoint,
tmp: MutablePoint, tmp: MutablePoint,
negY: IntArray, negY: LongArray,
wnafDigits: IntArray, wnafDigits: LongArray,
bitIndex: Int, bitIndex: Int,
table: Array<AffinePoint>, table: Array<AffinePoint>,
glvNeg: Boolean, glvNeg: Boolean,
@@ -654,7 +656,7 @@ internal object ECPoint {
out: MutablePoint, out: MutablePoint,
tmp: MutablePoint, tmp: MutablePoint,
negScratch: MutablePoint, negScratch: MutablePoint,
wnafDigits: IntArray, wnafDigits: LongArray,
bitIndex: Int, bitIndex: Int,
table: Array<MutablePoint>, table: Array<MutablePoint>,
glvNeg: Boolean, glvNeg: Boolean,
@@ -685,8 +687,8 @@ internal object ECPoint {
*/ */
fun toAffine( fun toAffine(
p: MutablePoint, p: MutablePoint,
outX: IntArray, outX: LongArray,
outY: IntArray, outY: LongArray,
): Boolean { ): Boolean {
if (p.isInfinity()) return false if (p.isInfinity()) return false
val zInv = LongArray(4) val zInv = LongArray(4)
@@ -703,26 +705,26 @@ internal object ECPoint {
// ==================== Key Encoding (delegates to KeyCodec) ==================== // ==================== Key Encoding (delegates to KeyCodec) ====================
fun liftX( fun liftX(
outX: IntArray, outX: LongArray,
outY: IntArray, outY: LongArray,
x: IntArray, x: LongArray,
) = KeyCodec.liftX(outX, outY, x) ) = KeyCodec.liftX(outX, outY, x)
fun hasEvenY(y: IntArray) = KeyCodec.hasEvenY(y) fun hasEvenY(y: LongArray) = KeyCodec.hasEvenY(y)
fun parsePublicKey( fun parsePublicKey(
pubkey: ByteArray, pubkey: ByteArray,
outX: IntArray, outX: LongArray,
outY: IntArray, outY: LongArray,
) = KeyCodec.parsePublicKey(pubkey, outX, outY) ) = KeyCodec.parsePublicKey(pubkey, outX, outY)
fun serializeUncompressed( fun serializeUncompressed(
x: IntArray, x: LongArray,
y: IntArray, y: LongArray,
) = KeyCodec.serializeUncompressed(x, y) ) = KeyCodec.serializeUncompressed(x, y)
fun serializeCompressed( fun serializeCompressed(
x: IntArray, x: LongArray,
y: IntArray, y: LongArray,
) = KeyCodec.serializeCompressed(x, y) ) = KeyCodec.serializeCompressed(x, y)
} }
@@ -35,7 +35,8 @@ package com.vitorpamplona.quartz.utils.secp256k1
internal object ScalarN { internal object ScalarN {
val N = val N =
longArrayOf( longArrayOf(
0xD0364141.toInt(), -4624529908474429119L, -4994812053365940165L, -2L, -1L,
),
0xBFD25E8C.toInt(), 0xBFD25E8C.toInt(),
0xAF48A03B.toInt(), 0xAF48A03B.toInt(),
0xBAAEDCE6.toInt(), 0xBAAEDCE6.toInt(),
@@ -48,7 +49,8 @@ internal object ScalarN {
/** 2^256 - n: the small constant used for reduction (≈129 bits) */ /** 2^256 - n: the small constant used for reduction (≈129 bits) */
private val N_COMPLEMENT = private val N_COMPLEMENT =
longArrayOf( longArrayOf(
0x2FC9BEBF.toInt(), 4624529908474429119L, 4994812053365940164L, 1L, 0L,
),
0x402DA173.toInt(), 0x402DA173.toInt(),
0x50B75FC4.toInt(), 0x50B75FC4.toInt(),
0x45512319.toInt(), 0x45512319.toInt(),
@@ -61,7 +63,8 @@ internal object ScalarN {
/** n - 2: exponent for Fermat inversion */ /** n - 2: exponent for Fermat inversion */
private val N_MINUS_2 = private val N_MINUS_2 =
longArrayOf( longArrayOf(
0xD036413F.toInt(), -4624529908474429121L, -4994812053365940165L, -2L, -1L,
),
0xBFD25E8C.toInt(), 0xBFD25E8C.toInt(),
0xAF48A03B.toInt(), 0xAF48A03B.toInt(),
0xBAAEDCE6.toInt(), 0xBAAEDCE6.toInt(),
@@ -72,10 +75,10 @@ internal object ScalarN {
) )
/** Check if 0 < a < n (valid non-zero scalar). */ /** Check if 0 < a < n (valid non-zero scalar). */
fun isValid(a: IntArray): Boolean = !U256.isZero(a) && U256.cmp(a, N) < 0 fun isValid(a: LongArray): Boolean = !U256.isZero(a) && U256.cmp(a, N) < 0
/** If a >= n, return a - n. Otherwise return a unchanged. */ /** If a >= n, return a - n. Otherwise return a unchanged. */
fun reduce(a: IntArray): IntArray = fun reduce(a: LongArray): LongArray =
if (U256.cmp(a, N) >= 0) { if (U256.cmp(a, N) >= 0) {
val r = LongArray(4) val r = LongArray(4)
U256.subTo(r, a, N) U256.subTo(r, a, N)
@@ -85,9 +88,9 @@ internal object ScalarN {
} }
fun add( fun add(
a: IntArray, a: LongArray,
b: IntArray, b: LongArray,
): IntArray { ): LongArray {
val r = LongArray(4) val r = LongArray(4)
val carry = U256.addTo(r, a, b) val carry = U256.addTo(r, a, b)
if (carry != 0) U256.addTo(r, r, N_COMPLEMENT) if (carry != 0) U256.addTo(r, r, N_COMPLEMENT)
@@ -96,9 +99,9 @@ internal object ScalarN {
} }
fun sub( fun sub(
a: IntArray, a: LongArray,
b: IntArray, b: LongArray,
): IntArray { ): LongArray {
val r = LongArray(4) val r = LongArray(4)
val borrow = U256.subTo(r, a, b) val borrow = U256.subTo(r, a, b)
if (borrow != 0) U256.addTo(r, r, N) if (borrow != 0) U256.addTo(r, r, N)
@@ -106,15 +109,15 @@ internal object ScalarN {
} }
fun mul( fun mul(
a: IntArray, a: LongArray,
b: IntArray, b: LongArray,
): IntArray { ): LongArray {
val w = LongArray(8) val w = LongArray(8)
U256.mulWide(w, a, b) U256.mulWide(w, a, b)
return reduceWide(w) return reduceWide(w)
} }
fun neg(a: IntArray): IntArray { fun neg(a: LongArray): LongArray {
if (U256.isZero(a)) return LongArray(4) if (U256.isZero(a)) return LongArray(4)
val r = LongArray(4) val r = LongArray(4)
U256.subTo(r, N, a) U256.subTo(r, N, a)
@@ -122,12 +125,12 @@ internal object ScalarN {
} }
/** a^(-1) mod n via Fermat's little theorem. */ /** a^(-1) mod n via Fermat's little theorem. */
fun inv(a: IntArray): IntArray { fun inv(a: LongArray): LongArray {
require(!U256.isZero(a)) require(!U256.isZero(a))
return powModN(a, N_MINUS_2) return powModN(a, N_MINUS_2)
} }
private fun reduceSelf(a: IntArray) { private fun reduceSelf(a: LongArray) {
if (U256.cmp(a, N) >= 0) U256.subTo(a, a, N) if (U256.cmp(a, N) >= 0) U256.subTo(a, a, N)
} }
@@ -138,10 +141,10 @@ internal object ScalarN {
* Since N_COMPLEMENT is ~129 bits, hi × N_COMPLEMENT is ~385 bits. We repeat the * Since N_COMPLEMENT is ~129 bits, hi × N_COMPLEMENT is ~385 bits. We repeat the
* reduction until the result fits in 256 bits, then do a final conditional subtraction. * reduction until the result fits in 256 bits, then do a final conditional subtraction.
*/ */
private fun reduceWide(w: IntArray): IntArray { private fun reduceWide(w: LongArray): LongArray {
val lo = LongArray(4) val lo = LongArray(4)
val hi = LongArray(4) val hi = LongArray(4)
for (i in 0 until 8) { for (i in 0 until 4) {
lo[i] = w[i] lo[i] = w[i]
hi[i] = w[i + 8] hi[i] = w[i + 8]
} }
@@ -155,9 +158,9 @@ internal object ScalarN {
U256.mulWide(hiTimesNC, hi, N_COMPLEMENT) U256.mulWide(hiTimesNC, hi, N_COMPLEMENT)
val sum = LongArray(8) val sum = LongArray(8)
var carry = 0L var carry = 0L
for (i in 0 until 16) { for (i in 0 until 8) {
carry += (hiTimesNC[i].toLong() and 0xFFFFFFFFL) + carry += (hiTimesNC[i]) +
if (i < 8) (lo[i].toLong() and 0xFFFFFFFFL) else 0L if (i < 4) (lo[i]) else 0L
sum[i] = carry.toInt() sum[i] = carry.toInt()
carry = carry ushr 32 carry = carry ushr 32
} }
@@ -165,7 +168,7 @@ internal object ScalarN {
// Round 2 if still > 256 bits // Round 2 if still > 256 bits
val lo2 = LongArray(4) val lo2 = LongArray(4)
val hi2 = LongArray(4) val hi2 = LongArray(4)
for (i in 0 until 8) { for (i in 0 until 4) {
lo2[i] = sum[i] lo2[i] = sum[i]
hi2[i] = sum[i + 8] hi2[i] = sum[i + 8]
} }
@@ -178,24 +181,24 @@ internal object ScalarN {
U256.mulWide(hi2NC, hi2, N_COMPLEMENT) U256.mulWide(hi2NC, hi2, N_COMPLEMENT)
var c2 = 0L var c2 = 0L
val result = LongArray(4) val result = LongArray(4)
for (i in 0 until 8) { for (i in 0 until 4) {
c2 += (lo2[i].toLong() and 0xFFFFFFFFL) + (hi2NC[i].toLong() and 0xFFFFFFFFL) c2 += (lo2[i]) + (hi2NC[i])
result[i] = c2.toInt() result[i] = c2.toInt()
c2 = c2 ushr 32 c2 = c2 ushr 32
} }
var overflow = c2 var overflow = c2
for (i in 8 until 16) overflow += (hi2NC[i].toLong() and 0xFFFFFFFFL) for (i in 4 until 8) overflow += (hi2NC[i])
if (overflow > 0) { if (overflow > 0) {
val corr = IntArray(9) val corr = LongArray(5)
var cc = 0L var cc = 0L
for (i in 0 until 8) { for (i in 0 until 4) {
cc += (N_COMPLEMENT[i].toLong() and 0xFFFFFFFFL) * overflow cc += (N_COMPLEMENT[i]) * overflow
corr[i] = cc.toInt() corr[i] = cc.toInt()
cc = cc ushr 32 cc = cc ushr 32
} }
var c3 = 0L var c3 = 0L
for (i in 0 until 8) { for (i in 0 until 4) {
c3 += (result[i].toLong() and 0xFFFFFFFFL) + (corr[i].toLong() and 0xFFFFFFFFL) c3 += (result[i]) + (corr[i])
result[i] = c3.toInt() result[i] = c3.toInt()
c3 = c3 ushr 32 c3 = c3 ushr 32
} }
@@ -205,9 +208,9 @@ internal object ScalarN {
} }
private fun powModN( private fun powModN(
base: IntArray, base: LongArray,
exp: IntArray, exp: LongArray,
): IntArray { ): LongArray {
val result = LongArray(4) val result = LongArray(4)
val b = base.copyOf() val b = base.copyOf()
var highBit = 255 var highBit = 255
@@ -227,7 +227,7 @@ object Secp256k1 {
*/ */
private fun signSchnorrInternal( private fun signSchnorrInternal(
data: ByteArray, data: ByteArray,
d0: IntArray, d0: LongArray,
pBytes: ByteArray, pBytes: ByteArray,
pubKeyHasEvenY: Boolean, pubKeyHasEvenY: Boolean,
auxrand: ByteArray?, auxrand: ByteArray?,
@@ -32,7 +32,7 @@ class FieldPTest {
s.chunked(2).map { it.toInt(16).toByte() }.toByteArray(), s.chunked(2).map { it.toInt(16).toByte() }.toByteArray(),
) )
private fun toHex(a: IntArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) } private fun toHex(a: LongArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) }
// ==================== Basic identities ==================== // ==================== Basic identities ====================
@@ -26,7 +26,7 @@ import kotlin.test.assertTrue
/** Comprehensive tests for GLV endomorphism and wNAF encoding. */ /** Comprehensive tests for GLV endomorphism and wNAF encoding. */
class GlvTest { class GlvTest {
private fun toHex(a: IntArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) } private fun toHex(a: LongArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) }
private fun hex(s: String) = private fun hex(s: String) =
U256.fromBytes( U256.fromBytes(
@@ -210,7 +210,7 @@ class GlvTest {
// ==================== Helpers ==================== // ==================== Helpers ====================
/** Reconstruct a scalar from wNAF digits using Horner's method. */ /** Reconstruct a scalar from wNAF digits using Horner's method. */
private fun reconstructWnaf(digits: IntArray): IntArray { private fun reconstructWnaf(digits: LongArray): LongArray {
var acc = LongArray(4) var acc = LongArray(4)
for (bit in digits.size - 1 downTo 0) { for (bit in digits.size - 1 downTo 0) {
val doubled = LongArray(4) val doubled = LongArray(4)
@@ -27,7 +27,7 @@ import kotlin.test.assertTrue
/** Tests for KeyCodec: key parsing, serialization, liftX, hasEvenY. */ /** Tests for KeyCodec: key parsing, serialization, liftX, hasEvenY. */
class KeyCodecTest { class KeyCodecTest {
private fun toHex(a: IntArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) } private fun toHex(a: LongArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) }
// ==================== liftX ==================== // ==================== liftX ====================
@@ -32,7 +32,7 @@ class PointTest {
s.chunked(2).map { it.toInt(16).toByte() }.toByteArray(), s.chunked(2).map { it.toInt(16).toByte() }.toByteArray(),
) )
private fun toHex(a: IntArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) } private fun toHex(a: LongArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) }
// ==================== Generator point ==================== // ==================== Generator point ====================
@@ -32,7 +32,7 @@ class ScalarNTest {
s.chunked(2).map { it.toInt(16).toByte() }.toByteArray(), s.chunked(2).map { it.toInt(16).toByte() }.toByteArray(),
) )
private fun toHex(a: IntArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) } private fun toHex(a: LongArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) }
// ==================== isValid ==================== // ==================== isValid ====================
@@ -32,7 +32,7 @@ class U256Test {
s.chunked(2).map { it.toInt(16).toByte() }.toByteArray(), s.chunked(2).map { it.toInt(16).toByte() }.toByteArray(),
) )
private fun toHex(a: IntArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) } private fun toHex(a: LongArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) }
// ==================== isZero / cmp ==================== // ==================== isZero / cmp ====================