Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use bitcoin-kmp 0.20.0 #695

Merged
merged 1 commit into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ val currentOs = org.gradle.internal.os.OperatingSystem.current()

kotlin {

val bitcoinKmpVersion = "0.19.0" // when upgrading bitcoin-kmp, keep secpJniJvmVersion in sync!
val bitcoinKmpVersion = "0.20.0" // when upgrading bitcoin-kmp, keep secpJniJvmVersion in sync!
val secpJniJvmVersion = "0.15.0"

val serializationVersion = "1.6.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@ object Helpers {
val sig = Transactions.sign(it, channelKeys.revocationKey.deriveForRevocation(revokedCommitPublished.remotePerCommitmentSecret))
val signedTx = Transactions.addSigs(it, sig)
// we need to make sure that the tx is indeed valid
when (runTrying { Transaction.correctlySpends(signedTx.tx, listOf(htlcTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) }) {
when (runTrying { signedTx.tx.correctlySpends(listOf(htlcTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) }) {
is Try.Success -> signedTx
is Try.Failure -> null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ data class PartiallySignedSharedTransaction(override val tx: SharedTransaction,
}
}
val fullySignedTx = FullySignedSharedTransaction(tx, localSigs, remoteSigs, sharedSigs)
return when (runTrying { Transaction.correctlySpends(fullySignedTx.signedTx, tx.spentOutputs, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) }) {
return when (runTrying { fullySignedTx.signedTx.correctlySpends(tx.spentOutputs, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) }) {
is Try.Success -> fullySignedTx
is Try.Failure -> null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ data class LegacyWaitForFundingConfirmed(
when (cmd.watch) {
is WatchEventConfirmed -> {
val result = runTrying {
Transaction.correctlySpends(commitments.latest.localCommit.publishableTxs.commitTx.tx, listOf(cmd.watch.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
commitments.latest.localCommit.publishableTxs.commitTx.tx.correctlySpends(listOf(cmd.watch.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
}
if (result is Try.Failure) {
logger.error { "funding tx verification failed: ${result.error}" }
Expand Down
29 changes: 14 additions & 15 deletions src/commonMain/kotlin/fr/acinq/lightning/crypto/KeyManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,17 @@ interface KeyManager {
private val master: DeterministicWallet.ExtendedPrivateKey,
val account: Long
) {
private val xpriv = DeterministicWallet.derivePrivateKey(master, bip84BasePath(chain) / hardened(account))
private val xpriv = master.derivePrivateKey(bip84BasePath(chain) / hardened(account))

val xpub: String = DeterministicWallet.encode(
input = DeterministicWallet.publicKey(xpriv),
val xpub: String = xpriv.extendedPublicKey.encode(
prefix = when (chain) {
Chain.Testnet, Chain.Regtest, Chain.Signet -> DeterministicWallet.vpub
Chain.Testnet4, Chain.Testnet3, Chain.Regtest, Chain.Signet -> DeterministicWallet.vpub
Chain.Mainnet -> DeterministicWallet.zpub
}
)

fun privateKey(addressIndex: Long): PrivateKey {
return DeterministicWallet.derivePrivateKey(xpriv, KeyPath.empty / 0 / addressIndex).privateKey
return xpriv.derivePrivateKey(KeyPath.empty / 0 / addressIndex).privateKey
}

fun pubkeyScript(addressIndex: Long): ByteVector {
Expand All @@ -102,7 +101,7 @@ interface KeyManager {

companion object {
fun bip84BasePath(chain: Chain) = when (chain) {
Chain.Regtest, Chain.Testnet, Chain.Signet -> KeyPath.empty / hardened(84) / hardened(1)
Chain.Testnet4, Chain.Testnet3, Chain.Regtest, Chain.Signet -> KeyPath.empty / hardened(84) / hardened(1)
Chain.Mainnet -> KeyPath.empty / hardened(84) / hardened(0)
}
}
Expand All @@ -121,18 +120,18 @@ interface KeyManager {
val remoteServerPublicKey: PublicKey,
val refundDelay: Int = DefaultSwapInParams.RefundDelay
) {
private val userExtendedPrivateKey: DeterministicWallet.ExtendedPrivateKey = DeterministicWallet.derivePrivateKey(master, swapInUserKeyPath(chain))
private val userRefundExtendedPrivateKey: DeterministicWallet.ExtendedPrivateKey = DeterministicWallet.derivePrivateKey(master, swapInUserRefundKeyPath(chain))
private val userExtendedPrivateKey: DeterministicWallet.ExtendedPrivateKey = master.derivePrivateKey(swapInUserKeyPath(chain))
private val userRefundExtendedPrivateKey: DeterministicWallet.ExtendedPrivateKey = master.derivePrivateKey(swapInUserRefundKeyPath(chain))

val userPrivateKey: PrivateKey = userExtendedPrivateKey.privateKey
val userPublicKey: PublicKey = userPrivateKey.publicKey()

private val localServerExtendedPrivateKey: DeterministicWallet.ExtendedPrivateKey = DeterministicWallet.derivePrivateKey(master, swapInLocalServerKeyPath(chain))
fun localServerPrivateKey(remoteNodeId: PublicKey): PrivateKey = DeterministicWallet.derivePrivateKey(localServerExtendedPrivateKey, perUserPath(remoteNodeId)).privateKey
private val localServerExtendedPrivateKey: DeterministicWallet.ExtendedPrivateKey = master.derivePrivateKey(swapInLocalServerKeyPath(chain))
fun localServerPrivateKey(remoteNodeId: PublicKey): PrivateKey = localServerExtendedPrivateKey.derivePrivateKey(perUserPath(remoteNodeId)).privateKey

// legacy p2wsh-based swap-in protocol, with a fixed on-chain address
val legacySwapInProtocol = SwapInProtocolLegacy(userPublicKey, remoteServerPublicKey, refundDelay)
val legacyDescriptor = SwapInProtocolLegacy.descriptor(chain, DeterministicWallet.publicKey(master), DeterministicWallet.publicKey(userExtendedPrivateKey), remoteServerPublicKey, refundDelay)
val legacyDescriptor = SwapInProtocolLegacy.descriptor(chain, master.extendedPublicKey, userExtendedPrivateKey.extendedPublicKey, remoteServerPublicKey, refundDelay)

fun signSwapInputUserLegacy(fundingTx: Transaction, index: Int, parentTxOuts: List<TxOut>): ByteVector64 {
return legacySwapInProtocol.signSwapInputUser(fundingTx, index, parentTxOuts[fundingTx.txIn[index].outPoint.index.toInt()], userPrivateKey)
Expand All @@ -152,7 +151,7 @@ interface KeyManager {
* @return the swap-in protocol that matches the input public key script
*/
fun getSwapInProtocol(addressIndex: Int): SwapInProtocol {
val userRefundPrivateKey: PrivateKey = DeterministicWallet.derivePrivateKey(userRefundExtendedPrivateKey, addressIndex.toLong()).privateKey
val userRefundPrivateKey: PrivateKey = userRefundExtendedPrivateKey.derivePrivateKey(addressIndex.toLong()).privateKey
val userRefundPublicKey: PublicKey = userRefundPrivateKey.publicKey()
return SwapInProtocol(userPublicKey, remoteServerPublicKey, userRefundPublicKey, refundDelay)
}
Expand Down Expand Up @@ -189,7 +188,7 @@ interface KeyManager {
tx.updateWitness(inputIndex, legacySwapInProtocol.witnessRefund(sig))
}
else -> {
val userRefundPrivateKey: PrivateKey = DeterministicWallet.derivePrivateKey(userRefundExtendedPrivateKey, addressIndex.toLong()).privateKey
val userRefundPrivateKey: PrivateKey = userRefundExtendedPrivateKey.derivePrivateKey(addressIndex.toLong()).privateKey
val swapInProtocol = getSwapInProtocol(addressIndex)
val sig = swapInProtocol.signSwapInputRefund(tx, inputIndex, utxos.map { it.txOut }, userRefundPrivateKey)
tx.updateWitness(inputIndex, swapInProtocol.witnessRefund(sig))
Expand Down Expand Up @@ -237,7 +236,7 @@ interface KeyManager {

companion object {
private fun swapInKeyBasePath(chain: Chain) = when (chain) {
Chain.Regtest, Chain.Testnet, Chain.Signet -> KeyPath.empty / hardened(51) / hardened(0)
Chain.Testnet4, Chain.Testnet3, Chain.Regtest, Chain.Signet -> KeyPath.empty / hardened(51) / hardened(0)
Chain.Mainnet -> KeyPath.empty / hardened(52) / hardened(0)
}

Expand All @@ -248,7 +247,7 @@ interface KeyManager {
fun swapInUserRefundKeyPath(chain: Chain) = swapInKeyBasePath(chain) / hardened(2) / 0L

fun encodedSwapInUserKeyPath(chain: Chain) = when (chain) {
Chain.Regtest, Chain.Testnet, Chain.Signet -> "51h/0h/0h"
Chain.Testnet4, Chain.Testnet3, Chain.Regtest, Chain.Signet -> "51h/0h/0h"
Chain.Mainnet -> "52h/0h/0h"
}

Expand Down
19 changes: 9 additions & 10 deletions src/commonMain/kotlin/fr/acinq/lightning/crypto/LocalKeyManager.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package fr.acinq.lightning.crypto

import fr.acinq.bitcoin.*
import fr.acinq.bitcoin.DeterministicWallet.derivePrivateKey
import fr.acinq.bitcoin.DeterministicWallet.hardened
import fr.acinq.bitcoin.crypto.Pack
import fr.acinq.lightning.Lightning.secureRandom
Expand Down Expand Up @@ -38,8 +37,8 @@ data class LocalKeyManager(val seed: ByteVector, val chain: Chain, val remoteSwa
private val master = DeterministicWallet.generate(seed)

override val nodeKeys: KeyManager.NodeKeys = KeyManager.NodeKeys(
legacyNodeKey = @Suppress("DEPRECATION") derivePrivateKey(master, eclairNodeKeyBasePath(chain)),
nodeKey = derivePrivateKey(master, nodeKeyBasePath(chain)),
legacyNodeKey = @Suppress("DEPRECATION") master.derivePrivateKey(eclairNodeKeyBasePath(chain)),
nodeKey = master.derivePrivateKey(nodeKeyBasePath(chain)),
)

override val finalOnChainWallet: KeyManager.Bip84OnChainKeys = KeyManager.Bip84OnChainKeys(chain, master, account = 0)
Expand All @@ -50,7 +49,7 @@ data class LocalKeyManager(val seed: ByteVector, val chain: Chain, val remoteSwa
else -> DeterministicWallet.tpub
}
require(prefix == expectedPrefix) { "unexpected swap-in xpub prefix $prefix (expected $expectedPrefix)" }
val remoteSwapInPublicKey = DeterministicWallet.derivePublicKey(xpub, KeyManager.SwapInOnChainKeys.perUserPath(nodeKeys.nodeKey.publicKey)).publicKey
val remoteSwapInPublicKey = xpub.derivePublicKey(KeyManager.SwapInOnChainKeys.perUserPath(nodeKeys.nodeKey.publicKey)).publicKey
KeyManager.SwapInOnChainKeys(chain, master, remoteSwapInPublicKey)
}

Expand All @@ -60,9 +59,9 @@ data class LocalKeyManager(val seed: ByteVector, val chain: Chain, val remoteSwa
* This method offers direct access to the master key derivation. It should only be used for some advanced usage
* like (LNURL-auth, data encryption).
*/
fun derivePrivateKey(keyPath: KeyPath): DeterministicWallet.ExtendedPrivateKey = derivePrivateKey(master, keyPath)
fun derivePrivateKey(keyPath: KeyPath): DeterministicWallet.ExtendedPrivateKey = master.derivePrivateKey(keyPath)

fun privateKey(keyPath: KeyPath): PrivateKey = derivePrivateKey(master, keyPath).privateKey
fun privateKey(keyPath: KeyPath): PrivateKey = master.derivePrivateKey(keyPath).privateKey

override fun newFundingKeyPath(isInitiator: Boolean): KeyPath {
val last = hardened(if (isInitiator) 1 else 0)
Expand All @@ -72,7 +71,7 @@ data class LocalKeyManager(val seed: ByteVector, val chain: Chain, val remoteSwa

override fun channelKeys(fundingKeyPath: KeyPath): KeyManager.ChannelKeys {
// We use a different funding key for each splice, with a derivation based on the fundingTxIndex.
val fundingKey: (Long) -> PrivateKey = { index -> derivePrivateKey(master, channelKeyBasePath / fundingKeyPath / hardened(index)).privateKey }
val fundingKey: (Long) -> PrivateKey = { index -> master.derivePrivateKey(channelKeyBasePath / fundingKeyPath / hardened(index)).privateKey }
// We use the initial funding pubkey to compute the channel key path, and we use the recovery process even
// in the normal case, which guarantees it works all the time.
val initialFundingPubkey = fundingKey(0).publicKey()
Expand Down Expand Up @@ -150,19 +149,19 @@ data class LocalKeyManager(val seed: ByteVector, val chain: Chain, val remoteSwa
}

fun channelKeyBasePath(chain: Chain) = when (chain) {
Chain.Regtest, Chain.Testnet, Chain.Signet -> KeyPath.empty / hardened(48) / hardened(1)
Chain.Testnet4, Chain.Testnet3, Chain.Regtest, Chain.Signet -> KeyPath.empty / hardened(48) / hardened(1)
Chain.Mainnet -> KeyPath.empty / hardened(50) / hardened(1)
}

/** Path for node keys generated by eclair-core */
@Deprecated("used for backward-compat with eclair-core", replaceWith = ReplaceWith("nodeKeyBasePath(chain)"))
fun eclairNodeKeyBasePath(chain: Chain) = when (chain) {
Chain.Regtest, Chain.Testnet, Chain.Signet -> KeyPath.empty / hardened(46) / hardened(0)
Chain.Testnet4, Chain.Testnet3, Chain.Regtest, Chain.Signet -> KeyPath.empty / hardened(46) / hardened(0)
Chain.Mainnet -> KeyPath.empty / hardened(47) / hardened(0)
}

fun nodeKeyBasePath(chain: Chain) = when (chain) {
Chain.Regtest, Chain.Testnet, Chain.Signet -> KeyPath.empty / hardened(48) / hardened(0)
Chain.Testnet4, Chain.Testnet3, Chain.Regtest, Chain.Signet -> KeyPath.empty / hardened(48) / hardened(0)
Chain.Mainnet -> KeyPath.empty / hardened(50) / hardened(0)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,8 @@ object JsonSerializers {
chain = when {
o.chains.isEmpty() -> Chain.Mainnet.name
o.chains.contains(Chain.Mainnet.chainHash) && o.chains.size == 1 -> Chain.Mainnet.name
o.chains.contains(Chain.Testnet.chainHash) && o.chains.size == 1 -> Chain.Testnet.name
o.chains.contains(Chain.Testnet3.chainHash) && o.chains.size == 1 -> Chain.Testnet3.name
o.chains.contains(Chain.Testnet4.chainHash) && o.chains.size == 1 -> Chain.Testnet4.name
o.chains.contains(Chain.Regtest.chainHash) && o.chains.size == 1 -> Chain.Regtest.name
else -> "unknown"
}.lowercase(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ data class Bolt11Invoice(

private val prefixes = mapOf(
Chain.Regtest to "lnbcrt",
Chain.Testnet to "lntb",
Chain.Testnet3 to "lntb",
Chain.Testnet4 to "lntb",
Chain.Mainnet to "lnbc"
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ data class SwapInProtocol(val userPublicKey: PublicKey, val serverPublicKey: Pub
// The script path contains a refund script, generated from this policy: and_v(v:pk(user),older(refundDelay)).
// It does not depend upon the user's or server's key, just the user's refund key and the refund delay.
private val refundScript = listOf(OP_PUSHDATA(userRefundKey.xOnly()), OP_CHECKSIGVERIFY, OP_PUSHDATA(Script.encodeNumber(refundDelay)), OP_CHECKSEQUENCEVERIFY)
private val scriptTree = ScriptTree.Leaf(0, refundScript)
private val scriptTree = ScriptTree.Leaf(refundScript)
val pubkeyScript: List<ScriptElt> = Script.pay2tr(internalPublicKey, scriptTree)
val serializedPubkeyScript = Script.write(pubkeyScript).byteVector()

Expand All @@ -46,7 +46,7 @@ data class SwapInProtocol(val userPublicKey: PublicKey, val serverPublicKey: Pub

fun signSwapInputRefund(fundingTx: Transaction, index: Int, parentTxOuts: List<TxOut>, userPrivateKey: PrivateKey): ByteVector64 {
require(userPrivateKey.publicKey() == userRefundKey) { "refund private key does not match expected public key: are you using the user key instead of the refund key?" }
return Transaction.signInputTaprootScriptPath(userPrivateKey, fundingTx, index, parentTxOuts, SigHash.SIGHASH_DEFAULT, scriptTree.hash())
return fundingTx.signInputTaprootScriptPath(userPrivateKey, index, parentTxOuts, SigHash.SIGHASH_DEFAULT, scriptTree.hash())
}

fun signSwapInputServer(fundingTx: Transaction, index: Int, parentTxOuts: List<TxOut>, serverPrivateKey: PrivateKey, privateNonce: SecretNonce, userNonce: IndividualNonce, serverNonce: IndividualNonce): Either<Throwable, ByteVector32> {
Expand All @@ -62,7 +62,7 @@ data class SwapInProtocol(val userPublicKey: PublicKey, val serverPublicKey: Pub
Chain.Mainnet -> DeterministicWallet.xprv
else -> DeterministicWallet.tprv
}
val xpriv = DeterministicWallet.encode(masterRefundKey, prefix)
val xpriv = masterRefundKey.encode(prefix)
val desc = "tr(${internalPubKey.value},and_v(v:pk($xpriv/*),older($refundDelay)))"
val checksum = Descriptor.checksum(desc)
return "$desc#$checksum"
Expand All @@ -74,7 +74,7 @@ data class SwapInProtocol(val userPublicKey: PublicKey, val serverPublicKey: Pub
Chain.Mainnet -> DeterministicWallet.xpub
else -> DeterministicWallet.tpub
}
val xpub = DeterministicWallet.encode(masterRefundKey, prefix)
val xpub = masterRefundKey.encode(prefix)
val desc = "tr(${internalPubKey.value},and_v(v:pk($xpub/*),older($refundDelay)))"
val checksum = Descriptor.checksum(desc)
return "$desc#$checksum"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,7 @@ object Transactions {
.also { check(Scripts.der(it, SigHash.SIGHASH_ALL).size() == 72) { "Should be 72 bytes but is ${Scripts.der(it, SigHash.SIGHASH_ALL).size()} bytes" } }

fun sign(tx: Transaction, inputIndex: Int, redeemScript: ByteArray, amount: Satoshi, key: PrivateKey, sigHash: Int = SigHash.SIGHASH_ALL): ByteVector64 {
val sigDER = Transaction.signInput(tx, inputIndex, redeemScript, sigHash, amount, SigVersion.SIGVERSION_WITNESS_V0, key)
val sigDER = tx.signInput(inputIndex, redeemScript, sigHash, amount, SigVersion.SIGVERSION_WITNESS_V0, key)
return Crypto.der2compact(sigDER)
}

Expand Down Expand Up @@ -873,11 +873,11 @@ object Transactions {
}

fun checkSpendable(txinfo: TransactionWithInputInfo): Try<Unit> = runTrying {
Transaction.correctlySpends(txinfo.tx, mapOf(txinfo.tx.txIn.first().outPoint to txinfo.input.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
txinfo.tx.correctlySpends(mapOf(txinfo.tx.txIn.first().outPoint to txinfo.input.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
}

fun checkSig(txinfo: TransactionWithInputInfo, sig: ByteVector64, pubKey: PublicKey, sigHash: Int = SigHash.SIGHASH_ALL): Boolean {
val data = Transaction.hashForSigning(txinfo.tx, 0, txinfo.input.redeemScript.toByteArray(), sigHash, txinfo.input.txOut.amount, SigVersion.SIGVERSION_WITNESS_V0)
val data = txinfo.tx.hashForSigning(0, txinfo.input.redeemScript.toByteArray(), sigHash, txinfo.input.txOut.amount, SigVersion.SIGVERSION_WITNESS_V0)
return Crypto.verifySignature(data, sig, pubKey)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ class SwapInWalletTestsCommon : LightningTestSuite() {
@Test
fun `swap-in wallet test`() = runSuspendTest(timeout = 15.seconds) {
val mnemonics = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".split(" ")
val keyManager = LocalKeyManager(MnemonicCode.toSeed(mnemonics, "").toByteVector(), Chain.Testnet, TestConstants.aliceSwapInServerXpub)
val keyManager = LocalKeyManager(MnemonicCode.toSeed(mnemonics, "").toByteVector(), Chain.Testnet3, TestConstants.aliceSwapInServerXpub)
val client = connectToTestnetServer()
val wallet = SwapInWallet(Chain.Testnet, keyManager.swapInOnChainWallet, client, this, loggerFactory)
val wallet = SwapInWallet(Chain.Testnet3, keyManager.swapInOnChainWallet, client, this, loggerFactory)

// addresses 0 to 3 have funds on them, the current address is the 4th
assertEquals(4, wallet.swapInAddressFlow.filterNotNull().first().second)
Expand Down
Loading
Loading