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:
+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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user