Skip to content

Commit

Permalink
Use a range of swap-in addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
sstone committed Jan 9, 2024
1 parent 3b231d8 commit e6ae214
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 17 deletions.
20 changes: 12 additions & 8 deletions src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt
Original file line number Diff line number Diff line change
Expand Up @@ -285,13 +285,16 @@ data class FundingContributions(val inputs: List<InteractiveTxInput.Outgoing>, v
0xfffffffdU,
swapInKeys.userPublicKey, swapInKeys.remoteServerPublicKey, swapInKeys.refundDelay)

else -> InteractiveTxInput.LocalSwapIn(
0,
i.previousTx.stripInputWitnesses(),
i.outputIndex.toLong(),
0xfffffffdU,
TxAddInputTlv.SwapInParams(swapInKeys.userPublicKey, swapInKeys.remoteServerPublicKey, swapInKeys.userRefundPublicKey, swapInKeys.refundDelay),
)
else -> {
val swapInProtocol = swapInKeys.getSwapInProtocol(i.previousTx.txOut[i.outputIndex].publicKeyScript)!!
InteractiveTxInput.LocalSwapIn(
0,
i.previousTx.stripInputWitnesses(),
i.outputIndex.toLong(),
0xfffffffdU,
TxAddInputTlv.SwapInParams(swapInProtocol.userPublicKey, swapInProtocol.serverPublicKey, swapInProtocol.userRefundKey, swapInProtocol.refundDelay),
)
}
}
}
return if (params.isInitiator) {
Expand Down Expand Up @@ -674,7 +677,8 @@ data class InteractiveTxSession(
}

is InteractiveTxInput.LocalSwapIn -> {
val swapInParams = TxAddInputTlv.SwapInParams(swapInKeys.userPublicKey, swapInKeys.remoteServerPublicKey, swapInKeys.userRefundPublicKey, swapInKeys.refundDelay)
val swapInProtocol = swapInKeys.getSwapInProtocol(msg.value.previousTx.txOut[msg.value.previousTxOutput.toInt()].publicKeyScript)!!
val swapInParams = TxAddInputTlv.SwapInParams(swapInProtocol.userPublicKey, swapInProtocol.serverPublicKey, swapInProtocol.userRefundKey, swapInProtocol.refundDelay)
TxAddInput(fundingParams.channelId, msg.value.serialId, msg.value.previousTx, msg.value.previousTxOutput, msg.value.sequence, TlvStream(swapInParams))
}

Expand Down
24 changes: 19 additions & 5 deletions src/commonMain/kotlin/fr/acinq/lightning/crypto/KeyManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,24 @@ interface KeyManager {
val userPrivateKey: PrivateKey = userExtendedPrivateKey.privateKey
val userPublicKey: PublicKey = userPrivateKey.publicKey()

val userRefundPrivateKey: PrivateKey = DeterministicWallet.derivePrivateKey(userRefundExtendedPrivateKey, 0).privateKey
val userRefundPublicKey: PublicKey = userRefundPrivateKey.publicKey()

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

// legacy p2wsh-based swap-in protocol, with a fixed on-chain address
val legacySwapInProtocol = SwapInProtocolLegacy(userPublicKey, remoteServerPublicKey, refundDelay)

val swapInProtocol = SwapInProtocol(userPublicKey, remoteServerPublicKey, userRefundPublicKey, refundDelay)
val swapInProtocols = (0 until 100).map {
val userRefundPrivateKey: PrivateKey = DeterministicWallet.derivePrivateKey(userRefundExtendedPrivateKey, it.toLong()).privateKey
val userRefundPublicKey: PublicKey = userRefundPrivateKey.publicKey()
val protocol = SwapInProtocol(userPublicKey, remoteServerPublicKey, userRefundPublicKey, refundDelay)
protocol
}

/**
* @param pubkeyScript public key script
* @return the swap-in protocol that matches the input public key script
*/
fun getSwapInProtocol(pubkeyScript: ByteVector): SwapInProtocol? = swapInProtocols.find { it.serializedPubkeyScript == pubkeyScript }

/**
* The output script descriptor matching our legacy swap-in addresses.
Expand All @@ -158,6 +166,9 @@ interface KeyManager {
}

fun signSwapInputUser(fundingTx: Transaction, index: Int, parentTxOuts: List<TxOut>, userNonce: SecretNonce, commonNonce: AggregatedNonce): ByteVector32 {
val spentOutput = parentTxOuts[index]
val swapInProtocol = swapInProtocols.find { it.serializedPubkeyScript == spentOutput.publicKeyScript }
require(swapInProtocol != null) { "cannot match swap-in input ${fundingTx.txid}:$index" }
return swapInProtocol.signSwapInputUser(fundingTx, index, parentTxOuts, userPrivateKey, userNonce, commonNonce)
}

Expand All @@ -169,7 +180,7 @@ interface KeyManager {
* @return a signed transaction that spends our swap-in transaction. It cannot be published until `swapInTx` has enough confirmations
*/
fun createRecoveryTransaction(swapInTx: Transaction, address: String, feeRate: FeeratePerKw): Transaction? {
val utxos = swapInTx.txOut.filter { it.publicKeyScript.contentEquals(Script.write(legacySwapInProtocol.pubkeyScript)) || it.publicKeyScript.contentEquals(Script.write(swapInProtocol.pubkeyScript))}
val utxos = swapInTx.txOut.filter { it.publicKeyScript.contentEquals(Script.write(legacySwapInProtocol.pubkeyScript)) || swapInProtocols.find { p -> p.serializedPubkeyScript == it.publicKeyScript } != null }
return if (utxos.isEmpty()) {
null
} else {
Expand All @@ -188,6 +199,9 @@ interface KeyManager {
val sig = legacySwapInProtocol.signSwapInputUser(tx, index, utxo, userPrivateKey)
tx.updateWitness(index, legacySwapInProtocol.witnessRefund(sig))
} else {
val i = swapInProtocols.indexOfFirst { it.serializedPubkeyScript == utxo.publicKeyScript }
val userRefundPrivateKey: PrivateKey = DeterministicWallet.derivePrivateKey(userRefundExtendedPrivateKey, i.toLong()).privateKey
val swapInProtocol = swapInProtocols[i]
val sig = swapInProtocol.signSwapInputRefund(tx, index, utxos, userRefundPrivateKey)
tx.updateWitness(index, swapInProtocol.witnessRefund(sig))
}
Expand Down
2 changes: 1 addition & 1 deletion src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ class Peer(

val swapInWallet = ElectrumMiniWallet(nodeParams.chainHash, watcher.client, scope, nodeParams.loggerFactory, name = "swap-in")
val legacySwapInAddress: String = nodeParams.keyManager.swapInOnChainWallet.legacySwapInProtocol.address(nodeParams.chain).also { swapInWallet.addAddress(it) }
val swapInAddress: String = nodeParams.keyManager.swapInOnChainWallet.swapInProtocol.address(nodeParams.chain).also { swapInWallet.addAddress(it) }
val swapInAddresses: List<String> = nodeParams.keyManager.swapInOnChainWallet.swapInProtocols.map { it.address(nodeParams.chain) }.onEach { swapInWallet.addAddress(it) }

private var swapInJob: Job? = null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class SwapInProtocol(val userPublicKey: PublicKey, val serverPublicKey: PublicKe
val commonPubKey = commonPubKeyAndParity.first
private val parity = commonPubKeyAndParity.second
val pubkeyScript: List<ScriptElt> = Script.pay2tr(commonPubKey)
val serializedPubkeyScript = Script.write(pubkeyScript).byteVector()

private val executionData = Script.ExecutionData(annex = null, tapleafHash = merkleRoot)
private val controlBlock = byteArrayOf((Script.TAPROOT_LEAF_TAPSCRIPT + (if (parity) 1 else 0)).toByte()) + internalPubKey.value.toByteArray()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ object TestsHelper {
}

fun createWallet(keyManager: KeyManager, amount: Satoshi): Pair<PrivateKey, List<WalletState.Utxo>> {
val (privateKey, script) = keyManager.swapInOnChainWallet.run { Pair(userPrivateKey, swapInProtocol.pubkeyScript) }
val (privateKey, script) = keyManager.swapInOnChainWallet.run { Pair(userPrivateKey, swapInProtocols[0].pubkeyScript) }
val parentTx = Transaction(2, listOf(TxIn(OutPoint(TxId(randomBytes32()), 3), 0)), listOf(TxOut(amount, script)), 0)
return privateKey to listOf(WalletState.Utxo(parentTx, 0, 42))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ class LocalKeyManagerTestsCommon : LightningTestSuite() {
TxOut(Satoshi(100000), TestConstants.Alice.keyManager.swapInOnChainWallet.legacySwapInProtocol.pubkeyScript),
TxOut(Satoshi(150000), TestConstants.Alice.keyManager.swapInOnChainWallet.legacySwapInProtocol.pubkeyScript),
TxOut(Satoshi(150000), Script.pay2wpkh(randomKey().publicKey())),
TxOut(Satoshi(100000), TestConstants.Alice.keyManager.swapInOnChainWallet.swapInProtocol.pubkeyScript),
TxOut(Satoshi(150000), TestConstants.Alice.keyManager.swapInOnChainWallet.swapInProtocol.pubkeyScript),
TxOut(Satoshi(100000), TestConstants.Alice.keyManager.swapInOnChainWallet.swapInProtocols[0].pubkeyScript),
TxOut(Satoshi(150000), TestConstants.Alice.keyManager.swapInOnChainWallet.swapInProtocols[1].pubkeyScript),
TxOut(Satoshi(150000), Script.pay2wpkh(randomKey().publicKey()))
),
lockTime = 0)
Expand Down

0 comments on commit e6ae214

Please sign in to comment.