Skip to content

Commit

Permalink
Use Chain type provided by bitcoin-kmp
Browse files Browse the repository at this point in the history
  • Loading branch information
sstone committed Feb 15, 2024
1 parent d53282b commit 7b124e7
Show file tree
Hide file tree
Showing 11 changed files with 75 additions and 90 deletions.
19 changes: 3 additions & 16 deletions src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package fr.acinq.lightning

import co.touchlab.kermit.Logger
import fr.acinq.bitcoin.Block
import fr.acinq.bitcoin.Bitcoin
import fr.acinq.bitcoin.PublicKey
import fr.acinq.bitcoin.Satoshi
import fr.acinq.lightning.Lightning.nodeFee
Expand Down Expand Up @@ -152,7 +152,7 @@ data class NodeParams(
val autoReconnect: Boolean,
val initialRandomReconnectDelaySeconds: Long,
val maxReconnectIntervalSeconds: Long,
val chain: Chain,
val chain: Bitcoin.Chain,
val channelFlags: Byte,
val paymentRequestExpirySeconds: Long,
val multiPartPaymentExpirySeconds: Long,
Expand Down Expand Up @@ -183,7 +183,7 @@ data class NodeParams(
/**
* Library integrators should use this constructor and override values.
*/
constructor(chain: Chain, loggerFactory: LoggerFactory, keyManager: KeyManager) : this(
constructor(chain: Bitcoin.Chain, loggerFactory: LoggerFactory, keyManager: KeyManager) : this(
loggerFactory = loggerFactory,
keyManager = keyManager,
alias = "lightning-kmp",
Expand Down Expand Up @@ -244,17 +244,4 @@ data class NodeParams(
paymentRecipientExpiryParams = RecipientCltvExpiryParams(CltvExpiryDelta(75), CltvExpiryDelta(200)),
liquidityPolicy = MutableStateFlow<LiquidityPolicy>(LiquidityPolicy.Auto(maxAbsoluteFee = 2_000.sat, maxRelativeFeeBasisPoints = 3_000 /* 3000 = 30 % */, skipAbsoluteFeeCheck = false))
)

sealed class Chain(val name: String, private val genesis: Block) {
object Regtest : Chain("Regtest", Block.RegtestGenesisBlock)
object Testnet : Chain("Testnet", Block.TestnetGenesisBlock)
object Mainnet : Chain("Mainnet", Block.LivenetGenesisBlock)

fun isMainnet(): Boolean = this is Mainnet
fun isTestnet(): Boolean = this is Testnet

val chainHash by lazy { genesis.hash }

override fun toString(): String = name
}
}
26 changes: 14 additions & 12 deletions src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt
Original file line number Diff line number Diff line change
Expand Up @@ -686,24 +686,25 @@ data class InteractiveTxSession(
}
}
is Either.Left -> {
val txAddInput = when (msg.value) {
is InteractiveTxInput.LocalOnly -> TxAddInput(fundingParams.channelId, msg.value.serialId, msg.value.previousTx, msg.value.previousTxOutput, msg.value.sequence)
val inputOutgoing = msg.value
val txAddInput = when (inputOutgoing) {
is InteractiveTxInput.LocalOnly -> TxAddInput(fundingParams.channelId, inputOutgoing.serialId, inputOutgoing.previousTx, inputOutgoing.previousTxOutput, inputOutgoing.sequence)
is InteractiveTxInput.LocalLegacySwapIn -> {
val swapInParams = TxAddInputTlv.SwapInParamsLegacy(swapInKeys.userPublicKey, swapInKeys.remoteServerPublicKey, swapInKeys.refundDelay)
TxAddInput(fundingParams.channelId, msg.value.serialId, msg.value.previousTx, msg.value.previousTxOutput, msg.value.sequence, TlvStream(swapInParams))
TxAddInput(fundingParams.channelId, inputOutgoing.serialId, inputOutgoing.previousTx, inputOutgoing.previousTxOutput, inputOutgoing.sequence, TlvStream(swapInParams))
}
is InteractiveTxInput.LocalSwapIn -> {
val swapInParams = TxAddInputTlv.SwapInParams(swapInKeys.userPublicKey, swapInKeys.remoteServerPublicKey, swapInKeys.userRefundPublicKey, swapInKeys.refundDelay)
TxAddInput(fundingParams.channelId, msg.value.serialId, msg.value.previousTx, msg.value.previousTxOutput, msg.value.sequence, TlvStream(swapInParams))
TxAddInput(fundingParams.channelId, inputOutgoing.serialId, inputOutgoing.previousTx, inputOutgoing.previousTxOutput, inputOutgoing.sequence, TlvStream(swapInParams))
}
is InteractiveTxInput.Shared -> TxAddInput(fundingParams.channelId, msg.value.serialId, msg.value.outPoint, msg.value.sequence)
is InteractiveTxInput.Shared -> TxAddInput(fundingParams.channelId, inputOutgoing.serialId, inputOutgoing.outPoint, inputOutgoing.sequence)
}
val nextSecretNonces = when (msg.value) {
val nextSecretNonces = when (inputOutgoing) {
// Generate a secret nonce for this input if we don't already have one.
is InteractiveTxInput.LocalSwapIn -> when (secretNonces[msg.value.serialId]) {
is InteractiveTxInput.LocalSwapIn -> when (secretNonces[inputOutgoing.serialId]) {
null -> {
val secretNonce = Musig2.generateNonce(randomBytes32(), swapInKeys.userPrivateKey, listOf(swapInKeys.userPublicKey, swapInKeys.remoteServerPublicKey))
secretNonces + (msg.value.serialId to secretNonce)
secretNonces + (inputOutgoing.serialId to secretNonce)
}
else -> secretNonces
}
Expand All @@ -713,10 +714,11 @@ data class InteractiveTxSession(
Pair(next, InteractiveTxSessionAction.SendMessage(txAddInput))
}
is Either.Right -> {
val next = copy(toSend = toSend.tail(), localOutputs = localOutputs + msg.value, txCompleteSent = null)
val txAddOutput = when (msg.value) {
is InteractiveTxOutput.Local -> TxAddOutput(fundingParams.channelId, msg.value.serialId, msg.value.amount, msg.value.pubkeyScript)
is InteractiveTxOutput.Shared -> TxAddOutput(fundingParams.channelId, msg.value.serialId, msg.value.amount, msg.value.pubkeyScript)
val outputOutgoing = msg.value
val next = copy(toSend = toSend.tail(), localOutputs = localOutputs + outputOutgoing, txCompleteSent = null)
val txAddOutput = when (outputOutgoing) {
is InteractiveTxOutput.Local -> TxAddOutput(fundingParams.channelId, outputOutgoing.serialId, outputOutgoing.amount, outputOutgoing.pubkeyScript)
is InteractiveTxOutput.Shared -> TxAddOutput(fundingParams.channelId, outputOutgoing.serialId, outputOutgoing.amount, outputOutgoing.pubkeyScript)
}
Pair(next, InteractiveTxSessionAction.SendMessage(txAddOutput))
}
Expand Down
33 changes: 16 additions & 17 deletions src/commonMain/kotlin/fr/acinq/lightning/crypto/KeyManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import fr.acinq.bitcoin.crypto.musig2.SecretNonce
import fr.acinq.bitcoin.io.ByteArrayInput
import fr.acinq.bitcoin.utils.Either
import fr.acinq.lightning.DefaultSwapInParams
import fr.acinq.lightning.NodeParams
import fr.acinq.lightning.blockchain.fee.FeeratePerKw
import fr.acinq.lightning.transactions.SwapInProtocol
import fr.acinq.lightning.transactions.SwapInProtocolLegacy
Expand Down Expand Up @@ -71,7 +70,7 @@ interface KeyManager {
}

data class Bip84OnChainKeys(
private val chain: NodeParams.Chain,
private val chain: Bitcoin.Chain,
private val master: DeterministicWallet.ExtendedPrivateKey,
val account: Long
) {
Expand All @@ -80,8 +79,8 @@ interface KeyManager {
val xpub: String = DeterministicWallet.encode(
input = DeterministicWallet.publicKey(xpriv),
prefix = when (chain) {
NodeParams.Chain.Testnet, NodeParams.Chain.Regtest -> DeterministicWallet.vpub
NodeParams.Chain.Mainnet -> DeterministicWallet.zpub
Bitcoin.Chain.Testnet, Bitcoin.Chain.Regtest, Bitcoin.Chain.Signet -> DeterministicWallet.vpub
Bitcoin.Chain.Mainnet -> DeterministicWallet.zpub
}
)

Expand All @@ -101,9 +100,9 @@ interface KeyManager {
}

companion object {
fun bip84BasePath(chain: NodeParams.Chain) = when (chain) {
NodeParams.Chain.Regtest, NodeParams.Chain.Testnet -> KeyPath.empty / hardened(84) / hardened(1)
NodeParams.Chain.Mainnet -> KeyPath.empty / hardened(84) / hardened(0)
fun bip84BasePath(chain: Bitcoin.Chain) = when (chain) {
Bitcoin.Chain.Regtest, Bitcoin.Chain.Testnet, Bitcoin.Chain.Signet -> KeyPath.empty / hardened(84) / hardened(1)
Bitcoin.Chain.Mainnet -> KeyPath.empty / hardened(84) / hardened(0)
}
}
}
Expand All @@ -116,7 +115,7 @@ interface KeyManager {
* The keys used are static across swaps to make recovery easier.
*/
data class SwapInOnChainKeys(
private val chain: NodeParams.Chain,
private val chain: Bitcoin.Chain,
private val master: DeterministicWallet.ExtendedPrivateKey,
val remoteServerPublicKey: PublicKey,
val refundDelay: Int = DefaultSwapInParams.RefundDelay
Expand Down Expand Up @@ -200,20 +199,20 @@ interface KeyManager {
}

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

fun swapInUserKeyPath(chain: NodeParams.Chain) = swapInKeyBasePath(chain) / hardened(0)
fun swapInUserKeyPath(chain: Bitcoin.Chain) = swapInKeyBasePath(chain) / hardened(0)

fun swapInLocalServerKeyPath(chain: NodeParams.Chain) = swapInKeyBasePath(chain) / hardened(1)
fun swapInLocalServerKeyPath(chain: Bitcoin.Chain) = swapInKeyBasePath(chain) / hardened(1)

fun swapInUserRefundKeyPath(chain: NodeParams.Chain) = swapInKeyBasePath(chain) / hardened(2) / 0L
fun swapInUserRefundKeyPath(chain: Bitcoin.Chain) = swapInKeyBasePath(chain) / hardened(2) / 0L

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

/** Swap-in servers use a different swap-in key for different users. */
Expand Down
23 changes: 11 additions & 12 deletions src/commonMain/kotlin/fr/acinq/lightning/crypto/LocalKeyManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import fr.acinq.bitcoin.DeterministicWallet.derivePrivateKey
import fr.acinq.bitcoin.DeterministicWallet.hardened
import fr.acinq.bitcoin.crypto.Pack
import fr.acinq.lightning.Lightning.secureRandom
import fr.acinq.lightning.NodeParams.Chain
import fr.acinq.lightning.crypto.LocalKeyManager.Companion.channelKeyPath

/**
Expand Down Expand Up @@ -34,7 +33,7 @@ import fr.acinq.lightning.crypto.LocalKeyManager.Companion.channelKeyPath
* @param seed seed from which the channel keys will be derived
* @param remoteSwapInExtendedPublicKey xpub belonging to our swap-in server, that must be used in our swap address
*/
data class LocalKeyManager(val seed: ByteVector, val chain: Chain, val remoteSwapInExtendedPublicKey: String) : KeyManager {
data class LocalKeyManager(val seed: ByteVector, val chain: Bitcoin.Chain, val remoteSwapInExtendedPublicKey: String) : KeyManager {

private val master = DeterministicWallet.generate(seed)

Expand All @@ -47,7 +46,7 @@ data class LocalKeyManager(val seed: ByteVector, val chain: Chain, val remoteSwa
override val swapInOnChainWallet: KeyManager.SwapInOnChainKeys = run {
val (prefix, xpub) = DeterministicWallet.ExtendedPublicKey.decode(remoteSwapInExtendedPublicKey)
val expectedPrefix = when (chain) {
Chain.Mainnet -> DeterministicWallet.xpub
Bitcoin.Chain.Mainnet -> DeterministicWallet.xpub
else -> DeterministicWallet.tpub
}
require(prefix == expectedPrefix) { "unexpected swap-in xpub prefix $prefix (expected $expectedPrefix)" }
Expand Down Expand Up @@ -150,21 +149,21 @@ data class LocalKeyManager(val seed: ByteVector, val chain: Chain, val remoteSwa
return KeyPath(path)
}

fun channelKeyBasePath(chain: Chain) = when (chain) {
Chain.Regtest, Chain.Testnet -> KeyPath.empty / hardened(48) / hardened(1)
Chain.Mainnet -> KeyPath.empty / hardened(50) / hardened(1)
fun channelKeyBasePath(chain: Bitcoin.Chain) = when (chain) {
Bitcoin.Chain.Regtest, Bitcoin.Chain.Testnet, Bitcoin.Chain.Signet -> KeyPath.empty / hardened(48) / hardened(1)
Bitcoin.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 -> KeyPath.empty / hardened(46) / hardened(0)
Chain.Mainnet -> KeyPath.empty / hardened(47) / hardened(0)
fun eclairNodeKeyBasePath(chain: Bitcoin.Chain) = when (chain) {
Bitcoin.Chain.Regtest, Bitcoin.Chain.Testnet, Bitcoin.Chain.Signet -> KeyPath.empty / hardened(46) / hardened(0)
Bitcoin.Chain.Mainnet -> KeyPath.empty / hardened(47) / hardened(0)
}

fun nodeKeyBasePath(chain: Chain) = when (chain) {
Chain.Regtest, Chain.Testnet -> KeyPath.empty / hardened(48) / hardened(0)
Chain.Mainnet -> KeyPath.empty / hardened(50) / hardened(0)
fun nodeKeyBasePath(chain: Bitcoin.Chain) = when (chain) {
Bitcoin.Chain.Regtest, Bitcoin.Chain.Testnet, Bitcoin.Chain.Signet -> KeyPath.empty / hardened(48) / hardened(0)
Bitcoin.Chain.Mainnet -> KeyPath.empty / hardened(50) / hardened(0)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import fr.acinq.bitcoin.crypto.musig2.IndividualNonce
import fr.acinq.bitcoin.crypto.musig2.Musig2
import fr.acinq.bitcoin.crypto.musig2.SecretNonce
import fr.acinq.bitcoin.utils.Either
import fr.acinq.lightning.NodeParams

/**
* new swap-in protocol based on musig2 and taproot: (user key + server key) OR (user refund key + delay)
Expand All @@ -24,7 +23,7 @@ data class SwapInProtocol(val userPublicKey: PublicKey, val serverPublicKey: Pub
private val scriptTree = ScriptTree.Leaf(0, refundScript)
val pubkeyScript: List<ScriptElt> = Script.pay2tr(internalPublicKey, scriptTree)

fun address(chain: NodeParams.Chain): String = Bitcoin.addressFromPublicKeyScript(chain.chainHash, pubkeyScript).right!!
fun address(chain: Bitcoin.Chain): String = Bitcoin.addressFromPublicKeyScript(chain.chainHash, pubkeyScript).right!!

fun witness(fundingTx: Transaction, index: Int, parentTxOuts: List<TxOut>, userNonce: IndividualNonce, serverNonce: IndividualNonce, userPartialSig: ByteVector32, serverPartialSig: ByteVector32): Either<Throwable, ScriptWitness> {
val publicKeys = listOf(userPublicKey, serverPublicKey)
Expand Down Expand Up @@ -56,10 +55,10 @@ data class SwapInProtocol(val userPublicKey: PublicKey, val serverPublicKey: Pub
}

companion object {
fun privateDescriptor(chain: NodeParams.Chain, userPublicKey: PublicKey, serverPublicKey: PublicKey, refundDelay: Int, masterRefundKey: DeterministicWallet.ExtendedPrivateKey): String {
fun privateDescriptor(chain: Bitcoin.Chain, userPublicKey: PublicKey, serverPublicKey: PublicKey, refundDelay: Int, masterRefundKey: DeterministicWallet.ExtendedPrivateKey): String {
val internalPubKey = Musig2.aggregateKeys(listOf(userPublicKey, serverPublicKey))
val prefix = when (chain) {
NodeParams.Chain.Mainnet -> DeterministicWallet.xprv
Bitcoin.Chain.Mainnet -> DeterministicWallet.xprv
else -> DeterministicWallet.tprv
}
val xpriv = DeterministicWallet.encode(masterRefundKey, prefix)
Expand All @@ -68,10 +67,10 @@ data class SwapInProtocol(val userPublicKey: PublicKey, val serverPublicKey: Pub
return "$desc#$checksum"
}

fun publicDescriptor(chain: NodeParams.Chain, userPublicKey: PublicKey, serverPublicKey: PublicKey, refundDelay: Int, masterRefundKey: DeterministicWallet.ExtendedPublicKey): String {
fun publicDescriptor(chain: Bitcoin.Chain, userPublicKey: PublicKey, serverPublicKey: PublicKey, refundDelay: Int, masterRefundKey: DeterministicWallet.ExtendedPublicKey): String {
val internalPubKey = Musig2.aggregateKeys(listOf(userPublicKey, serverPublicKey))
val prefix = when (chain) {
NodeParams.Chain.Mainnet -> DeterministicWallet.xpub
Bitcoin.Chain.Mainnet -> DeterministicWallet.xpub
else -> DeterministicWallet.tpub
}
val xpub = DeterministicWallet.encode(masterRefundKey, prefix)
Expand Down Expand Up @@ -99,7 +98,7 @@ data class SwapInProtocolLegacy(val userPublicKey: PublicKey, val serverPublicKe

val pubkeyScript: List<ScriptElt> = Script.pay2wsh(redeemScript)

fun address(chain: NodeParams.Chain): String = Bitcoin.addressFromPublicKeyScript(chain.chainHash, pubkeyScript).right!!
fun address(chain: Bitcoin.Chain): String = Bitcoin.addressFromPublicKeyScript(chain.chainHash, pubkeyScript).right!!

fun witness(userSig: ByteVector64, serverSig: ByteVector64): ScriptWitness {
return ScriptWitness(listOf(Scripts.der(serverSig, SigHash.SIGHASH_ALL), Scripts.der(userSig, SigHash.SIGHASH_ALL), Script.write(redeemScript).byteVector()))
Expand Down Expand Up @@ -130,12 +129,12 @@ data class SwapInProtocolLegacy(val userPublicKey: PublicKey, val serverPublicKe
* @param refundDelay refund delay
* @return a p2wsh descriptor that can be imported in bitcoin core (from version 24 on) to recover user funds once the funding delay has passed.
*/
fun descriptor(chain: NodeParams.Chain, masterPublicKey: DeterministicWallet.ExtendedPublicKey, userExtendedPublicKey: DeterministicWallet.ExtendedPublicKey, remoteServerPublicKey: PublicKey, refundDelay: Int): String {
fun descriptor(chain: Bitcoin.Chain, masterPublicKey: DeterministicWallet.ExtendedPublicKey, userExtendedPublicKey: DeterministicWallet.ExtendedPublicKey, remoteServerPublicKey: PublicKey, refundDelay: Int): String {
// Since child public keys cannot be derived from a master xpub when hardened derivation is used,
// we need to provide the fingerprint of the master xpub and the hardened derivation path.
// This lets wallets that have access to the master xpriv derive the corresponding private and public keys.
val masterFingerprint = ByteVector(Crypto.hash160(masterPublicKey.publickeybytes).take(4).toByteArray())
val encodedChildKey = DeterministicWallet.encode(userExtendedPublicKey, testnet = chain != NodeParams.Chain.Mainnet)
val encodedChildKey = DeterministicWallet.encode(userExtendedPublicKey, testnet = chain != Bitcoin.Chain.Mainnet)
val userKey = "[${masterFingerprint.toHex()}/${userExtendedPublicKey.path.asString('h').removePrefix("m/")}]$encodedChildKey"
return "wsh(and_v(v:pk($userKey),or_d(pk(${remoteServerPublicKey.toHex()}),older($refundDelay))))"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package fr.acinq.lightning.blockchain.electrum

import fr.acinq.bitcoin.*
import fr.acinq.lightning.Lightning.randomBytes32
import fr.acinq.lightning.NodeParams
import fr.acinq.lightning.SwapInParams
import fr.acinq.lightning.blockchain.BITCOIN_FUNDING_DEPTHOK
import fr.acinq.lightning.blockchain.WatchEventConfirmed
Expand All @@ -25,7 +24,7 @@ class SwapInManagerTestsCommon : LightningTestSuite() {

private val dummyPubkey = PublicKey.fromHex("02eae982c8563a1c256ee9b4655af7d4c0dc545d1e5c350a68c5f8902cd4cf3021")
private val dummyScript = Script.pay2wpkh(dummyPubkey)
private val dummyAddress = Bitcoin.computeP2WpkhAddress(dummyPubkey, NodeParams.Chain.Regtest.chainHash)
private val dummyAddress = Bitcoin.computeP2WpkhAddress(dummyPubkey, Bitcoin.Chain.Regtest.chainHash)

@Test
fun `swap funds`() {
Expand Down
Loading

0 comments on commit 7b124e7

Please sign in to comment.