Skip to content

Commit ce27e4e

Browse files
authored
Merge pull request #9046 from Roasbeef/taproot-chan-sync-bug-fix
lnwallet: ensure we re-sign retransmitted commits for taproot channels
2 parents e8c5e7d + 80b2579 commit ce27e4e

File tree

4 files changed

+223
-41
lines changed

4 files changed

+223
-41
lines changed

docs/release-notes/release-notes-0.18.3.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ blinded path expiry.
8080
* [Fix a bug](https://github.com/lightningnetwork/lnd/pull/9039) that would
8181
cause UpdateAddHTLC message with blinding point fields to not be re-forwarded
8282
correctly on restart.
83+
84+
* [A bug related to sending dangling channel
85+
updates](https://github.com/lightningnetwork/lnd/pull/9046) after a
86+
reconnection for taproot channels has been fixed.
8387

8488
# New Features
8589
## Functional Enhancements

lnwallet/channel.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4151,6 +4151,27 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
41514151
}, nil
41524152
}
41534153

4154+
// resignMusigCommit is used to resign a commitment transaction for taproot
4155+
// channels when we need to retransmit a signature after a channel reestablish
4156+
// message. Taproot channels use musig2, which means we must use fresh nonces
4157+
// each time. After we receive the channel reestablish message, we learn the
4158+
// nonce we need to use for the remote party. As a result, we need to generate
4159+
// the partial signature again with the new nonce.
4160+
func (lc *LightningChannel) resignMusigCommit(commitTx *wire.MsgTx,
4161+
) (lnwire.OptPartialSigWithNonceTLV, error) {
4162+
4163+
remoteSession := lc.musigSessions.RemoteSession
4164+
musig, err := remoteSession.SignCommit(commitTx)
4165+
if err != nil {
4166+
var none lnwire.OptPartialSigWithNonceTLV
4167+
return none, err
4168+
}
4169+
4170+
partialSig := lnwire.MaybePartialSigWithNonce(musig.ToWireSig())
4171+
4172+
return partialSig, nil
4173+
}
4174+
41544175
// ProcessChanSyncMsg processes a ChannelReestablish message sent by the remote
41554176
// connection upon re establishment of our connection with them. This method
41564177
// will return a single message if we are currently out of sync, otherwise a
@@ -4428,12 +4449,23 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
44284449
commitUpdates = append(commitUpdates, logUpdate.UpdateMsg)
44294450
}
44304451

4452+
// If this is a taproot channel, then we need to regenerate the
4453+
// musig2 signature for the remote party, using their fresh
4454+
// nonce.
4455+
if lc.channelState.ChanType.IsTaproot() {
4456+
partialSig, err := lc.resignMusigCommit(
4457+
commitDiff.Commitment.CommitTx,
4458+
)
4459+
if err != nil {
4460+
return nil, nil, nil, err
4461+
}
4462+
4463+
commitDiff.CommitSig.PartialSig = partialSig
4464+
}
4465+
44314466
// With the batch of updates accumulated, we'll now re-send the
44324467
// original CommitSig message required to re-sync their remote
44334468
// commitment chain with our local version of their chain.
4434-
//
4435-
// TODO(roasbeef): need to re-sign commitment states w/
4436-
// fresh nonce
44374469
commitUpdates = append(commitUpdates, commitDiff.CommitSig)
44384470

44394471
// NOTE: If a revocation is not owed, then updates is empty.

lnwallet/channel_test.go

Lines changed: 161 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3046,19 +3046,11 @@ func restartChannel(channelOld *LightningChannel) (*LightningChannel, error) {
30463046
return channelNew, nil
30473047
}
30483048

3049-
// TestChanSyncOweCommitment tests that if Bob restarts (and then Alice) before
3050-
// he receives Alice's CommitSig message, then Alice concludes that she needs
3051-
// to re-send the CommitDiff. After the diff has been sent, both nodes should
3052-
// resynchronize and be able to complete the dangling commit.
3053-
func TestChanSyncOweCommitment(t *testing.T) {
3054-
t.Parallel()
3055-
3049+
func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) {
30563050
// Create a test channel which will be used for the duration of this
30573051
// unittest. The channel will be funded evenly with Alice having 5 BTC,
30583052
// and Bob having 5 BTC.
3059-
aliceChannel, bobChannel, err := CreateTestChannels(
3060-
t, channeldb.SingleFunderTweaklessBit,
3061-
)
3053+
aliceChannel, bobChannel, err := CreateTestChannels(t, chanType)
30623054
require.NoError(t, err, "unable to create test channels")
30633055

30643056
var fakeOnionBlob [lnwire.OnionPacketSize]byte
@@ -3133,6 +3125,15 @@ func TestChanSyncOweCommitment(t *testing.T) {
31333125
aliceNewCommit, err := aliceChannel.SignNextCommitment()
31343126
require.NoError(t, err, "unable to sign commitment")
31353127

3128+
// If this is a taproot channel, then we'll generate fresh verification
3129+
// nonce for both sides.
3130+
if chanType.IsTaproot() {
3131+
_, err = aliceChannel.GenMusigNonces()
3132+
require.NoError(t, err)
3133+
_, err = bobChannel.GenMusigNonces()
3134+
require.NoError(t, err)
3135+
}
3136+
31363137
// Bob doesn't get this message so upon reconnection, they need to
31373138
// synchronize. Alice should conclude that she owes Bob a commitment,
31383139
// while Bob should think he's properly synchronized.
@@ -3144,7 +3145,7 @@ func TestChanSyncOweCommitment(t *testing.T) {
31443145
// This is a helper function that asserts Alice concludes that she
31453146
// needs to retransmit the exact commitment that we failed to send
31463147
// above.
3147-
assertAliceCommitRetransmit := func() {
3148+
assertAliceCommitRetransmit := func() *lnwire.CommitSig {
31483149
aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg(
31493150
bobSyncMsg,
31503151
)
@@ -3209,12 +3210,25 @@ func TestChanSyncOweCommitment(t *testing.T) {
32093210
len(commitSigMsg.HtlcSigs))
32103211
}
32113212
for i, htlcSig := range commitSigMsg.HtlcSigs {
3212-
if htlcSig != aliceNewCommit.HtlcSigs[i] {
3213+
if !bytes.Equal(htlcSig.RawBytes(),
3214+
aliceNewCommit.HtlcSigs[i].RawBytes()) {
3215+
32133216
t.Fatalf("htlc sig msgs don't match: "+
3214-
"expected %x got %x",
3215-
aliceNewCommit.HtlcSigs[i], htlcSig)
3217+
"expected %v got %v",
3218+
spew.Sdump(aliceNewCommit.HtlcSigs[i]),
3219+
spew.Sdump(htlcSig))
32163220
}
32173221
}
3222+
3223+
// If this is a taproot channel, then partial sig information
3224+
// should be present in the commit sig sent over. This
3225+
// signature will be re-regenerated, so we can't compare it
3226+
// with the old one.
3227+
if chanType.IsTaproot() {
3228+
require.True(t, commitSigMsg.PartialSig.IsSome())
3229+
}
3230+
3231+
return commitSigMsg
32183232
}
32193233

32203234
// Alice should detect that she needs to re-send 5 messages: the 3
@@ -3235,14 +3249,19 @@ func TestChanSyncOweCommitment(t *testing.T) {
32353249
// send the exact same set of messages.
32363250
aliceChannel, err = restartChannel(aliceChannel)
32373251
require.NoError(t, err, "unable to restart alice")
3238-
assertAliceCommitRetransmit()
32393252

3240-
// TODO(roasbeef): restart bob as well???
3253+
// To properly simulate a restart, we'll use the *new* signature that
3254+
// would send in an actual p2p setting.
3255+
aliceReCommitSig := assertAliceCommitRetransmit()
32413256

32423257
// At this point, we should be able to resume the prior state update
32433258
// without any issues, resulting in Alice settling the 3 htlc's, and
32443259
// adding one of her own.
3245-
err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs)
3260+
err = bobChannel.ReceiveNewCommitment(&CommitSigs{
3261+
CommitSig: aliceReCommitSig.CommitSig,
3262+
HtlcSigs: aliceReCommitSig.HtlcSigs,
3263+
PartialSig: aliceReCommitSig.PartialSig,
3264+
})
32463265
require.NoError(t, err, "bob unable to process alice's commitment")
32473266
bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment()
32483267
require.NoError(t, err, "unable to revoke bob commitment")
@@ -3329,16 +3348,53 @@ func TestChanSyncOweCommitment(t *testing.T) {
33293348
}
33303349
}
33313350

3332-
// TestChanSyncOweCommitmentPendingRemote asserts that local updates are applied
3333-
// to the remote commit across restarts.
3334-
func TestChanSyncOweCommitmentPendingRemote(t *testing.T) {
3351+
// TestChanSyncOweCommitment tests that if Bob restarts (and then Alice) before
3352+
// he receives Alice's CommitSig message, then Alice concludes that she needs
3353+
// to re-send the CommitDiff. After the diff has been sent, both nodes should
3354+
// resynchronize and be able to complete the dangling commit.
3355+
func TestChanSyncOweCommitment(t *testing.T) {
33353356
t.Parallel()
33363357

3358+
testCases := []struct {
3359+
name string
3360+
chanType channeldb.ChannelType
3361+
}{
3362+
{
3363+
name: "tweakless",
3364+
chanType: channeldb.SingleFunderTweaklessBit,
3365+
},
3366+
{
3367+
name: "anchors",
3368+
chanType: channeldb.SingleFunderTweaklessBit |
3369+
channeldb.AnchorOutputsBit,
3370+
},
3371+
{
3372+
name: "taproot",
3373+
chanType: channeldb.SingleFunderTweaklessBit |
3374+
channeldb.AnchorOutputsBit |
3375+
channeldb.SimpleTaprootFeatureBit,
3376+
},
3377+
{
3378+
name: "taproot with tapscript root",
3379+
chanType: channeldb.SingleFunderTweaklessBit |
3380+
channeldb.AnchorOutputsBit |
3381+
channeldb.SimpleTaprootFeatureBit |
3382+
channeldb.TapscriptRootBit,
3383+
},
3384+
}
3385+
for _, tc := range testCases {
3386+
t.Run(tc.name, func(t *testing.T) {
3387+
testChanSyncOweCommitment(t, tc.chanType)
3388+
})
3389+
}
3390+
}
3391+
3392+
func testChanSyncOweCommitmentPendingRemote(t *testing.T,
3393+
chanType channeldb.ChannelType) {
3394+
33373395
// Create a test channel which will be used for the duration of this
33383396
// unittest.
3339-
aliceChannel, bobChannel, err := CreateTestChannels(
3340-
t, channeldb.SingleFunderTweaklessBit,
3341-
)
3397+
aliceChannel, bobChannel, err := CreateTestChannels(t, chanType)
33423398
require.NoError(t, err, "unable to create test channels")
33433399

33443400
var fakeOnionBlob [lnwire.OnionPacketSize]byte
@@ -3421,6 +3477,12 @@ func TestChanSyncOweCommitmentPendingRemote(t *testing.T) {
34213477
bobChannel, err = restartChannel(bobChannel)
34223478
require.NoError(t, err, "unable to restart bob")
34233479

3480+
// If this is a taproot channel, then since Bob just restarted, we need
3481+
// to exchange nonces once again.
3482+
if chanType.IsTaproot() {
3483+
require.NoError(t, initMusigNonce(aliceChannel, bobChannel))
3484+
}
3485+
34243486
// Bob signs the commitment he owes.
34253487
bobNewCommit, err := bobChannel.SignNextCommitment()
34263488
require.NoError(t, err, "unable to sign commitment")
@@ -3446,6 +3508,45 @@ func TestChanSyncOweCommitmentPendingRemote(t *testing.T) {
34463508
}
34473509
}
34483510

3511+
// TestChanSyncOweCommitmentPendingRemote asserts that local updates are applied
3512+
// to the remote commit across restarts.
3513+
func TestChanSyncOweCommitmentPendingRemote(t *testing.T) {
3514+
t.Parallel()
3515+
3516+
testCases := []struct {
3517+
name string
3518+
chanType channeldb.ChannelType
3519+
}{
3520+
{
3521+
name: "tweakless",
3522+
chanType: channeldb.SingleFunderTweaklessBit,
3523+
},
3524+
{
3525+
name: "anchors",
3526+
chanType: channeldb.SingleFunderTweaklessBit |
3527+
channeldb.AnchorOutputsBit,
3528+
},
3529+
{
3530+
name: "taproot",
3531+
chanType: channeldb.SingleFunderTweaklessBit |
3532+
channeldb.AnchorOutputsBit |
3533+
channeldb.SimpleTaprootFeatureBit,
3534+
},
3535+
{
3536+
name: "taproot with tapscript root",
3537+
chanType: channeldb.SingleFunderTweaklessBit |
3538+
channeldb.AnchorOutputsBit |
3539+
channeldb.SimpleTaprootFeatureBit |
3540+
channeldb.TapscriptRootBit,
3541+
},
3542+
}
3543+
for _, tc := range testCases {
3544+
t.Run(tc.name, func(t *testing.T) {
3545+
testChanSyncOweCommitmentPendingRemote(t, tc.chanType)
3546+
})
3547+
}
3548+
}
3549+
34493550
// testChanSyncOweRevocation is the internal version of
34503551
// TestChanSyncOweRevocation that is parameterized based on the type of channel
34513552
// being used in the test.
@@ -3595,8 +3696,6 @@ func testChanSyncOweRevocation(t *testing.T, chanType channeldb.ChannelType) {
35953696

35963697
assertAliceOwesRevoke()
35973698

3598-
// TODO(roasbeef): restart bob too???
3599-
36003699
// We'll continue by then allowing bob to process Alice's revocation
36013700
// message.
36023701
_, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation)
@@ -3645,6 +3744,15 @@ func TestChanSyncOweRevocation(t *testing.T) {
36453744

36463745
testChanSyncOweRevocation(t, taprootBits)
36473746
})
3747+
t.Run("taproot with tapscript root", func(t *testing.T) {
3748+
taprootBits := channeldb.SimpleTaprootFeatureBit |
3749+
channeldb.AnchorOutputsBit |
3750+
channeldb.ZeroHtlcTxFeeBit |
3751+
channeldb.SingleFunderTweaklessBit |
3752+
channeldb.TapscriptRootBit
3753+
3754+
testChanSyncOweRevocation(t, taprootBits)
3755+
})
36483756
}
36493757

36503758
func testChanSyncOweRevocationAndCommit(t *testing.T,
@@ -3774,6 +3882,14 @@ func testChanSyncOweRevocationAndCommit(t *testing.T,
37743882
bobNewCommit.HtlcSigs[i])
37753883
}
37763884
}
3885+
3886+
// If this is a taproot channel, then partial sig information
3887+
// should be present in the commit sig sent over. This
3888+
// signature will be re-regenerated, so we can't compare it
3889+
// with the old one.
3890+
if chanType.IsTaproot() {
3891+
require.True(t, bobReCommitSigMsg.PartialSig.IsSome())
3892+
}
37773893
}
37783894

37793895
// We expect Bob to send exactly two messages: first his revocation
@@ -3830,6 +3946,15 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) {
38303946

38313947
testChanSyncOweRevocationAndCommit(t, taprootBits)
38323948
})
3949+
t.Run("taproot with tapscript root", func(t *testing.T) {
3950+
taprootBits := channeldb.SimpleTaprootFeatureBit |
3951+
channeldb.AnchorOutputsBit |
3952+
channeldb.ZeroHtlcTxFeeBit |
3953+
channeldb.SingleFunderTweaklessBit |
3954+
channeldb.TapscriptRootBit
3955+
3956+
testChanSyncOweRevocationAndCommit(t, taprootBits)
3957+
})
38333958
}
38343959

38353960
func testChanSyncOweRevocationAndCommitForceTransition(t *testing.T,
@@ -4061,6 +4186,17 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) {
40614186
t, taprootBits,
40624187
)
40634188
})
4189+
t.Run("taproot with tapscript root", func(t *testing.T) {
4190+
taprootBits := channeldb.SimpleTaprootFeatureBit |
4191+
channeldb.AnchorOutputsBit |
4192+
channeldb.ZeroHtlcTxFeeBit |
4193+
channeldb.SingleFunderTweaklessBit |
4194+
channeldb.TapscriptRootBit
4195+
4196+
testChanSyncOweRevocationAndCommitForceTransition(
4197+
t, taprootBits,
4198+
)
4199+
})
40644200
}
40654201

40664202
// TestChanSyncFailure tests the various scenarios during channel sync where we

0 commit comments

Comments
 (0)