Skip to content

Commit a83fb4c

Browse files
committed
Move nonce exchange into the interactive tx session
The exchange of Musig2 nonces is done in the interactive tx session, and are attached to the `tx_complete` message. There are 2 types of nonces: - "funding nonces", which are used to sign a new funding tx that spends the current funding tx (splice, rbf). - "commit nonces", which are used to sign the commit tx that is one of the outputs of the interactive session. "funding nonces" can be randomly generated on-the-fly: either the interactive session will fail, and they can be forgotten, or it will succeed and we'll get a new, fully signed funding tx. "commit nonces" can be deterministically generated. This make nonce exchange simpler to reason about: - when we send `tx_complete`, we know exactly what the funding tx and commit tx will be (so the funding tx id can be mixed in the nonce generation process). - dual funding, splice and rbf message do not need to be modified Channel re-establishment becomes a bit more complex, as one node could still be waiting for signatures while the other has completed the splice workflow, but it can be mitigated by storing the last sent commit_sig and re-sending it again if needed.
1 parent 4dd0fef commit a83fb4c

28 files changed

+1360
-609
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala

Lines changed: 73 additions & 55 deletions
Large diffs are not rendered by default.

eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ object Helpers {
168168
if (open.dustLimit > nodeParams.channelConf.maxRemoteDustLimit) return Left(DustLimitTooLarge(open.temporaryChannelId, open.dustLimit, nodeParams.channelConf.maxRemoteDustLimit))
169169

170170
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
171-
if ((channelFeatures.hasFeature(Features.SimpleTaproot) || channelFeatures.hasFeature(Features.SimpleTaprootStaging)) && open.tlvStream.get[ChannelTlv.NextLocalNoncesTlv].isEmpty) return Left(MissingNextLocalNonce(open.temporaryChannelId))
172171

173172
// BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large.
174173
val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingAmount)
@@ -268,7 +267,6 @@ object Helpers {
268267
liquidityPurchase_opt <- LiquidityAds.validateRemoteFunding(open.requestFunding_opt, remoteNodeId, accept.temporaryChannelId, fundingScript, accept.fundingAmount, open.fundingFeerate, isChannelCreation = true, accept.willFund_opt)
269268
} yield {
270269
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
271-
if ((channelFeatures.hasFeature(Features.SimpleTaproot) || channelFeatures.hasFeature(Features.SimpleTaprootStaging)) && accept.tlvStream.get[ChannelTlv.NextLocalNoncesTlv].isEmpty) return Left(MissingNextLocalNonce(open.temporaryChannelId))
272270
(channelFeatures, script_opt, liquidityPurchase_opt)
273271
}
274272
}
@@ -529,7 +527,10 @@ object Helpers {
529527
val localPerCommitmentSecret = keyManager.commitmentSecret(channelKeyPath, commitments.localCommitIndex - 1)
530528
val localNextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommitIndex + 1)
531529
val tlvStream: TlvStream[RevokeAndAckTlv] = if (commitments.params.commitmentFormat.useTaproot) {
532-
val nonces = commitments.active.map(c => keyManager.verificationNonce(commitments.params.localParams.fundingKeyPath, c.fundingTxIndex, channelKeyPath, commitments.localCommitIndex + 1))
530+
val nonces = commitments.active.map(c => {
531+
val fundingPubkey = keyManager.fundingPublicKey(commitments.params.localParams.fundingKeyPath, c.fundingTxIndex).publicKey
532+
keyManager.verificationNonce(c.fundingTxId, fundingPubkey, commitments.localCommitIndex + 1)
533+
})
533534
TlvStream(RevokeAndAckTlv.NextLocalNoncesTlv(nonces.map(_._2).toList))
534535
} else {
535536
TlvStream.empty
@@ -725,23 +726,21 @@ object Helpers {
725726
val (closingTx, closingSigned) = makeClosingTx(keyManager, commitment, localScriptPubkey, remoteScriptPubkey, ClosingFees(remoteClosingFee, remoteClosingFee, remoteClosingFee), Some(localClosingNonce), Some(remoteClosingNonce))
726727
if (checkClosingDustAmounts(closingTx)) {
727728
log.info("using closing nonce = {} remote closing nonce = {}", localClosingNonce, remoteClosingNonce)
728-
val Right(localClosingPartialSig) = keyManager.partialSign(
729-
closingTx,
730-
keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex), commitment.remoteFundingPubKey,
731-
TxOwner.Local,
732-
localClosingNonce, remoteClosingNonce,
733-
)
734729
val fundingPubKey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex)
735-
val Right(aggSig) = Musig2.aggregateTaprootSignatures(
736-
Seq(localClosingPartialSig, remoteClosingPartialSig), closingTx.tx, closingTx.tx.txIn.indexWhere(_.outPoint == closingTx.input.outPoint),
737-
Seq(closingTx.input.txOut),
738-
Scripts.sort(Seq(fundingPubKey.publicKey, commitment.remoteFundingPubKey)),
739-
Seq(localClosingNonce._2, remoteClosingNonce),
740-
None)
741-
val signedClosingTx = Transactions.addAggregatedSignature(closingTx, aggSig)
742-
Transactions.checkSpendable(signedClosingTx) match {
743-
case Success(_) => Right(signedClosingTx, closingSigned)
744-
case _ => Left(InvalidCloseSignature(commitment.channelId, signedClosingTx.tx.txid))
730+
731+
(for {
732+
localClosingPartialSig <- keyManager.partialSign(closingTx, fundingPubKey, commitment.remoteFundingPubKey, TxOwner.Local, localClosingNonce, remoteClosingNonce)
733+
inputIndex = closingTx.tx.txIn.indexWhere(_.outPoint == closingTx.input.outPoint)
734+
aggSig <- Musig2.aggregateTaprootSignatures(
735+
Seq(localClosingPartialSig, remoteClosingPartialSig),
736+
closingTx.tx, inputIndex, Seq(closingTx.input.txOut),
737+
Scripts.sort(Seq(fundingPubKey.publicKey, commitment.remoteFundingPubKey)),
738+
Seq(localClosingNonce._2, remoteClosingNonce),
739+
None)
740+
signedClosingTx = Transactions.addAggregatedSignature(closingTx, aggSig)
741+
} yield signedClosingTx) match {
742+
case Right(signedClosingTx) if Transactions.checkSpendable(signedClosingTx).isSuccess => Right(signedClosingTx, closingSigned)
743+
case _ => Left(InvalidCloseSignature(commitment.channelId, closingTx.tx.txid))
745744
}
746745
} else {
747746
Left(InvalidCloseAmountBelowDust(commitment.channelId, closingTx.tx.txid))

0 commit comments

Comments
 (0)