test: add comprehensive unit tests for U256, FieldP, ScalarN, ECPoint
Tests each layer of the secp256k1 implementation independently, not just through the public Secp256k1 API. This catches bugs in the foundations that could silently produce wrong results for specific input values. U256Test (22 tests): - isZero: zero, non-zero, high-bit cases - cmp: equal, less-than, greater-than - addTo/subTo: simple, carry/borrow, overflow/underflow - mulWide: small values, large values, sqrWide consistency - sqrWide: matches mulWide for arbitrary and max values - fromBytes/toBytes: round-trip, zero encoding - getNibble: low nibbles, high nibbles - testBit: low bits, high bits - xorTo: basic XOR FieldPTest (22 tests): - Identities: add zero, sub self, mul one, neg twice - Reduction: add near p, overflow past p, underflow past 0 - Commutativity, distributivity of mul - sqr matches mul(a,a) - inv: a * a^(-1) = 1, inv(1) = 1, inv(p-1) = p-1 - half: even values, odd values, half-then-double round-trip - sqrt: square root of perfect square, non-residue rejection, generator point - reduceWide: (p-1)² = 1 mod p - In-place operations: add, sqr into output arrays ScalarNTest (18 tests): - isValid: normal, zero, n, n-1 - Identities: add zero, sub self, add/sub round-trip, neg twice, add neg - Commutativity, distributivity of mul - inv: a * a^(-1) = 1 - Edge cases: (n-1)+1=0, (n-1)+2=1, (n-1)²=1 - neg(0)=0, reduce(n)=0, reduce(small)=unchanged PointTest (22 tests): - Generator on curve: y² = x³ + 7 - doublePoint: matches 2·G, in-place, infinity - addPoints: G+G=2G, infinity identity, inverse→infinity - addMixed: matches full addition, infinity input - mulG: by 1, by 0 (infinity), by n (infinity), matches generic mul - mulDoubleG: separate vs combined computation - liftX: generator, invalid x - Serialization: compress/decompress round-trip, uncompressed round-trip, invalid key rejection Total: 122 tests (86 new + 36 existing BIP-340/ACINQ vectors) https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/** Tests for field arithmetic modulo p. */
|
||||
class FieldPTest {
|
||||
private fun hex(s: String) =
|
||||
U256.fromBytes(
|
||||
s.chunked(2).map { it.toInt(16).toByte() }.toByteArray(),
|
||||
)
|
||||
|
||||
private fun toHex(a: IntArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) }
|
||||
|
||||
// ==================== Basic identities ====================
|
||||
|
||||
@Test
|
||||
fun addZeroIdentity() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val zero = IntArray(8)
|
||||
assertEquals(toHex(a), toHex(FieldP.add(a, zero)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subSelfIsZero() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val result = FieldP.sub(a, a)
|
||||
assertTrue(U256.isZero(result))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addThenSubRoundTrips() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val b = hex("3982f19bef1615bccfbb05e321c10e1d4cba3df0e841c2e41eeb6016347653c3")
|
||||
val sum = FieldP.add(a, b)
|
||||
val back = FieldP.sub(sum, b)
|
||||
assertEquals(toHex(a), toHex(back))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mulOneIdentity() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val one = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
assertEquals(toHex(a), toHex(FieldP.mul(a, one)))
|
||||
}
|
||||
|
||||
// ==================== Reduction near p ====================
|
||||
|
||||
@Test
|
||||
fun addNearP() {
|
||||
// (p - 1) + 1 = p ≡ 0 (mod p)
|
||||
val pMinus1 = hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e")
|
||||
val one = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
val result = FieldP.add(pMinus1, one)
|
||||
assertTrue(U256.isZero(result))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addNearPOverflow() {
|
||||
// (p - 1) + (p - 1) = 2p - 2 ≡ p - 2 (mod p)
|
||||
val pMinus1 = hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e")
|
||||
val result = FieldP.add(pMinus1, pMinus1)
|
||||
val expected = hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2d") // p-2
|
||||
assertEquals(toHex(expected), toHex(result))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subUnderflow() {
|
||||
// 0 - 1 ≡ p - 1 (mod p)
|
||||
val zero = IntArray(8)
|
||||
val one = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
val result = FieldP.sub(zero, one)
|
||||
val expected = hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e") // p-1
|
||||
assertEquals(toHex(expected), toHex(result))
|
||||
}
|
||||
|
||||
// ==================== Negation ====================
|
||||
|
||||
@Test
|
||||
fun negTwiceIsIdentity() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
assertEquals(toHex(a), toHex(FieldP.neg(FieldP.neg(a))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun negZeroIsZero() {
|
||||
assertTrue(U256.isZero(FieldP.neg(IntArray(8))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addNegIsZero() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val result = FieldP.add(a, FieldP.neg(a))
|
||||
assertTrue(U256.isZero(result))
|
||||
}
|
||||
|
||||
// ==================== Multiplication ====================
|
||||
|
||||
@Test
|
||||
fun mulCommutative() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val b = hex("3982f19bef1615bccfbb05e321c10e1d4cba3df0e841c2e41eeb6016347653c3")
|
||||
assertEquals(toHex(FieldP.mul(a, b)), toHex(FieldP.mul(b, a)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sqrMatchesMul() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
assertEquals(toHex(FieldP.mul(a, a)), toHex(FieldP.sqr(a)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mulDistributive() {
|
||||
// a * (b + c) = a*b + a*c
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val b = hex("3982f19bef1615bccfbb05e321c10e1d4cba3df0e841c2e41eeb6016347653c3")
|
||||
val c = hex("0000000000000000000000000000000000000000000000000000000000000007")
|
||||
val lhs = FieldP.mul(a, FieldP.add(b, c))
|
||||
val rhs = FieldP.add(FieldP.mul(a, b), FieldP.mul(a, c))
|
||||
assertEquals(toHex(lhs), toHex(rhs))
|
||||
}
|
||||
|
||||
// ==================== Inversion ====================
|
||||
|
||||
@Test
|
||||
fun invMulIsOne() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val aInv = FieldP.inv(a)
|
||||
val product = FieldP.mul(a, aInv)
|
||||
val one = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
assertEquals(toHex(one), toHex(product))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invOfOne() {
|
||||
val one = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
assertEquals(toHex(one), toHex(FieldP.inv(one)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invOfPMinus1() {
|
||||
// (p-1)^(-1) = p-1 because (p-1)^2 = 1 mod p
|
||||
val pMinus1 = hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e")
|
||||
assertEquals(toHex(pMinus1), toHex(FieldP.inv(pMinus1)))
|
||||
}
|
||||
|
||||
// ==================== Half ====================
|
||||
|
||||
@Test
|
||||
fun halfOfEven() {
|
||||
val out = IntArray(8)
|
||||
val four = intArrayOf(4, 0, 0, 0, 0, 0, 0, 0)
|
||||
FieldP.half(out, four)
|
||||
assertEquals(2, out[0])
|
||||
for (i in 1 until 8) assertEquals(0, out[i])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun halfOfOdd() {
|
||||
// half(1) = (1 + p) / 2 = (p + 1) / 2
|
||||
val out = IntArray(8)
|
||||
val one = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
FieldP.half(out, one)
|
||||
// Verify: 2 * half(1) = 1 mod p
|
||||
val doubled = FieldP.add(out, out)
|
||||
assertEquals(1, doubled[0])
|
||||
for (i in 1 until 8) assertEquals(0, doubled[i])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun halfThenDoubleRoundTrips() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val out = IntArray(8)
|
||||
FieldP.half(out, a)
|
||||
val doubled = FieldP.add(out, out)
|
||||
assertEquals(toHex(a), toHex(doubled))
|
||||
}
|
||||
|
||||
// ==================== Square root ====================
|
||||
|
||||
@Test
|
||||
fun sqrtOfSquare() {
|
||||
// sqrt(a²) should return a or p-a
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val aSq = FieldP.sqr(a)
|
||||
val root = FieldP.sqrt(aSq)!!
|
||||
// root² should equal a²
|
||||
assertEquals(toHex(aSq), toHex(FieldP.sqr(root)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sqrtOfNonResidue() {
|
||||
// 3 is not a quadratic residue mod p (for secp256k1's p)
|
||||
val three = intArrayOf(3, 0, 0, 0, 0, 0, 0, 0)
|
||||
assertNull(FieldP.sqrt(three))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sqrtOfSecp256k1Generator() {
|
||||
// G.y² = G.x³ + 7. sqrt(G.x³ + 7) should give G.y or -G.y
|
||||
val gx = ECPoint.GX
|
||||
val gy = ECPoint.GY
|
||||
val x3 = FieldP.mul(FieldP.sqr(gx), gx)
|
||||
val y2 = FieldP.add(x3, intArrayOf(7, 0, 0, 0, 0, 0, 0, 0))
|
||||
val root = FieldP.sqrt(y2)!!
|
||||
// root should be gy or -gy
|
||||
val isGy = U256.cmp(root, gy) == 0
|
||||
val isNegGy = U256.cmp(root, FieldP.neg(gy)) == 0
|
||||
assertTrue(isGy || isNegGy)
|
||||
}
|
||||
|
||||
// ==================== Reduction edge cases ====================
|
||||
|
||||
@Test
|
||||
fun reduceWideWithMaxValues() {
|
||||
// Multiply two values near p and verify result is < p
|
||||
val pMinus1 = hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e")
|
||||
val result = FieldP.mul(pMinus1, pMinus1)
|
||||
assertTrue(U256.cmp(result, FieldP.P) < 0, "Result should be < p")
|
||||
// (p-1)² ≡ 1 (mod p)
|
||||
val one = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
assertEquals(toHex(one), toHex(result))
|
||||
}
|
||||
|
||||
// ==================== In-place operations ====================
|
||||
|
||||
@Test
|
||||
fun inPlaceAdd() {
|
||||
val a = hex("0000000000000000000000000000000000000000000000000000000000000005")
|
||||
val b = hex("0000000000000000000000000000000000000000000000000000000000000003")
|
||||
val out = IntArray(8)
|
||||
FieldP.add(out, a, b)
|
||||
assertEquals(8, out[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun inPlaceSqr() {
|
||||
val a = hex("0000000000000000000000000000000000000000000000000000000000000005")
|
||||
val out = IntArray(8)
|
||||
FieldP.sqr(out, a)
|
||||
assertEquals(25, out[0]) // 5² = 25
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/** Tests for elliptic curve point operations. */
|
||||
class PointTest {
|
||||
private fun hex(s: String) =
|
||||
U256.fromBytes(
|
||||
s.chunked(2).map { it.toInt(16).toByte() }.toByteArray(),
|
||||
)
|
||||
|
||||
private fun toHex(a: IntArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) }
|
||||
|
||||
// ==================== Generator point ====================
|
||||
|
||||
@Test
|
||||
fun generatorIsOnCurve() {
|
||||
// y² = x³ + 7
|
||||
val x3 = FieldP.mul(FieldP.sqr(ECPoint.GX), ECPoint.GX)
|
||||
val y2expected = FieldP.add(x3, intArrayOf(7, 0, 0, 0, 0, 0, 0, 0))
|
||||
val y2actual = FieldP.sqr(ECPoint.GY)
|
||||
assertEquals(toHex(y2expected), toHex(y2actual))
|
||||
}
|
||||
|
||||
// ==================== Point doubling ====================
|
||||
|
||||
@Test
|
||||
fun doubleGMatchesTwoG() {
|
||||
// 2·G via doubling
|
||||
val p = MutablePoint()
|
||||
p.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
val doubled = MutablePoint()
|
||||
ECPoint.doublePoint(doubled, p)
|
||||
val dx = IntArray(8)
|
||||
val dy = IntArray(8)
|
||||
ECPoint.toAffine(doubled, dx, dy)
|
||||
|
||||
// 2·G via scalar multiplication
|
||||
val two = intArrayOf(2, 0, 0, 0, 0, 0, 0, 0)
|
||||
val mulResult = MutablePoint()
|
||||
ECPoint.mulG(mulResult, two)
|
||||
val mx = IntArray(8)
|
||||
val my = IntArray(8)
|
||||
ECPoint.toAffine(mulResult, mx, my)
|
||||
|
||||
assertEquals(toHex(mx), toHex(dx))
|
||||
assertEquals(toHex(my), toHex(dy))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doubleInPlace() {
|
||||
// doublePoint(out, out) should work correctly
|
||||
val p = MutablePoint()
|
||||
p.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
ECPoint.doublePoint(p, p)
|
||||
val x = IntArray(8)
|
||||
val y = IntArray(8)
|
||||
ECPoint.toAffine(p, x, y)
|
||||
|
||||
val two = intArrayOf(2, 0, 0, 0, 0, 0, 0, 0)
|
||||
val expected = MutablePoint()
|
||||
ECPoint.mulG(expected, two)
|
||||
val ex = IntArray(8)
|
||||
val ey = IntArray(8)
|
||||
ECPoint.toAffine(expected, ex, ey)
|
||||
|
||||
assertEquals(toHex(ex), toHex(x))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doubleInfinityIsInfinity() {
|
||||
val inf = MutablePoint()
|
||||
inf.setInfinity()
|
||||
val result = MutablePoint()
|
||||
ECPoint.doublePoint(result, inf)
|
||||
assertTrue(result.isInfinity())
|
||||
}
|
||||
|
||||
// ==================== Point addition ====================
|
||||
|
||||
@Test
|
||||
fun addGPlusGEqualsDoubleG() {
|
||||
val g = MutablePoint()
|
||||
g.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
val sum = MutablePoint()
|
||||
ECPoint.addPoints(sum, g, g)
|
||||
val sx = IntArray(8)
|
||||
val sy = IntArray(8)
|
||||
ECPoint.toAffine(sum, sx, sy)
|
||||
|
||||
val doubled = MutablePoint()
|
||||
ECPoint.doublePoint(doubled, g)
|
||||
val dx = IntArray(8)
|
||||
val dy = IntArray(8)
|
||||
ECPoint.toAffine(doubled, dx, dy)
|
||||
|
||||
assertEquals(toHex(dx), toHex(sx))
|
||||
assertEquals(toHex(dy), toHex(sy))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addInfinityIdentity() {
|
||||
val g = MutablePoint()
|
||||
g.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
val inf = MutablePoint()
|
||||
inf.setInfinity()
|
||||
|
||||
val result = MutablePoint()
|
||||
ECPoint.addPoints(result, g, inf)
|
||||
val rx = IntArray(8)
|
||||
val ry = IntArray(8)
|
||||
ECPoint.toAffine(result, rx, ry)
|
||||
assertEquals(toHex(ECPoint.GX), toHex(rx))
|
||||
|
||||
val result2 = MutablePoint()
|
||||
ECPoint.addPoints(result2, inf, g)
|
||||
val r2x = IntArray(8)
|
||||
val r2y = IntArray(8)
|
||||
ECPoint.toAffine(result2, r2x, r2y)
|
||||
assertEquals(toHex(ECPoint.GX), toHex(r2x))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addInverseIsInfinity() {
|
||||
// G + (-G) = infinity
|
||||
val g = MutablePoint()
|
||||
g.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
val negG = MutablePoint()
|
||||
negG.setAffine(ECPoint.GX, FieldP.neg(ECPoint.GY))
|
||||
|
||||
val result = MutablePoint()
|
||||
ECPoint.addPoints(result, g, negG)
|
||||
assertTrue(result.isInfinity())
|
||||
}
|
||||
|
||||
// ==================== Mixed addition ====================
|
||||
|
||||
@Test
|
||||
fun addMixedMatchesFull() {
|
||||
// addMixed should produce the same result as addPoints when q is affine
|
||||
val three = intArrayOf(3, 0, 0, 0, 0, 0, 0, 0)
|
||||
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 = IntArray(8)
|
||||
val my = IntArray(8)
|
||||
ECPoint.toAffine(mixed, mx, my)
|
||||
|
||||
// Add G as Jacobian
|
||||
val gJac = MutablePoint()
|
||||
gJac.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
val full = MutablePoint()
|
||||
ECPoint.addPoints(full, p, gJac)
|
||||
val fx = IntArray(8)
|
||||
val fy = IntArray(8)
|
||||
ECPoint.toAffine(full, fx, fy)
|
||||
|
||||
assertEquals(toHex(fx), toHex(mx))
|
||||
assertEquals(toHex(fy), toHex(my))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addMixedInfinityInput() {
|
||||
val inf = MutablePoint()
|
||||
inf.setInfinity()
|
||||
val result = MutablePoint()
|
||||
ECPoint.addMixed(result, inf, ECPoint.GX, ECPoint.GY)
|
||||
val rx = IntArray(8)
|
||||
val ry = IntArray(8)
|
||||
ECPoint.toAffine(result, rx, ry)
|
||||
assertEquals(toHex(ECPoint.GX), toHex(rx))
|
||||
}
|
||||
|
||||
// ==================== Scalar multiplication ====================
|
||||
|
||||
@Test
|
||||
fun mulGByOne() {
|
||||
val one = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
val result = MutablePoint()
|
||||
ECPoint.mulG(result, one)
|
||||
val rx = IntArray(8)
|
||||
val ry = IntArray(8)
|
||||
ECPoint.toAffine(result, rx, ry)
|
||||
assertEquals(toHex(ECPoint.GX), toHex(rx))
|
||||
assertEquals(toHex(ECPoint.GY), toHex(ry))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mulGByZeroIsInfinity() {
|
||||
val zero = IntArray(8)
|
||||
val result = MutablePoint()
|
||||
ECPoint.mulG(result, zero)
|
||||
assertTrue(result.isInfinity())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mulGByNIsInfinity() {
|
||||
// n·G = infinity (the group order)
|
||||
val result = MutablePoint()
|
||||
ECPoint.mulG(result, ScalarN.N)
|
||||
assertTrue(result.isInfinity())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mulGMatchesMul() {
|
||||
// mulG(k) should equal mul(G, k)
|
||||
val k = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val gResult = MutablePoint()
|
||||
ECPoint.mulG(gResult, k)
|
||||
val gx = IntArray(8)
|
||||
val gy = IntArray(8)
|
||||
ECPoint.toAffine(gResult, gx, gy)
|
||||
|
||||
val g = MutablePoint()
|
||||
g.setAffine(ECPoint.GX, ECPoint.GY)
|
||||
val mResult = MutablePoint()
|
||||
ECPoint.mul(mResult, g, k)
|
||||
val mx = IntArray(8)
|
||||
val my = IntArray(8)
|
||||
ECPoint.toAffine(mResult, mx, my)
|
||||
|
||||
assertEquals(toHex(mx), toHex(gx))
|
||||
assertEquals(toHex(my), toHex(gy))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mulDoubleGSeparateVsCombined() {
|
||||
// mulDoubleG(s, P, e) should equal mulG(s) + mul(P, e)
|
||||
val s = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val e = hex("3982f19bef1615bccfbb05e321c10e1d4cba3df0e841c2e41eeb6016347653c3")
|
||||
|
||||
val p = MutablePoint()
|
||||
val two = intArrayOf(2, 0, 0, 0, 0, 0, 0, 0)
|
||||
ECPoint.mulG(p, two) // P = 2·G
|
||||
|
||||
// Combined
|
||||
val combined = MutablePoint()
|
||||
ECPoint.mulDoubleG(combined, s, p, e)
|
||||
val cx = IntArray(8)
|
||||
val cy = IntArray(8)
|
||||
ECPoint.toAffine(combined, cx, cy)
|
||||
|
||||
// Separate
|
||||
val sG = MutablePoint()
|
||||
ECPoint.mulG(sG, s)
|
||||
val eP = MutablePoint()
|
||||
ECPoint.mul(eP, p, e)
|
||||
val sep = MutablePoint()
|
||||
ECPoint.addPoints(sep, sG, eP)
|
||||
val sx = IntArray(8)
|
||||
val sy = IntArray(8)
|
||||
ECPoint.toAffine(sep, sx, sy)
|
||||
|
||||
assertEquals(toHex(sx), toHex(cx))
|
||||
assertEquals(toHex(sy), toHex(cy))
|
||||
}
|
||||
|
||||
// ==================== liftX ====================
|
||||
|
||||
@Test
|
||||
fun liftXGenerator() {
|
||||
val x = IntArray(8)
|
||||
val y = IntArray(8)
|
||||
assertTrue(ECPoint.liftX(x, y, ECPoint.GX))
|
||||
assertEquals(toHex(ECPoint.GX), toHex(x))
|
||||
// liftX returns even y
|
||||
assertTrue(ECPoint.hasEvenY(y))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun liftXInvalidX() {
|
||||
// p itself is not a valid x coordinate
|
||||
val x = IntArray(8)
|
||||
val y = IntArray(8)
|
||||
assertFalse(ECPoint.liftX(x, y, FieldP.P))
|
||||
}
|
||||
|
||||
// ==================== Serialization round-trips ====================
|
||||
|
||||
@Test
|
||||
fun compressDecompressRoundTrip() {
|
||||
val compressed = ECPoint.serializeCompressed(ECPoint.GX, ECPoint.GY)
|
||||
val x = IntArray(8)
|
||||
val y = IntArray(8)
|
||||
assertTrue(ECPoint.parsePublicKey(compressed, x, y))
|
||||
assertEquals(toHex(ECPoint.GX), toHex(x))
|
||||
assertEquals(toHex(ECPoint.GY), toHex(y))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun uncompressedRoundTrip() {
|
||||
val uncompressed = ECPoint.serializeUncompressed(ECPoint.GX, ECPoint.GY)
|
||||
val x = IntArray(8)
|
||||
val y = IntArray(8)
|
||||
assertTrue(ECPoint.parsePublicKey(uncompressed, x, y))
|
||||
assertEquals(toHex(ECPoint.GX), toHex(x))
|
||||
assertEquals(toHex(ECPoint.GY), toHex(y))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseInvalidKey() {
|
||||
val x = IntArray(8)
|
||||
val y = IntArray(8)
|
||||
assertFalse(ECPoint.parsePublicKey(ByteArray(10), x, y))
|
||||
assertFalse(ECPoint.parsePublicKey(ByteArray(33), x, y)) // wrong prefix (0x00)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/** Tests for scalar arithmetic modulo n. */
|
||||
class ScalarNTest {
|
||||
private fun hex(s: String) =
|
||||
U256.fromBytes(
|
||||
s.chunked(2).map { it.toInt(16).toByte() }.toByteArray(),
|
||||
)
|
||||
|
||||
private fun toHex(a: IntArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) }
|
||||
|
||||
// ==================== isValid ====================
|
||||
|
||||
@Test
|
||||
fun isValidNormal() {
|
||||
assertTrue(ScalarN.isValid(hex("0000000000000000000000000000000000000000000000000000000000000001")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isValidZero() {
|
||||
assertFalse(ScalarN.isValid(IntArray(8)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isValidN() {
|
||||
// n itself is not valid (must be < n)
|
||||
assertFalse(ScalarN.isValid(ScalarN.N))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isValidNMinus1() {
|
||||
val nMinus1 = hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")
|
||||
assertTrue(ScalarN.isValid(nMinus1))
|
||||
}
|
||||
|
||||
// ==================== Arithmetic identities ====================
|
||||
|
||||
@Test
|
||||
fun addZeroIdentity() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
assertEquals(toHex(a), toHex(ScalarN.add(a, IntArray(8))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subSelfIsZero() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
assertTrue(U256.isZero(ScalarN.sub(a, a)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addThenSubRoundTrips() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val b = hex("3982f19bef1615bccfbb05e321c10e1d4cba3df0e841c2e41eeb6016347653c3")
|
||||
assertEquals(toHex(a), toHex(ScalarN.sub(ScalarN.add(a, b), b)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun negTwiceIsIdentity() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
assertEquals(toHex(a), toHex(ScalarN.neg(ScalarN.neg(a))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addNegIsZero() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
assertTrue(U256.isZero(ScalarN.add(a, ScalarN.neg(a))))
|
||||
}
|
||||
|
||||
// ==================== Multiplication ====================
|
||||
|
||||
@Test
|
||||
fun mulOneIdentity() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val one = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
assertEquals(toHex(a), toHex(ScalarN.mul(a, one)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mulCommutative() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val b = hex("3982f19bef1615bccfbb05e321c10e1d4cba3df0e841c2e41eeb6016347653c3")
|
||||
assertEquals(toHex(ScalarN.mul(a, b)), toHex(ScalarN.mul(b, a)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mulDistributive() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val b = hex("3982f19bef1615bccfbb05e321c10e1d4cba3df0e841c2e41eeb6016347653c3")
|
||||
val c = hex("0000000000000000000000000000000000000000000000000000000000000007")
|
||||
val lhs = ScalarN.mul(a, ScalarN.add(b, c))
|
||||
val rhs = ScalarN.add(ScalarN.mul(a, b), ScalarN.mul(a, c))
|
||||
assertEquals(toHex(lhs), toHex(rhs))
|
||||
}
|
||||
|
||||
// ==================== Inversion ====================
|
||||
|
||||
@Test
|
||||
fun invMulIsOne() {
|
||||
val a = hex("67e56582298859ddae725f972992a07c6c4fb9f62a8fff58ce3ca926a1063530")
|
||||
val aInv = ScalarN.inv(a)
|
||||
val product = ScalarN.mul(a, aInv)
|
||||
val one = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
assertEquals(toHex(one), toHex(product))
|
||||
}
|
||||
|
||||
// ==================== Reduction near n ====================
|
||||
|
||||
@Test
|
||||
fun addNearN() {
|
||||
// (n-1) + 1 should wrap to 0
|
||||
val nMinus1 = hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")
|
||||
val one = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
assertTrue(U256.isZero(ScalarN.add(nMinus1, one)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addNearNWrap() {
|
||||
// (n-1) + 2 should give 1
|
||||
val nMinus1 = hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")
|
||||
val two = intArrayOf(2, 0, 0, 0, 0, 0, 0, 0)
|
||||
val result = ScalarN.add(nMinus1, two)
|
||||
val one = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
assertEquals(toHex(one), toHex(result))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mulLargeScalars() {
|
||||
// (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 = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)
|
||||
assertEquals(toHex(one), toHex(result))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun negOfZeroIsZero() {
|
||||
assertTrue(U256.isZero(ScalarN.neg(IntArray(8))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reduceOfN() {
|
||||
val result = ScalarN.reduce(ScalarN.N.copyOf())
|
||||
assertTrue(U256.isZero(result))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reduceOfSmall() {
|
||||
val a = hex("0000000000000000000000000000000000000000000000000000000000000005")
|
||||
val result = ScalarN.reduce(a)
|
||||
assertEquals(toHex(a), toHex(result)) // No change — already < n
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/** Tests for 256-bit unsigned integer arithmetic. */
|
||||
class U256Test {
|
||||
private fun hex(s: String) =
|
||||
U256.fromBytes(
|
||||
s.chunked(2).map { it.toInt(16).toByte() }.toByteArray(),
|
||||
)
|
||||
|
||||
private fun toHex(a: IntArray) = U256.toBytes(a).joinToString("") { "%02x".format(it) }
|
||||
|
||||
// ==================== isZero / cmp ====================
|
||||
|
||||
@Test
|
||||
fun isZeroTrue() = assertTrue(U256.isZero(IntArray(8)))
|
||||
|
||||
@Test
|
||||
fun isZeroFalse() = assertFalse(U256.isZero(intArrayOf(1, 0, 0, 0, 0, 0, 0, 0)))
|
||||
|
||||
@Test
|
||||
fun isZeroHighBit() = assertFalse(U256.isZero(intArrayOf(0, 0, 0, 0, 0, 0, 0, 1)))
|
||||
|
||||
@Test
|
||||
fun cmpEqual() = assertEquals(0, U256.cmp(hex("0000000000000000000000000000000000000000000000000000000000000001"), hex("0000000000000000000000000000000000000000000000000000000000000001")))
|
||||
|
||||
@Test
|
||||
fun cmpLessThan() = assertEquals(-1, U256.cmp(hex("0000000000000000000000000000000000000000000000000000000000000001"), hex("0000000000000000000000000000000000000000000000000000000000000002")))
|
||||
|
||||
@Test
|
||||
fun cmpGreaterThan() = assertEquals(1, U256.cmp(hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), hex("0000000000000000000000000000000000000000000000000000000000000001")))
|
||||
|
||||
// ==================== add / sub ====================
|
||||
|
||||
@Test
|
||||
fun addSimple() {
|
||||
val out = IntArray(8)
|
||||
val carry = U256.addTo(out, hex("0000000000000000000000000000000000000000000000000000000000000001"), hex("0000000000000000000000000000000000000000000000000000000000000002"))
|
||||
assertEquals("0000000000000000000000000000000000000000000000000000000000000003", toHex(out))
|
||||
assertEquals(0, carry)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addOverflow() {
|
||||
val out = IntArray(8)
|
||||
val carry = U256.addTo(out, hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), hex("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
assertEquals("0000000000000000000000000000000000000000000000000000000000000000", toHex(out))
|
||||
assertEquals(1, carry)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addLimbCarry() {
|
||||
val out = IntArray(8)
|
||||
U256.addTo(out, hex("00000000000000000000000000000000000000000000000000000000ffffffff"), hex("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
assertEquals("0000000000000000000000000000000000000000000000000000000100000000", toHex(out))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subSimple() {
|
||||
val out = IntArray(8)
|
||||
val borrow = U256.subTo(out, hex("0000000000000000000000000000000000000000000000000000000000000003"), hex("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
assertEquals("0000000000000000000000000000000000000000000000000000000000000002", toHex(out))
|
||||
assertEquals(0, borrow)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subUnderflow() {
|
||||
val out = IntArray(8)
|
||||
val borrow = U256.subTo(out, hex("0000000000000000000000000000000000000000000000000000000000000000"), hex("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
assertEquals("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", toHex(out))
|
||||
assertEquals(1, borrow)
|
||||
}
|
||||
|
||||
// ==================== mulWide / sqrWide ====================
|
||||
|
||||
@Test
|
||||
fun mulWideSmall() {
|
||||
val out = IntArray(16)
|
||||
U256.mulWide(out, hex("0000000000000000000000000000000000000000000000000000000000000003"), hex("0000000000000000000000000000000000000000000000000000000000000007"))
|
||||
// 3 * 7 = 21 = 0x15
|
||||
assertEquals(0x15, out[0])
|
||||
for (i in 1 until 16) assertEquals(0, out[i])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mulWideLarge() {
|
||||
// (2^128 - 1)² consistency: mulWide and sqrWide should match
|
||||
val out1 = IntArray(16)
|
||||
val out2 = IntArray(16)
|
||||
val maxHalf = hex("00000000000000000000000000000000ffffffffffffffffffffffffffffffff")
|
||||
U256.mulWide(out1, maxHalf, maxHalf)
|
||||
U256.sqrWide(out2, maxHalf)
|
||||
for (i in 0 until 16) assertEquals(out1[i], out2[i], "Limb $i mismatch")
|
||||
// Lowest limb is 1 (from +1 in (2^128-1)² = 2^256 - 2^129 + 1)
|
||||
assertEquals(1, out1[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sqrWideMatchesMulWide() {
|
||||
// sqrWide(a) should produce the same result as mulWide(a, a)
|
||||
val a = hex("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530")
|
||||
val mulResult = IntArray(16)
|
||||
U256.mulWide(mulResult, a, a)
|
||||
val sqrResult = IntArray(16)
|
||||
U256.sqrWide(sqrResult, a)
|
||||
for (i in 0 until 16) {
|
||||
assertEquals(mulResult[i], sqrResult[i], "Limb $i mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sqrWideMaxValue() {
|
||||
// (2^256 - 1)^2 should match mulWide
|
||||
val maxVal = hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
val mulResult = IntArray(16)
|
||||
U256.mulWide(mulResult, maxVal, maxVal)
|
||||
val sqrResult = IntArray(16)
|
||||
U256.sqrWide(sqrResult, maxVal)
|
||||
for (i in 0 until 16) {
|
||||
assertEquals(mulResult[i], sqrResult[i], "Limb $i mismatch for max value sqr")
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== fromBytes / toBytes ====================
|
||||
|
||||
@Test
|
||||
fun bytesRoundTrip() {
|
||||
val hex = "67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530"
|
||||
val bytes = hex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
val limbs = U256.fromBytes(bytes)
|
||||
val back = U256.toBytes(limbs)
|
||||
assertEquals(hex.lowercase(), back.joinToString("") { "%02x".format(it) })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bytesZero() {
|
||||
val limbs = U256.fromBytes(ByteArray(32))
|
||||
assertTrue(U256.isZero(limbs))
|
||||
}
|
||||
|
||||
// ==================== getNibble ====================
|
||||
|
||||
@Test
|
||||
fun getNibbleValues() {
|
||||
// 0xAB at the lowest byte = limb[0] = 0x...AB
|
||||
val a = hex("00000000000000000000000000000000000000000000000000000000000000ab")
|
||||
assertEquals(0xB, U256.getNibble(a, 0)) // lowest nibble
|
||||
assertEquals(0xA, U256.getNibble(a, 1)) // second nibble
|
||||
assertEquals(0, U256.getNibble(a, 2)) // third nibble
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getNibbleHighBits() {
|
||||
val a = hex("f000000000000000000000000000000000000000000000000000000000000000")
|
||||
assertEquals(0xF, U256.getNibble(a, 63)) // highest nibble
|
||||
assertEquals(0, U256.getNibble(a, 62))
|
||||
}
|
||||
|
||||
// ==================== testBit ====================
|
||||
|
||||
@Test
|
||||
fun testBitLow() {
|
||||
val a = hex("0000000000000000000000000000000000000000000000000000000000000005") // bits 0 and 2
|
||||
assertTrue(U256.testBit(a, 0))
|
||||
assertFalse(U256.testBit(a, 1))
|
||||
assertTrue(U256.testBit(a, 2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBitHigh() {
|
||||
val a = hex("8000000000000000000000000000000000000000000000000000000000000000") // bit 255
|
||||
assertTrue(U256.testBit(a, 255))
|
||||
assertFalse(U256.testBit(a, 254))
|
||||
}
|
||||
|
||||
// ==================== xor ====================
|
||||
|
||||
@Test
|
||||
fun xorBasic() {
|
||||
val out = IntArray(8)
|
||||
U256.xorTo(out, hex("ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00"), hex("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"))
|
||||
assertEquals("f00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00f", toHex(out))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user