From 14e02038db0b6cd9299d75175d1a0b64e4cf0ee1 Mon Sep 17 00:00:00 2001 From: sstone Date: Tue, 4 Jul 2023 14:56:33 +0200 Subject: [PATCH] Update dependencies Use bitcoin-kmp 0.13.0 which includes improvements to address/script conversions methods that make them easier to use from Phoenix. bitcoin-kmp 0.13.0 uses secp256k1-kmp 0.10.1 which is based on secp256k1 0.3.2 Use kotlinx coroutines 1.7.2 (a bugfix release, see https://github.com/Kotlin/kotlinx.coroutines/releases/tag/1.7.2) --- build.gradle.kts | 6 +-- .../blockchain/electrum/ElectrumMiniWallet.kt | 29 ++++++++----- .../fr/acinq/lightning/crypto/KeyManager.kt | 41 ++++++++++--------- .../electrum/ElectrumMiniWalletTest.kt | 2 +- .../crypto/LocalKeyManagerTestsCommon.kt | 4 +- 5 files changed, 47 insertions(+), 35 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 621c0124d..23439e057 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,11 +29,11 @@ kotlin { val ktorVersion: String by extra { "2.0.3" } fun ktor(module: String) = "io.ktor:ktor-$module:$ktorVersion" val serializationVersion = "1.5.1" - val coroutineVersion = "1.7.1" + val coroutineVersion = "1.7.2" val commonMain by sourceSets.getting { dependencies { - api("fr.acinq.bitcoin:bitcoin-kmp:0.12.0") // when upgrading, keep secp256k1-kmp-jni-jvm in sync below + api("fr.acinq.bitcoin:bitcoin-kmp:0.13.0") // when upgrading, keep secp256k1-kmp-jni-jvm in sync below api("org.kodein.log:canard:0.18.0") api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion") api("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion") @@ -63,7 +63,7 @@ kotlin { api(ktor("client-okhttp")) api(ktor("network")) api(ktor("network-tls")) - implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-jvm:0.10.0") + implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-jvm:0.10.1") implementation("org.slf4j:slf4j-api:1.7.36") api("org.xerial:sqlite-jdbc:3.32.3.2") } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWallet.kt b/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWallet.kt index 7d3d3c22e..c8fb4fab0 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWallet.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWallet.kt @@ -156,15 +156,23 @@ class ElectrumMiniWallet( * Depending on the status of the electrum connection, the subscription may or may not be sent to a server. * It is the responsibility of the caller to resubscribe on reconnection. */ - suspend fun subscribe(bitcoinAddress: String): Pair { - val pubkeyScript = ByteVector(Script.write(Bitcoin.addressToPublicKeyScript(chainHash, bitcoinAddress))) - val scriptHash = ElectrumClient.computeScriptHash(pubkeyScript) - kotlin.runCatching { client.startScriptHashSubscription(scriptHash) } - .map { response -> - logger.info { "subscribed to address=$bitcoinAddress pubkeyScript=$pubkeyScript scriptHash=$scriptHash" } - _walletStateFlow.value = _walletStateFlow.value.processSubscriptionResponse(response) + suspend fun subscribe(bitcoinAddress: String): Pair? { + return when (val result = Bitcoin.addressToPublicKeyScript(chainHash, bitcoinAddress)) { + is AddressToPublicKeyScriptResult.Failure -> { + logger.error { "cannot subscribe to $bitcoinAddress ($result)" } + null } - return scriptHash to bitcoinAddress + + is AddressToPublicKeyScriptResult.Success -> { + val pubkeyScript = ByteVector(Script.write(result.script)) + val scriptHash = ElectrumClient.computeScriptHash(pubkeyScript) + kotlin.runCatching { client.startScriptHashSubscription(scriptHash) }.map { response -> + logger.info { "subscribed to address=$bitcoinAddress pubkeyScript=$pubkeyScript scriptHash=$scriptHash" } + _walletStateFlow.value = _walletStateFlow.value.processSubscriptionResponse(response) + } + scriptHash to bitcoinAddress + } + } } job = launch { @@ -192,8 +200,9 @@ class ElectrumMiniWallet( is WalletCommand.Companion.AddAddress -> { logger.mdcinfo { "adding new address=${it.bitcoinAddress}" } - val (scriptHash, address) = subscribe(it.bitcoinAddress) - scriptHashes = scriptHashes + (scriptHash to address) + subscribe(it.bitcoinAddress)?.let { + scriptHashes = scriptHashes + it + } } } } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/crypto/KeyManager.kt b/src/commonMain/kotlin/fr/acinq/lightning/crypto/KeyManager.kt index e1e0e5dc8..342a54c9e 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/crypto/KeyManager.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/crypto/KeyManager.kt @@ -125,7 +125,7 @@ interface KeyManager { val redeemScript: List = Scripts.swapIn2of2(userPublicKey, remoteServerPublicKey, refundDelay) val pubkeyScript: List = Script.pay2wsh(redeemScript) - val address: String = Bitcoin.addressFromPublicKeyScript(chain.chainHash, pubkeyScript)!! + val address: String = Bitcoin.addressFromPublicKeyScript(chain.chainHash, pubkeyScript).result!! /** * The output script descriptor matching our swap-in addresses. @@ -153,27 +153,30 @@ interface KeyManager { return if (utxos.isEmpty()) { null } else { - val ourOutput = TxOut(utxos.map { it.amount }.sum(), Bitcoin.addressToPublicKeyScript(chain.chainHash, address)) - val unsignedTx = Transaction( - version = 2, - txIn = utxos.map { TxIn(OutPoint(swapInTx, swapInTx.txOut.indexOf(it).toLong()), sequence = refundDelay.toLong()) }, - txOut = listOf(ourOutput), - lockTime = 0 - ) - val fees = run { - val recoveryTx = utxos.foldIndexed(unsignedTx) { index, tx, utxo -> + val pubKeyScript = Bitcoin.addressToPublicKeyScript(chain.chainHash, address).result + pubKeyScript?.let { script -> + val ourOutput = TxOut(utxos.map { it.amount }.sum(), script) + val unsignedTx = Transaction( + version = 2, + txIn = utxos.map { TxIn(OutPoint(swapInTx, swapInTx.txOut.indexOf(it).toLong()), sequence = refundDelay.toLong()) }, + txOut = listOf(ourOutput), + lockTime = 0 + ) + val fees = run { + val recoveryTx = utxos.foldIndexed(unsignedTx) { index, tx, utxo -> + val sig = Transactions.signSwapInputUser(tx, index, utxo, userPrivateKey, remoteServerPublicKey, refundDelay) + tx.updateWitness(index, Scripts.witnessSwapIn2of2Refund(sig, userPublicKey, remoteServerPublicKey, refundDelay)) + } + Transactions.weight2fee(feeRate, recoveryTx.weight()) + } + val unsignedTx1 = unsignedTx.copy(txOut = listOf(ourOutput.copy(amount = ourOutput.amount - fees))) + val recoveryTx = utxos.foldIndexed(unsignedTx1) { index, tx, utxo -> val sig = Transactions.signSwapInputUser(tx, index, utxo, userPrivateKey, remoteServerPublicKey, refundDelay) - tx.updateWitness(index, Scripts.witnessSwapIn2of2Refund(sig, userPublicKey,remoteServerPublicKey, refundDelay)) + tx.updateWitness(index, Scripts.witnessSwapIn2of2Refund(sig, userPublicKey, remoteServerPublicKey, refundDelay)) } - Transactions.weight2fee(feeRate, recoveryTx.weight()) - } - val unsignedTx1 = unsignedTx.copy(txOut = listOf(ourOutput.copy(amount = ourOutput.amount - fees))) - val recoveryTx = utxos.foldIndexed(unsignedTx1) { index, tx, utxo -> - val sig = Transactions.signSwapInputUser(tx, index, utxo, userPrivateKey, remoteServerPublicKey, refundDelay) - tx.updateWitness(index, Scripts.witnessSwapIn2of2Refund(sig, userPublicKey,remoteServerPublicKey, refundDelay)) + // this tx is signed but cannot be published until swapInTx has `refundDelay` confirmations + recoveryTx } - // this tx is signed but cannot be published until swapInTx has `refundDelay` confirmations - recoveryTx } } diff --git a/src/commonTest/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWalletTest.kt b/src/commonTest/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWalletTest.kt index 3e9e0e90d..9527e33e6 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWalletTest.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWalletTest.kt @@ -111,7 +111,7 @@ class ElectrumMiniWalletTest : LightningTestSuite() { ), actual = walletState.utxos.map { val txOut = it.previousTx.txOut[it.outputIndex] - val address = Bitcoin.addressFromPublicKeyScript(Block.LivenetGenesisBlock.hash, txOut.publicKeyScript.toByteArray()) + val address = Bitcoin.addressFromPublicKeyScript(Block.LivenetGenesisBlock.hash, txOut.publicKeyScript.toByteArray()).result!! Triple(address, it.previousTx.txid to it.outputIndex, txOut.amount) }.toSet() ) diff --git a/src/commonTest/kotlin/fr/acinq/lightning/crypto/LocalKeyManagerTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/crypto/LocalKeyManagerTestsCommon.kt index ed4853b8d..9bd0811a5 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/crypto/LocalKeyManagerTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/crypto/LocalKeyManagerTestsCommon.kt @@ -195,8 +195,8 @@ class LocalKeyManagerTestsCommon : LightningTestSuite() { val swapInTx = Transaction(version = 2, txIn = listOf(), txOut = listOf( - TxOut(Satoshi(100000), Bitcoin.addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, TestConstants.Alice.keyManager.swapInOnChainWallet.address)), - TxOut(Satoshi(150000), Bitcoin.addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, TestConstants.Alice.keyManager.swapInOnChainWallet.address)) + TxOut(Satoshi(100000), Bitcoin.addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, TestConstants.Alice.keyManager.swapInOnChainWallet.address).result!!), + TxOut(Satoshi(150000), Bitcoin.addressToPublicKeyScript(Block.RegtestGenesisBlock.hash, TestConstants.Alice.keyManager.swapInOnChainWallet.address).result!!) ), lockTime = 0) val recoveryTx = TestConstants.Alice.keyManager.swapInOnChainWallet.createRecoveryTransaction(swapInTx, TestConstants.Alice.keyManager.finalOnChainWallet.address(0), FeeratePerKw(FeeratePerByte(Satoshi(5))))!!