Skip to content

Commit dc983d7

Browse files
authored
Refactor ChannelKeys and KeyManager (#798)
We refactor our `ChannelKeys` to use the same model as `eclair`. This is a first step before refactoring transactions and channel data similarly to what was done in `eclair` in preparation for taproot channels. Backwards-compatibility is guaranteed by our unit tests for the local key manager and transactions building.
1 parent bec708b commit dc983d7

File tree

24 files changed

+898
-848
lines changed

24 files changed

+898
-848
lines changed

modules/core/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWallet.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import co.touchlab.kermit.Logger
44
import fr.acinq.bitcoin.*
55
import fr.acinq.lightning.SwapInParams
66
import fr.acinq.lightning.blockchain.fee.FeeratePerKw
7-
import fr.acinq.lightning.crypto.KeyManager
7+
import fr.acinq.lightning.crypto.SwapInOnChainKeys
88
import fr.acinq.lightning.logging.debug
99
import fr.acinq.lightning.logging.info
1010
import fr.acinq.lightning.utils.sat
@@ -96,9 +96,9 @@ data class WalletState(val addresses: Map<String, AddressState>) {
9696
}.coerceAtLeast(0)
9797

9898
/** Builds a transaction spending all expired utxos and computes the mining fee. The transaction is fully signed but not published. */
99-
fun spendExpiredSwapIn(swapInKeys: KeyManager.SwapInOnChainKeys, scriptPubKey: ByteVector, feerate: FeeratePerKw): Pair<Transaction, Satoshi>? {
99+
fun spendExpiredSwapIn(swapInKeys: SwapInOnChainKeys, scriptPubKey: ByteVector, feerate: FeeratePerKw): Pair<Transaction, Satoshi>? {
100100
val utxos = readyForRefund.map {
101-
KeyManager.SwapInOnChainKeys.SwapInUtxo(
101+
SwapInOnChainKeys.SwapInUtxo(
102102
txOut = it.txOut,
103103
outPoint = it.outPoint,
104104
addressIndex = it.addressMeta.indexOrNull

modules/core/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/FinalWallet.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package fr.acinq.lightning.blockchain.electrum
22

33
import fr.acinq.bitcoin.*
44
import fr.acinq.lightning.blockchain.fee.FeeratePerKw
5-
import fr.acinq.lightning.crypto.KeyManager
5+
import fr.acinq.lightning.crypto.Bip84OnChainKeys
66
import fr.acinq.lightning.logging.LoggerFactory
77
import fr.acinq.lightning.logging.info
88
import fr.acinq.lightning.transactions.Transactions
@@ -14,7 +14,7 @@ import kotlinx.coroutines.launch
1414

1515
class FinalWallet(
1616
private val chain: Chain,
17-
private val finalWalletKeys: KeyManager.Bip84OnChainKeys,
17+
private val finalWalletKeys: Bip84OnChainKeys,
1818
electrum: IElectrumClient,
1919
scope: CoroutineScope,
2020
loggerFactory: LoggerFactory

modules/core/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/SwapInWallet.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package fr.acinq.lightning.blockchain.electrum
22

33
import fr.acinq.bitcoin.Chain
4-
import fr.acinq.lightning.crypto.KeyManager
4+
import fr.acinq.lightning.crypto.SwapInOnChainKeys
55
import fr.acinq.lightning.logging.LoggerFactory
66
import fr.acinq.lightning.logging.info
77
import kotlinx.coroutines.CoroutineScope
@@ -13,7 +13,7 @@ import kotlinx.coroutines.launch
1313

1414
class SwapInWallet(
1515
chain: Chain,
16-
swapInKeys: KeyManager.SwapInOnChainKeys,
16+
swapInKeys: SwapInOnChainKeys,
1717
electrum: IElectrumClient,
1818
scope: CoroutineScope,
1919
loggerFactory: LoggerFactory

modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/ChannelCommand.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import fr.acinq.lightning.blockchain.WatchTriggered
77
import fr.acinq.lightning.blockchain.electrum.WalletState
88
import fr.acinq.lightning.blockchain.fee.FeeratePerKw
99
import fr.acinq.lightning.channel.states.PersistedChannelState
10-
import fr.acinq.lightning.crypto.KeyManager
10+
import fr.acinq.lightning.crypto.ChannelKeys
1111
import fr.acinq.lightning.utils.UUID
1212
import fr.acinq.lightning.wire.FailureMessage
1313
import fr.acinq.lightning.wire.LightningMessage
@@ -36,7 +36,7 @@ sealed class ChannelCommand {
3636
val requestRemoteFunding: LiquidityAds.RequestFunding?,
3737
val channelOrigin: Origin?,
3838
) : Init() {
39-
fun temporaryChannelId(keyManager: KeyManager): ByteVector32 = keyManager.channelKeys(localParams.fundingKeyPath).temporaryChannelId
39+
fun temporaryChannelId(channelKeys: ChannelKeys): ByteVector32 = (ByteVector(ByteArray(33) { 0 }) + channelKeys.revocationBasePoint.value).sha256()
4040
}
4141

4242
data class NonInitiator(

modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/Commitments.kt

Lines changed: 103 additions & 112 deletions
Large diffs are not rendered by default.

modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/Helpers.kt

Lines changed: 114 additions & 148 deletions
Large diffs are not rendered by default.

modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import fr.acinq.lightning.Lightning.randomBytes32
1313
import fr.acinq.lightning.MilliSatoshi
1414
import fr.acinq.lightning.blockchain.electrum.WalletState
1515
import fr.acinq.lightning.blockchain.fee.FeeratePerKw
16-
import fr.acinq.lightning.crypto.Bolt3Derivation.deriveForCommitment
16+
import fr.acinq.lightning.crypto.ChannelKeys
1717
import fr.acinq.lightning.crypto.KeyManager
18+
import fr.acinq.lightning.crypto.SwapInOnChainKeys
1819
import fr.acinq.lightning.logging.MDCLogger
1920
import fr.acinq.lightning.transactions.*
2021
import fr.acinq.lightning.utils.*
@@ -29,7 +30,7 @@ import kotlinx.coroutines.CompletableDeferred
2930
sealed class SharedFundingInput {
3031
abstract val info: Transactions.InputInfo
3132
abstract val weight: Int
32-
abstract fun sign(channelKeys: KeyManager.ChannelKeys, tx: Transaction): ByteVector64
33+
abstract fun sign(channelKeys: ChannelKeys, tx: Transaction): ByteVector64
3334

3435
data class Multisig2of2(override val info: Transactions.InputInfo, val fundingTxIndex: Long, val remoteFundingPubkey: PublicKey) : SharedFundingInput() {
3536

@@ -42,7 +43,7 @@ sealed class SharedFundingInput {
4243
// This value was computed assuming 73 bytes signatures (worst-case scenario).
4344
override val weight: Int = Multisig2of2.weight
4445

45-
override fun sign(channelKeys: KeyManager.ChannelKeys, tx: Transaction): ByteVector64 {
46+
override fun sign(channelKeys: ChannelKeys, tx: Transaction): ByteVector64 {
4647
val fundingKey = channelKeys.fundingKey(fundingTxIndex)
4748
return Transactions.sign(Transactions.TransactionWithInputInfo.SpliceTx(info, tx), fundingKey)
4849
}
@@ -94,9 +95,9 @@ data class InteractiveTxParams(
9495
// BOLT 2: the initiator's serial IDs MUST use even values and the non-initiator odd values.
9596
val serialIdParity = if (isInitiator) 0 else 1
9697

97-
fun fundingPubkeyScript(channelKeys: KeyManager.ChannelKeys): ByteVector {
98+
fun fundingPubkeyScript(channelKeys: ChannelKeys): ByteVector {
9899
val fundingTxIndex = (sharedInput as? SharedFundingInput.Multisig2of2)?.let { it.fundingTxIndex + 1 } ?: 0
99-
return Helpers.Funding.makeFundingPubKeyScript(channelKeys.fundingPubKey(fundingTxIndex), remoteFundingPubkey)
100+
return Helpers.Funding.makeFundingPubKeyScript(channelKeys.fundingKey(fundingTxIndex).publicKey(), remoteFundingPubkey)
100101
}
101102

102103
fun liquidityFees(purchase: LiquidityAds.Purchase?): MilliSatoshi = purchase?.let { l ->
@@ -266,8 +267,8 @@ data class FundingContributions(val inputs: List<InteractiveTxInput.Outgoing>, v
266267
* @param walletInputs 2-of-2 swap-in wallet inputs.
267268
*/
268269
fun create(
269-
channelKeys: KeyManager.ChannelKeys,
270-
swapInKeys: KeyManager.SwapInOnChainKeys,
270+
channelKeys: ChannelKeys,
271+
swapInKeys: SwapInOnChainKeys,
271272
params: InteractiveTxParams,
272273
walletInputs: List<WalletState.Utxo>,
273274
liquidityPurchase: LiquidityAds.Purchase?
@@ -282,8 +283,8 @@ data class FundingContributions(val inputs: List<InteractiveTxInput.Outgoing>, v
282283
* @param changePubKey if provided, a corresponding p2wpkh change output will be created.
283284
*/
284285
fun create(
285-
channelKeys: KeyManager.ChannelKeys,
286-
swapInKeys: KeyManager.SwapInOnChainKeys,
286+
channelKeys: ChannelKeys,
287+
swapInKeys: SwapInOnChainKeys,
287288
params: InteractiveTxParams,
288289
sharedUtxo: Pair<SharedFundingInput, SharedFundingInputBalances>?,
289290
walletInputs: List<WalletState.Utxo>,
@@ -460,7 +461,8 @@ data class SharedTransaction(
460461

461462
fun sign(session: InteractiveTxSession, keyManager: KeyManager, fundingParams: InteractiveTxParams, localParams: LocalParams, remoteNodeId: PublicKey): PartiallySignedSharedTransaction {
462463
val unsignedTx = buildUnsignedTx()
463-
val sharedSig = fundingParams.sharedInput?.sign(keyManager.channelKeys(localParams.fundingKeyPath), unsignedTx)
464+
val channelKeys = keyManager.channelKeys(localParams.fundingKeyPath)
465+
val sharedSig = fundingParams.sharedInput?.sign(channelKeys, unsignedTx)
464466
// NB: the order in this list must match the order of the transaction's inputs.
465467
val previousOutputs = unsignedTx.txIn.map { spentOutputs[it.outPoint]!! }
466468

@@ -538,7 +540,7 @@ data class PartiallySignedSharedTransaction(override val tx: SharedTransaction,
538540
override val txId: TxId = localSigs.txId
539541
override val signedTx = null
540542

541-
fun addRemoteSigs(channelKeys: KeyManager.ChannelKeys, fundingParams: InteractiveTxParams, remoteSigs: TxSignatures): FullySignedSharedTransaction? {
543+
fun addRemoteSigs(channelKeys: ChannelKeys, fundingParams: InteractiveTxParams, remoteSigs: TxSignatures): FullySignedSharedTransaction? {
542544
if (localSigs.swapInUserSigs.size != tx.localInputs.filterIsInstance<InteractiveTxInput.LocalLegacySwapIn>().size) return null
543545
if (localSigs.swapInUserPartialSigs.size != tx.localInputs.filterIsInstance<InteractiveTxInput.LocalSwapIn>().size) return null
544546
if (remoteSigs.swapInUserSigs.size != tx.remoteInputs.filterIsInstance<InteractiveTxInput.RemoteLegacySwapIn>().size) return null
@@ -552,7 +554,7 @@ data class PartiallySignedSharedTransaction(override val tx: SharedTransaction,
552554
is SharedFundingInput.Multisig2of2 -> Scripts.witness2of2(
553555
localSigs.previousFundingTxSig ?: return null,
554556
remoteSigs.previousFundingTxSig ?: return null,
555-
channelKeys.fundingPubKey(it.fundingTxIndex),
557+
channelKeys.fundingKey(it.fundingTxIndex).publicKey(),
556558
it.remoteFundingPubkey,
557559
)
558560
}
@@ -641,8 +643,8 @@ sealed class InteractiveTxSessionAction {
641643

642644
data class InteractiveTxSession(
643645
val remoteNodeId: PublicKey,
644-
val channelKeys: KeyManager.ChannelKeys,
645-
val swapInKeys: KeyManager.SwapInOnChainKeys,
646+
val channelKeys: ChannelKeys,
647+
val swapInKeys: SwapInOnChainKeys,
646648
val fundingParams: InteractiveTxParams,
647649
val previousFunding: SharedFundingInputBalances,
648650
val toSend: List<Either<InteractiveTxInput.Outgoing, InteractiveTxOutput.Outgoing>>,
@@ -675,8 +677,8 @@ data class InteractiveTxSession(
675677

676678
constructor(
677679
remoteNodeId: PublicKey,
678-
channelKeys: KeyManager.ChannelKeys,
679-
swapInKeys: KeyManager.SwapInOnChainKeys,
680+
channelKeys: ChannelKeys,
681+
swapInKeys: SwapInOnChainKeys,
680682
fundingParams: InteractiveTxParams,
681683
previousLocalBalance: MilliSatoshi,
682684
previousRemoteBalance: MilliSatoshi,
@@ -1034,12 +1036,22 @@ data class InteractiveTxSigningSession(
10341036
is Either.Right -> localCommit.value.index + 1
10351037
}
10361038

1037-
fun receiveCommitSig(channelKeys: KeyManager.ChannelKeys, channelParams: ChannelParams, remoteCommitSig: CommitSig, currentBlockHeight: Long, logger: MDCLogger): Pair<InteractiveTxSigningSession, InteractiveTxSigningSessionAction> {
1039+
fun receiveCommitSig(channelKeys: ChannelKeys, channelParams: ChannelParams, remoteCommitSig: CommitSig, currentBlockHeight: Long, logger: MDCLogger): Pair<InteractiveTxSigningSession, InteractiveTxSigningSessionAction> {
10381040
return when (localCommit) {
10391041
is Either.Left -> {
10401042
val localCommitIndex = localCommit.value.index
1041-
val localPerCommitmentPoint = channelKeys.commitmentPoint(localCommitIndex)
1042-
when (val signedLocalCommit = LocalCommit.fromCommitSig(channelKeys, channelParams, fundingTxIndex, fundingParams.remoteFundingPubkey, commitInput, remoteCommitSig, localCommitIndex, localCommit.value.spec, localPerCommitmentPoint, logger)) {
1043+
val commitKeys = channelKeys.localCommitmentKeys(channelParams, localCommitIndex)
1044+
when (val signedLocalCommit = LocalCommit.fromCommitSig(
1045+
channelParams = channelParams,
1046+
commitKeys = commitKeys,
1047+
fundingKey = channelKeys.fundingKey(fundingTxIndex),
1048+
remoteFundingPubKey = fundingParams.remoteFundingPubkey,
1049+
commitInput = commitInput,
1050+
commit = remoteCommitSig,
1051+
localCommitIndex = localCommitIndex,
1052+
spec = localCommit.value.spec,
1053+
log = logger
1054+
)) {
10431055
is Either.Left -> {
10441056
val fundingKey = channelKeys.fundingKey(fundingTxIndex)
10451057
val localSigOfLocalTx = Transactions.sign(localCommit.value.commitTx, fundingKey)
@@ -1067,7 +1079,7 @@ data class InteractiveTxSigningSession(
10671079
}
10681080
}
10691081

1070-
fun receiveTxSigs(channelKeys: KeyManager.ChannelKeys, remoteTxSigs: TxSignatures, currentBlockHeight: Long): Either<InteractiveTxSigningSessionAction.AbortFundingAttempt, InteractiveTxSigningSessionAction.SendTxSigs> {
1082+
fun receiveTxSigs(channelKeys: ChannelKeys, remoteTxSigs: TxSignatures, currentBlockHeight: Long): Either<InteractiveTxSigningSessionAction.AbortFundingAttempt, InteractiveTxSigningSessionAction.SendTxSigs> {
10711083
return when (localCommit) {
10721084
is Either.Left -> Either.Left(InteractiveTxSigningSessionAction.AbortFundingAttempt(UnexpectedFundingSignatures(fundingParams.channelId)))
10731085
is Either.Right -> when (val fullySignedTx = fundingTx.addRemoteSigs(channelKeys, fundingParams, remoteTxSigs)) {
@@ -1100,42 +1112,43 @@ data class InteractiveTxSigningSession(
11001112
localHtlcs: Set<DirectedHtlc>
11011113
): Either<ChannelException, Pair<InteractiveTxSigningSession, CommitSig>> {
11021114
val channelKeys = channelParams.localParams.channelKeys(keyManager)
1115+
val fundingKey = channelKeys.fundingKey(fundingTxIndex)
1116+
val localCommitKeys = channelKeys.localCommitmentKeys(channelParams, localCommitmentIndex)
1117+
val remoteCommitKeys = channelKeys.remoteCommitmentKeys(channelParams, remotePerCommitmentPoint)
11031118
val unsignedTx = sharedTx.buildUnsignedTx()
11041119
val sharedOutputIndex = unsignedTx.txOut.indexOfFirst { it.publicKeyScript == fundingParams.fundingPubkeyScript(channelKeys) }
11051120
val liquidityFees = fundingParams.liquidityFees(liquidityPurchase)
11061121
return Helpers.Funding.makeCommitTxs(
1107-
channelKeys,
1108-
channelParams.channelId,
1109-
channelParams.localParams, channelParams.remoteParams,
1122+
channelParams = channelParams,
11101123
fundingAmount = sharedTx.sharedOutput.amount,
11111124
toLocal = sharedTx.sharedOutput.localAmount - liquidityFees,
11121125
toRemote = sharedTx.sharedOutput.remoteAmount + liquidityFees,
11131126
localHtlcs = localHtlcs,
11141127
localCommitmentIndex = localCommitmentIndex,
11151128
remoteCommitmentIndex = remoteCommitmentIndex,
1116-
commitTxFeerate,
1117-
fundingTxIndex = fundingTxIndex, fundingTxId = unsignedTx.txid, fundingTxOutputIndex = sharedOutputIndex,
1129+
commitTxFeerate = commitTxFeerate,
1130+
fundingTxId = unsignedTx.txid,
1131+
fundingTxOutputIndex = sharedOutputIndex,
1132+
localFundingKey = fundingKey,
11181133
remoteFundingPubkey = fundingParams.remoteFundingPubkey,
1119-
remotePerCommitmentPoint = remotePerCommitmentPoint
1134+
localCommitKeys = localCommitKeys,
1135+
remoteCommitKeys = remoteCommitKeys,
11201136
).map { firstCommitTx ->
1121-
val localSigOfRemoteCommitTx = Transactions.sign(firstCommitTx.remoteCommitTx, channelKeys.fundingKey(fundingTxIndex))
1122-
val localSigsOfRemoteHtlcTxs = firstCommitTx.remoteHtlcTxs.map { Transactions.sign(it, channelKeys.htlcKey.deriveForCommitment(remotePerCommitmentPoint), SigHash.SIGHASH_SINGLE or SigHash.SIGHASH_ANYONECANPAY) }
1123-
1137+
val localSigOfRemoteCommitTx = Transactions.sign(firstCommitTx.remoteCommitTx, fundingKey)
1138+
val localSigsOfRemoteHtlcTxs = firstCommitTx.remoteHtlcTxs.map { Transactions.sign(it, remoteCommitKeys.ourHtlcKey, SigHash.SIGHASH_SINGLE or SigHash.SIGHASH_ANYONECANPAY) }
11241139
val alternativeSigs = if (firstCommitTx.remoteHtlcTxs.isEmpty()) {
11251140
val commitSigTlvs = Commitments.alternativeFeerates.map { feerate ->
11261141
val alternativeSpec = firstCommitTx.remoteSpec.copy(feerate = feerate)
11271142
val (alternativeRemoteCommitTx, _) = Commitments.makeRemoteTxs(
1128-
channelKeys,
1129-
remoteCommitmentIndex,
1130-
channelParams.localParams,
1131-
channelParams.remoteParams,
1132-
fundingTxIndex,
1133-
fundingParams.remoteFundingPubkey,
1134-
firstCommitTx.remoteCommitTx.input,
1135-
remotePerCommitmentPoint,
1136-
alternativeSpec
1143+
channelParams = channelParams,
1144+
commitKeys = remoteCommitKeys,
1145+
commitTxNumber = remoteCommitmentIndex,
1146+
localFundingKey = fundingKey,
1147+
remoteFundingPubKey = fundingParams.remoteFundingPubkey,
1148+
commitmentInput = firstCommitTx.remoteCommitTx.input,
1149+
spec = alternativeSpec
11371150
)
1138-
val sig = Transactions.sign(alternativeRemoteCommitTx, channelKeys.fundingKey(fundingTxIndex))
1151+
val sig = Transactions.sign(alternativeRemoteCommitTx, fundingKey)
11391152
CommitSigTlv.AlternativeFeerateSig(feerate, sig)
11401153
}
11411154
TlvStream(CommitSigTlv.AlternativeFeerateSigs(commitSigTlvs) as CommitSigTlv)

0 commit comments

Comments
 (0)