Skip to content

Commit

Permalink
Update secp256k1-kmp (#141)
Browse files Browse the repository at this point in the history
* Tests: use kotlinx-io to read resource files

* Use secp256k1-kmp 0.16.0
  • Loading branch information
sstone authored Nov 26, 2024
1 parent d633793 commit 5a7f362
Show file tree
Hide file tree
Showing 16 changed files with 102 additions and 111 deletions.
6 changes: 3 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ kotlin {
}

sourceSets {
val secp256k1KmpVersion = "0.15.0"
val secp256k1KmpVersion = "0.16.0"

val commonMain by getting {
dependencies {
Expand All @@ -59,8 +59,8 @@ kotlin {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation("org.kodein.memory:klio-files:0.12.0")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.5.4")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
}
}

Expand Down
15 changes: 1 addition & 14 deletions src/commonTest/kotlin/fr/acinq/bitcoin/BlockTestsCommon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,11 @@

package fr.acinq.bitcoin

import fr.acinq.bitcoin.reference.TransactionTestsCommon
import fr.acinq.secp256k1.Hex
import org.kodein.memory.file.openReadableFile
import org.kodein.memory.file.resolve
import org.kodein.memory.use
import kotlin.test.*

class BlockTestsCommon {
private val blockData = run {
val file = TransactionTestsCommon.resourcesDir().resolve("block1.dat")
file.openReadableFile().use {
val len = it.size
// workaround for a bug in kotlin memory file where dstOffset cannot be 0 but is still ignored...
val buffer = ByteArray(len)
it.readBytes(buffer, 0, buffer.size)
buffer
}
}
private val blockData = TestHelpers.readResourceAsByteArray("block1.dat")

@Test
fun `read blocks`() {
Expand Down
55 changes: 26 additions & 29 deletions src/commonTest/kotlin/fr/acinq/bitcoin/CryptoTestsCommon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@

package fr.acinq.bitcoin

import fr.acinq.bitcoin.reference.TransactionTestsCommon
import fr.acinq.secp256k1.Hex
import fr.acinq.secp256k1.Secp256k1
import org.kodein.memory.file.openReadableFile
import org.kodein.memory.file.resolve
import org.kodein.memory.text.readLine
import org.kodein.memory.use
import kotlinx.io.buffered
import kotlinx.io.files.Path
import kotlinx.io.files.SystemFileSystem
import kotlinx.io.readLine
import kotlin.random.Random
import kotlin.test.*

Expand Down Expand Up @@ -207,7 +206,6 @@ class CryptoTestsCommon {
val publicKey = privateKey.publicKey()
val shared = Crypto.ecdh(privateKey, publicKey)
assertEquals("56bc84cffc7db1ca04046fc04ec8f84232c340be789bc4779d221fe8b978af06", Hex.encode(shared))

val random = Random
val privateKey1 = PrivateKey(random.nextBytes(32))
val privateKey2 = PrivateKey(random.nextBytes(32))
Expand Down Expand Up @@ -257,29 +255,28 @@ class CryptoTestsCommon {
var pub: PublicKey? = null
var sig: ByteVector? = null
var recid: Int
val file = TransactionTestsCommon.resourcesDir().resolve("recid.txt")
file.openReadableFile().use {
while (true) {
val line = it.readLine() ?: return
val values = line.split(" = ")
val lhs = values[0]
val rhs = values[1]
when (lhs) {
"privkey" -> priv = PrivateKey(ByteVector(rhs).toByteArray())
"message" -> message = ByteVector(rhs)
"pubkey" -> pub = PublicKey(ByteVector(rhs))
"sig" -> sig = run {
val reversed = ByteVector(rhs).take(64)
ByteVector((reversed.take(32).reversed() + reversed.takeRight(32).reversed()).toByteArray())
}
"recid" -> {
recid = rhs.toInt()
assertEquals(priv!!.publicKey(), pub)
val sig1 = Crypto.sign(message!!.toByteArray(), priv!!)
assertEquals(sig1, sig)
val pub1 = Crypto.recoverPublicKey(sig1, message!!.toByteArray(), recid)
assertEquals(pub1, pub)
}
val source = SystemFileSystem.source(Path(TestHelpers.resourcesPath, "recid.txt")).buffered()
while (true) {
val line = source.readLine() ?: return
val values = line.split(" = ")
val lhs = values[0]
val rhs = values[1]
when (lhs) {
"privkey" -> priv = PrivateKey(ByteVector(rhs).toByteArray())
"message" -> message = ByteVector(rhs)
"pubkey" -> pub = PublicKey(ByteVector(rhs))
"sig" -> sig = run {
val reversed = ByteVector(rhs).take(64)
ByteVector((reversed.take(32).reversed() + reversed.takeRight(32).reversed()).toByteArray())
}

"recid" -> {
recid = rhs.toInt()
assertEquals(priv!!.publicKey(), pub)
val sig1 = Crypto.sign(message!!.toByteArray(), priv)
assertEquals(sig1, sig)
val pub1 = Crypto.recoverPublicKey(sig1, message.toByteArray(), recid)
assertEquals(pub1, pub)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package fr.acinq.bitcoin

import fr.acinq.bitcoin.MnemonicCode.toMnemonics
import fr.acinq.bitcoin.MnemonicCode.toSeed
import fr.acinq.bitcoin.reference.TransactionTestsCommon
import fr.acinq.secp256k1.Hex
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
Expand All @@ -42,7 +41,7 @@ class MnemonicCodeTestsCommon {

@Test
fun `reference tests`() {
val tests = TransactionTestsCommon.readData("bip39_vectors.json")
val tests = TestHelpers.readResourceAsJson("bip39_vectors.json")

tests.jsonObject["english"]!!.jsonArray.map {
val raw = it.jsonArray[0].jsonPrimitive.content
Expand Down
14 changes: 2 additions & 12 deletions src/commonTest/kotlin/fr/acinq/bitcoin/TaprootTestsCommon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,8 @@ import fr.acinq.bitcoin.Bech32.hrp
import fr.acinq.bitcoin.Bitcoin.addressToPublicKeyScript
import fr.acinq.bitcoin.Transaction.Companion.hashForSigningSchnorr
import fr.acinq.bitcoin.io.ByteArrayInput
import fr.acinq.bitcoin.io.ByteArrayOutput
import fr.acinq.bitcoin.reference.TransactionTestsCommon.Companion.resourcesDir
import fr.acinq.secp256k1.Hex
import fr.acinq.secp256k1.Secp256k1
import org.kodein.memory.file.openReadableFile
import org.kodein.memory.file.resolve
import kotlin.test.*

class TaprootTestsCommon {
Expand Down Expand Up @@ -356,10 +352,7 @@ class TaprootTestsCommon {
@Test
fun `parse and validate huge transaction`() {
// this is the tx that broke btcd/lnd
val file = resourcesDir().resolve("7393096d97bfee8660f4100ffd61874d62f9a65de9fb6acf740c4c386990ef73.bin").openReadableFile()
val buffer = ByteArray(file.size)
file.readBytes(buffer, 0, buffer.size)
file.close()
val buffer = TestHelpers.readResourceAsByteArray("7393096d97bfee8660f4100ffd61874d62f9a65de9fb6acf740c4c386990ef73.bin")
val tx = Transaction.read(buffer)
assertEquals(1001, tx.txIn[0].witness.stack.size)
val parentTx = Transaction.read(
Expand Down Expand Up @@ -408,10 +401,7 @@ class TaprootTestsCommon {

@Test
fun `parse and validate large ordinals transaction`() {
val file = resourcesDir().resolve("b5a7e05f28d00e4a791759ad7b6bd6799d856693293ceeaad9b0bb93c8851f7f.bin").openReadableFile()
val buffer = ByteArray(file.size)
file.readBytes(buffer, 0, buffer.size)
file.close()
val buffer = TestHelpers.readResourceAsByteArray("b5a7e05f28d00e4a791759ad7b6bd6799d856693293ceeaad9b0bb93c8851f7f.bin")
val tx = Transaction.read(buffer)
val parentTx = Transaction.read(
"0100000000010273721ae5e7d59775f7104670fc8f74e9dee6fe57de47a2ebc14c95cafe4241050000000000fdffffff73721ae5e7d59775f7104670fc8f74e9dee6fe57de47a2ebc14c95cafe4241050100000000fdffffff025459f00200000000225120ca991d5bfbc6840c7568146e305f9eb67d8650948b1f929e941659b2649195941ea50c000000000022512051bf94b8b1d63574a47847d5fdccf2c90953189fa0220cf8f2d6284cf60e5f820140a844d30c2231d6e9c370b094200004475f21545efbd548a6f374ea956e2eea51d62d7048350130bc2fca3a5517ffe34d9e02d150ac58aba2920c88acb3cfc7fe014089d904d0be731c542ef9fe623b19502483348b1b6e0c9058e31c5ebd755070a27aa7dea288b839e2853da70607ea35851394f887e71c1ed58f15bf661969aa5900000000"
Expand Down
26 changes: 26 additions & 0 deletions src/commonTest/kotlin/fr/acinq/bitcoin/TestHelpers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package fr.acinq.bitcoin

import kotlinx.io.buffered
import kotlinx.io.files.Path
import kotlinx.io.files.SystemFileSystem
import kotlinx.io.readByteArray
import kotlinx.io.readString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement

object TestHelpers {
val resourcesPath = Path(readEnvironmentVariable("TEST_RESOURCES_PATH")?: "src/commonTest/resources")

fun readResourceAsJson(filename: String): JsonElement {
val raw = SystemFileSystem.source(Path(resourcesPath, filename)).buffered().readString()
val format = Json { ignoreUnknownKeys = true }
return format.parseToJsonElement(raw)
}


fun readResourceAsByteArray(filename: String): ByteArray {
return SystemFileSystem.source(Path(resourcesPath, filename)).buffered().readByteArray()
}
}

expect fun readEnvironmentVariable(name: String): String?
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import kotlin.test.*
class Musig2TestsCommon {
@Test
fun `aggregate public keys`() {
val tests = TransactionTestsCommon.readData("musig2/key_agg_vectors.json")
val tests = TestHelpers.readResourceAsJson("musig2/key_agg_vectors.json")
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { PublicKey(ByteVector(it.jsonPrimitive.content)) }
val tweaks = tests.jsonObject["tweaks"]!!.jsonArray.map { ByteVector32.fromValidHex(it.jsonPrimitive.content) }

Expand Down Expand Up @@ -58,7 +58,7 @@ class Musig2TestsCommon {

@Test
fun `generate secret nonce`() {
val tests = TransactionTestsCommon.readData("musig2/nonce_gen_vectors.json")
val tests = TestHelpers.readResourceAsJson("musig2/nonce_gen_vectors.json")
tests.jsonObject["test_cases"]!!.jsonArray.forEach {
val randprime = ByteVector32.fromValidHex(it.jsonObject["rand_"]!!.jsonPrimitive.content)
val sk = it.jsonObject["sk"]?.jsonPrimitive?.contentOrNull?.let { PrivateKey.fromHex(it) }
Expand All @@ -85,7 +85,7 @@ class Musig2TestsCommon {

@Test
fun `aggregate nonces`() {
val tests = TransactionTestsCommon.readData("musig2/nonce_agg_vectors.json")
val tests = TestHelpers.readResourceAsJson("musig2/nonce_agg_vectors.json")
val nonces = tests.jsonObject["pnonces"]!!.jsonArray.map { IndividualNonce(it.jsonPrimitive.content) }
tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach {
val nonceIndices = it.jsonObject["pnonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
Expand All @@ -102,7 +102,7 @@ class Musig2TestsCommon {

@Test
fun sign() {
val tests = TransactionTestsCommon.readData("musig2/sign_verify_vectors.json")
val tests = TestHelpers.readResourceAsJson("musig2/sign_verify_vectors.json")
val sk = PrivateKey.fromHex(tests.jsonObject["sk"]!!.jsonPrimitive.content)
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { PublicKey(ByteVector(it.jsonPrimitive.content)) }
val secnonces = tests.jsonObject["secnonces"]!!.jsonArray.map { deserializeSecretNonce(it.jsonPrimitive.content) }
Expand Down Expand Up @@ -148,7 +148,7 @@ class Musig2TestsCommon {

@Test
fun `aggregate signatures`() {
val tests = TransactionTestsCommon.readData("musig2/sig_agg_vectors.json")
val tests = TestHelpers.readResourceAsJson("musig2/sig_agg_vectors.json")
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { PublicKey(ByteVector(it.jsonPrimitive.content)) }
val pnonces = tests.jsonObject["pnonces"]!!.jsonArray.map { IndividualNonce(it.jsonPrimitive.content) }
val tweaks = tests.jsonObject["tweaks"]!!.jsonArray.map { ByteVector32.fromValidHex(it.jsonPrimitive.content) }
Expand Down Expand Up @@ -193,7 +193,7 @@ class Musig2TestsCommon {

@Test
fun `tweak tests`() {
val tests = TransactionTestsCommon.readData("musig2/tweak_vectors.json")
val tests = TestHelpers.readResourceAsJson("musig2/tweak_vectors.json")
val sk = PrivateKey.fromHex(tests.jsonObject["sk"]!!.jsonPrimitive.content)
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { PublicKey(ByteVector(it.jsonPrimitive.content)) }
val pnonces = tests.jsonObject["pnonces"]!!.jsonArray.map { IndividualNonce(it.jsonPrimitive.content) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import kotlin.test.assertNotNull
class BIP341TestsCommon {
@Test
fun `BIP341 reference tests -- key path spending`() {
val tests = TransactionTestsCommon.readData("data/bip341_wallet_vectors.json").jsonObject["keyPathSpending"]!!
val tests = TestHelpers.readResourceAsJson("data/bip341_wallet_vectors.json").jsonObject["keyPathSpending"]!!
tests.jsonArray.forEach { it ->
val fullySignedTx = Transaction.read(it.jsonObject["auxiliary"]!!.jsonObject["fullySignedTx"]!!.jsonPrimitive.content)
val rawUnsignedTx = Transaction.read(it.jsonObject["given"]!!.jsonObject["rawUnsignedTx"]!!.jsonPrimitive.content)
Expand Down Expand Up @@ -78,7 +78,7 @@ class BIP341TestsCommon {

@Test
fun `BIP341 reference tests -- script path spending`() {
val tests = TransactionTestsCommon.readData("data/bip341_wallet_vectors.json").jsonObject["scriptPubKey"]!!
val tests = TestHelpers.readResourceAsJson("data/bip341_wallet_vectors.json").jsonObject["scriptPubKey"]!!
tests.jsonArray.forEach { it ->
val given = it.jsonObject["given"]!!.jsonObject
val internalPubkey = XonlyPublicKey(ByteVector32.fromValidHex(given["internalPubkey"]!!.jsonPrimitive.content))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package fr.acinq.bitcoin.reference

import fr.acinq.bitcoin.Base58
import fr.acinq.bitcoin.Base58Check
import fr.acinq.bitcoin.TestHelpers
import fr.acinq.secp256k1.Hex
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.jsonArray
Expand All @@ -29,7 +30,7 @@ import kotlin.test.assertEquals
class Base58ReferenceTestsCommon {
@Test
fun `reference encode-decode test`() {
val tests = TransactionTestsCommon.readData("data/base58_encode_decode.json")
val tests = TestHelpers.readResourceAsJson("data/base58_encode_decode.json")
tests.jsonArray.filter { it.jsonArray.size == 2 }.map { it.jsonArray }.forEach {
val hex = it[0].jsonPrimitive.content
val expected = it[1].jsonPrimitive.content
Expand All @@ -41,7 +42,7 @@ class Base58ReferenceTestsCommon {

@Test
fun `reference valid keys test`() {
val tests = TransactionTestsCommon.readData("data/base58_keys_valid.json")
val tests = TestHelpers.readResourceAsJson("data/base58_keys_valid.json")
tests.jsonArray.forEach {
val base58: String = it.jsonArray[0].jsonPrimitive.content
val (version, data) = Base58Check.decode(base58)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import kotlin.test.assertTrue
class KeyEncodingTestsCommon {
@Test
fun `valid keys`() {
val tests = TransactionTestsCommon.readData("data/key_io_valid.json")
val tests = TestHelpers.readResourceAsJson("data/key_io_valid.json")
tests.jsonArray.filter { it.jsonArray.size == 3 }.map { it.jsonArray }.forEach {
var encoded: String = it[0].jsonPrimitive.content
val hex: String = it[1].jsonPrimitive.content
Expand Down Expand Up @@ -94,7 +94,7 @@ class KeyEncodingTestsCommon {

@Test
fun `invalid keys`() {
val tests = TransactionTestsCommon.readData("data/key_io_invalid.json")
val tests = TestHelpers.readResourceAsJson("data/key_io_invalid.json")
tests.jsonArray.forEach {
val value = it.jsonArray[0].jsonPrimitive.content
assertTrue(!isValidBase58(value))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class ScriptTestsCommon {
@Test
fun `reference client script test`() {
// ["Format is: [[wit..., amount]?, scriptSig, scriptPubKey, flags, expected_scripterror, ... comments]"]
val tests = TransactionTestsCommon.readData("data/script_tests.json")
val tests = TestHelpers.readResourceAsJson("data/script_tests.json")
var count = 0
tests.jsonArray.filter { it.jsonArray.size >= 4 }.forEach {
runTest(it.jsonArray)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,20 @@

package fr.acinq.bitcoin.reference

import fr.acinq.bitcoin.TestHelpers
import fr.acinq.bitcoin.Transaction
import fr.acinq.secp256k1.Hex
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import org.kodein.memory.file.openReadableFile
import org.kodein.memory.file.resolve
import org.kodein.memory.text.readString
import org.kodein.memory.use
import kotlin.test.Test
import kotlin.test.assertEquals

class SigHashTestsCommon {

@Test
fun `reference client sighash test`() {
val file = TransactionTestsCommon.resourcesDir().resolve("data/sighash.json")
val raw = file.openReadableFile().use { it.readString() }
val format = Json { ignoreUnknownKeys = true }
val json = format.parseToJsonElement(raw)
val json = TestHelpers.readResourceAsJson("data/sighash.json")
// ["raw_transaction, script, input_index, hashType, signature_hash (result)"],
json.jsonArray.filter { it.jsonArray.size == 5 }.map { it.jsonArray }.forEach {
val rawTx = it[0].jsonPrimitive.content
Expand Down
Loading

0 comments on commit 5a7f362

Please sign in to comment.