Skip to content

Commit d1863f9

Browse files
authored
Create fresh shutdown nonce on reconnection (#3184)
When we disconnect after sending `shutdown`, we must re-send `shutdown`. When using taproot, we must generate a fresh nonce and store the private nonce locally, otherwise we won't be able to create our `closing_sig`.
1 parent cc75b13 commit d1863f9

File tree

2 files changed

+29
-13
lines changed

2 files changed

+29
-13
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import fr.acinq.eclair.blockchain._
3030
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher
3131
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
3232
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient
33-
import fr.acinq.eclair.channel.ChannelTypes.SimpleTaprootChannelsPhoenix
3433
import fr.acinq.eclair.channel.Commitments.PostRevocationAction
3534
import fr.acinq.eclair.channel.Helpers.Closing.MutualClose
3635
import fr.acinq.eclair.channel.Helpers.Syncing.SyncResult
@@ -2720,10 +2719,13 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
27202719
}
27212720

27222721
// BOLT 2: A node if it has sent a previous shutdown MUST retransmit shutdown.
2723-
d.localShutdown.foreach {
2724-
localShutdown =>
2725-
log.debug("re-sending local_shutdown")
2726-
sendQueue = sendQueue :+ localShutdown
2722+
val shutdown_opt = d.localShutdown match {
2723+
case None => None
2724+
case Some(shutdown) =>
2725+
log.debug("re-sending local shutdown")
2726+
val shutdown1 = createShutdown(commitments1, shutdown.scriptPubKey)
2727+
sendQueue = sendQueue :+ shutdown1
2728+
Some(shutdown1)
27272729
}
27282730

27292731
if (d.commitments.announceChannel) {
@@ -2754,7 +2756,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
27542756
peer ! ChannelReadyForPayments(self, remoteNodeId, d.channelId, fundingTxIndex)
27552757
}
27562758

2757-
goto(NORMAL) using d.copy(commitments = commitments1, spliceStatus = spliceStatus1) sending sendQueue
2759+
goto(NORMAL) using d.copy(commitments = commitments1, spliceStatus = spliceStatus1, localShutdown = shutdown_opt) sending sendQueue
27582760
}
27592761
}
27602762

@@ -2780,9 +2782,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
27802782
handleSyncFailure(channelReestablish, syncFailure, d)
27812783
case syncSuccess: SyncResult.Success =>
27822784
val commitments1 = d.commitments.discardUnsignedUpdates()
2783-
val sendQueue = Queue.empty[LightningMessage] ++ syncSuccess.retransmit :+ d.localShutdown
2785+
// We retransmit our shutdown: we may have updated our script and they may not have received it.
2786+
val shutdown = createShutdown(commitments1, d.localShutdown.scriptPubKey)
2787+
val sendQueue = Queue.empty[LightningMessage] ++ syncSuccess.retransmit :+ shutdown
27842788
// BOLT 2: A node if it has sent a previous shutdown MUST retransmit shutdown.
2785-
goto(SHUTDOWN) using d.copy(commitments = commitments1) sending sendQueue
2789+
goto(SHUTDOWN) using d.copy(commitments = commitments1, localShutdown = shutdown) sending sendQueue
27862790
}
27872791
}
27882792

eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import fr.acinq.eclair.reputation.Reputation
3636
import fr.acinq.eclair.testutils.PimpTestProbe.convert
3737
import fr.acinq.eclair.transactions.Transactions
3838
import fr.acinq.eclair.transactions.Transactions._
39-
import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ChannelReestablish, ChannelUpdate, ClosingSigned, CommitSig, Error, FailureMessageCodecs, FailureReason, Init, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc}
39+
import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ChannelReestablish, ChannelUpdate, ClosingComplete, ClosingSig, ClosingSigned, CommitSig, Error, FailureMessageCodecs, FailureReason, Init, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc}
4040
import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes32, randomKey}
4141
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
4242
import org.scalatest.{Outcome, Tag}
@@ -1023,10 +1023,14 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
10231023
alice2bob.forward(bob, channelReestablishAlice)
10241024
bob2alice.forward(alice, channelReestablishBob)
10251025
// They retransmit shutdown.
1026-
alice2bob.expectMsgType[Shutdown]
1027-
alice2bob.forward(bob)
1028-
bob2alice.expectMsgType[Shutdown]
1029-
bob2alice.forward(alice)
1026+
val shutdownAlice = alice2bob.expectMsgType[Shutdown]
1027+
alice2bob.forward(bob, shutdownAlice)
1028+
val shutdownBob = bob2alice.expectMsgType[Shutdown]
1029+
bob2alice.forward(alice, shutdownBob)
1030+
Seq(shutdownAlice, shutdownBob).foreach(shutdown => commitmentFormat match {
1031+
case _: SegwitV0CommitmentFormat => assert(shutdown.closeeNonce_opt.isEmpty)
1032+
case _: TaprootCommitmentFormat => assert(shutdown.closeeNonce_opt.nonEmpty)
1033+
})
10301034
// They resume HTLC settlement.
10311035
fulfillHtlc(0, r1, bob, alice, bob2alice, alice2bob)
10321036
crossSign(bob, alice, bob2alice, alice2bob)
@@ -1035,6 +1039,14 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
10351039
crossSign(bob, alice, bob2alice, alice2bob)
10361040
awaitCond(alice.stateName == NEGOTIATING_SIMPLE)
10371041
awaitCond(bob.stateName == NEGOTIATING_SIMPLE)
1042+
// They can now sign the closing transaction.
1043+
val closingCompleteAlice = alice2bob.expectMsgType[ClosingComplete]
1044+
alice2bob.forward(bob, closingCompleteAlice)
1045+
bob2alice.expectMsgType[ClosingComplete] // ignored
1046+
val closingTx = bob2blockchain.expectMsgType[PublishFinalTx]
1047+
val closingSigBob = bob2alice.expectMsgType[ClosingSig]
1048+
bob2alice.forward(alice, closingSigBob)
1049+
assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == closingTx.tx.txid)
10381050
}
10391051

10401052
test("recv INPUT_RESTORED", Tag(ChannelStateTestsTags.SimpleClose), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>

0 commit comments

Comments
 (0)