Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6/?] - lnwallet+chancloser: update channel state machine and co-op close for musig2 flow #7345

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3a94232
lnwallet: export MusigCommitType enum
Roasbeef Jun 24, 2023
9715ed6
lnwallet+htlcswitch: add NewCommitState struct, modify send/recv sig …
Roasbeef Jan 20, 2023
f60c547
lnwallet: update CreateHtlcTimeoutTx+CreateHtlcSuccessTx for taproot
Roasbeef Jan 20, 2023
e97f0c6
lnwallet: update genHTLC script to support segwit v0 + v1 (taproot) H…
Roasbeef Jan 20, 2023
d6dbe7d
lnwallet: update channel state machine to add musig session initializ…
Roasbeef Jan 20, 2023
de6e478
lnwallet: update to genRemoteHtlcSigJobs to generate taproot jobs
Roasbeef Jan 20, 2023
e2cc9d5
lnwallet: update genHtlcSigValidationJobs to be taproot aware
Roasbeef Jan 20, 2023
b0cda76
lnwallet: update testAddSettleWorkflow to test the new taproot flow
Roasbeef Jan 20, 2023
9d427cd
lnwallet/chanvalidate: update ValidateChannel to recognize taproot chans
Roasbeef Jan 20, 2023
c763481
lnwallet: update internal co-op close flow to support musig2 keyspend
Roasbeef Jan 20, 2023
1790723
lnwallet: return structured error from VerifyCommitSig
Roasbeef Jul 3, 2023
2035e38
lnwallet: fix bug in deriveMusig2Shachain
Roasbeef Jul 11, 2023
e017fc6
input: ensure sessionOpts is properly threaded through
Roasbeef Jul 12, 2023
ad6bebc
channeldb: update ChanSyncMsg to populate nonce info
Roasbeef Jul 12, 2023
40d22d7
lnwallet: move nonce generation into generateRevocation
Roasbeef Jul 12, 2023
2279ef2
lnwallet: handle nonce init in ProcessChanSyncMsg
Roasbeef Jul 12, 2023
5c09d61
lnwallet: refactor revocation tests to test for tweakless+taproot
Roasbeef Jul 12, 2023
7764f65
multi: fix linter warnings
Roasbeef Jul 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
88 changes: 86 additions & 2 deletions channeldb/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package channeldb

import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"errors"
Expand All @@ -13,6 +14,7 @@ import (
"sync"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
Expand Down Expand Up @@ -1369,6 +1371,65 @@ func (c *OpenChannel) SecondCommitmentPoint() (*btcec.PublicKey, error) {
return input.ComputeCommitmentPoint(revocation[:]), nil
}

var (
// taprootRevRootKey is the key used to derive the revocation root for
// the taproot nonces. This is done via HMAC of the existing revocation
// root.
taprootRevRootKey = []byte("taproot-rev-root")
)

// DeriveMusig2Shachain derives a shachain producer for the taproot channel
// from normal shachain revocation root.
func DeriveMusig2Shachain(revRoot shachain.Producer) (shachain.Producer, error) { //nolint:lll
// In order to obtain the revocation root hash to create the taproot
// revocation, we'll encode the producer into a buffer, then use that
// to derive the shachain root needed.
var rootHashBuf bytes.Buffer
if err := revRoot.Encode(&rootHashBuf); err != nil {
return nil, fmt.Errorf("unable to encode producer: %w", err)
}

revRootHash := chainhash.HashH(rootHashBuf.Bytes())

// For taproot channel types, we'll also generate a distinct shachain
// root using the same seed information. We'll use this to generate
// verification nonces for the channel. We'll bind with this a simple
// hmac.
taprootRevHmac := hmac.New(sha256.New, taprootRevRootKey)
if _, err := taprootRevHmac.Write(revRootHash[:]); err != nil {
return nil, err
}

taprootRevRoot := taprootRevHmac.Sum(nil)

// Once we have the root, we can then generate our shachain producer
// and from that generate the per-commitment point.
return shachain.NewRevocationProducerFromBytes(
taprootRevRoot,
)
}

// NewMusigVerificationNonce generates the local or verification nonce for
// another musig2 session. In order to permit our implementation to not have to
// write any secret nonce state to disk, we'll use the _next_ shachain
// pre-image as our primary randomness source. When used to generate the nonce
// again to broadcast our commitment hte current height will be used.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// again to broadcast our commitment hte current height will be used.
// again to broadcast our commitment the current height will be used.

func NewMusigVerificationNonce(pubKey *btcec.PublicKey, targetHeight uint64,
shaGen shachain.Producer) (*musig2.Nonces, error) {

// Now that we know what height we need, we'll grab the shachain
// pre-image at the target destination.
nextPreimage, err := shaGen.AtIndex(targetHeight)
if err != nil {
return nil, err
}

shaChainRand := musig2.WithCustomRand(bytes.NewBuffer(nextPreimage[:]))
pubKeyOpt := musig2.WithPublicKey(pubKey)

return musig2.GenNonces(pubKeyOpt, shaChainRand)
}

// ChanSyncMsg returns the ChannelReestablish message that should be sent upon
// reconnection with the remote peer that we're maintaining this channel with.
// The information contained within this message is necessary to re-sync our
Expand Down Expand Up @@ -1440,6 +1501,30 @@ func (c *OpenChannel) ChanSyncMsg() (*lnwire.ChannelReestablish, error) {
}
}

// If this is a taproot channel, then we'll need to generate our next
// verification nonce to send to the remote party. They'll use this to
// sign the next update to our commitment transaction.
var nextTaprootNonce *lnwire.Musig2Nonce
if c.ChanType.IsTaproot() {
taprootRevProducer, err := DeriveMusig2Shachain(
c.RevocationProducer,
)
if err != nil {
return nil, err
}

nextNonce, err := NewMusigVerificationNonce(
c.LocalChanCfg.MultiSigKey.PubKey,
nextLocalCommitHeight, taprootRevProducer,
)
if err != nil {
return nil, fmt.Errorf("unable to gen next "+
"nonce: %w", err)
}

nextTaprootNonce = (*lnwire.Musig2Nonce)(&nextNonce.PubNonce)
}

return &lnwire.ChannelReestablish{
ChanID: lnwire.NewChanIDFromOutPoint(
&c.FundingOutpoint,
Expand All @@ -1450,6 +1535,7 @@ func (c *OpenChannel) ChanSyncMsg() (*lnwire.ChannelReestablish, error) {
LocalUnrevokedCommitPoint: input.ComputeCommitmentPoint(
currentCommitSecret[:],
),
LocalNonce: nextTaprootNonce,
}, nil
}

Expand Down Expand Up @@ -3776,8 +3862,6 @@ func putChanRevocationState(chanBucket kvdb.RwBucket, channel *OpenChannel) erro
return err
}

// TODO(roasbeef): don't keep producer on disk

// If the next revocation is present, which is only the case after the
// FundingLocked message has been sent, then we'll write it to disk.
if channel.RemoteNextRevocation != nil {
Expand Down
46 changes: 2 additions & 44 deletions contractcourt/breacharbiter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,7 @@ func initBreachedState(t *testing.T) (*BreachArbiter,
if _, err := bob.ReceiveHTLC(htlc); err != nil {
t.Fatalf("bob unable to recv add htlc: %v", err)
}
if err := forceStateTransition(alice, bob); err != nil {
if err := lnwallet.ForceStateTransition(alice, bob); err != nil {
t.Fatalf("Can't update the channel state: %v", err)
}

Expand All @@ -996,7 +996,7 @@ func initBreachedState(t *testing.T) (*BreachArbiter,
if _, err := bob.ReceiveHTLC(htlc2); err != nil {
t.Fatalf("bob unable to recv add htlc: %v", err)
}
if err := forceStateTransition(alice, bob); err != nil {
if err := lnwallet.ForceStateTransition(alice, bob); err != nil {
t.Fatalf("Can't update the channel state: %v", err)
}

Expand Down Expand Up @@ -2436,45 +2436,3 @@ func createHTLC(data int, amount lnwire.MilliSatoshi) (*lnwire.UpdateAddHTLC, [3
Expiry: uint32(5),
}, returnPreimage
}

// forceStateTransition executes the necessary interaction between the two
// commitment state machines to transition to a new state locking in any
// pending updates.
// TODO(conner) remove code duplication
func forceStateTransition(chanA, chanB *lnwallet.LightningChannel) error {
aliceSig, aliceHtlcSigs, _, err := chanA.SignNextCommitment()
if err != nil {
return err
}
if err = chanB.ReceiveNewCommitment(aliceSig, aliceHtlcSigs); err != nil {
return err
}

bobRevocation, _, _, err := chanB.RevokeCurrentCommitment()
if err != nil {
return err
}
bobSig, bobHtlcSigs, _, err := chanB.SignNextCommitment()
if err != nil {
return err
}

_, _, _, _, err = chanA.ReceiveRevocation(bobRevocation)
if err != nil {
return err
}
if err := chanA.ReceiveNewCommitment(bobSig, bobHtlcSigs); err != nil {
return err
}

aliceRevocation, _, _, err := chanA.RevokeCurrentCommitment()
if err != nil {
return err
}
_, _, _, _, err = chanB.ReceiveRevocation(aliceRevocation)
if err != nil {
return err
}

return nil
}
2 changes: 1 addition & 1 deletion contractcourt/chain_watcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func TestChainWatcherRemoteUnilateralClosePendingCommit(t *testing.T) {

// With the HTLC added, we'll now manually initiate a state transition
// from Alice to Bob.
_, _, _, err = aliceChannel.SignNextCommitment()
_, err = aliceChannel.SignNextCommitment()
if err != nil {
t.Fatal(err)
}
Expand Down
21 changes: 15 additions & 6 deletions htlcswitch/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,6 @@ func (l *channelLink) syncChanStates() error {
return fmt.Errorf("unable to generate chan sync message for "+
"ChannelPoint(%v)", l.channel.ChannelPoint())
}

if err := l.cfg.Peer.SendMessage(true, localChanSyncMsg); err != nil {
return fmt.Errorf("unable to send chan sync message for "+
"ChannelPoint(%v): %v", l.channel.ChannelPoint(), err)
Expand Down Expand Up @@ -738,6 +737,13 @@ func (l *channelLink) syncChanStates() error {
l.ChanID(), nextRevocation,
)

// If this is a taproot channel, then we'll send the
// very same nonce that we sent above, as they should
// take the latest verification nonce we send.
if chanState.ChanType.IsTaproot() {
fundingLockedMsg.NextLocalNonce = localChanSyncMsg.LocalNonce //nolint:lll
}

// For channels that negotiated the option-scid-alias
// feature bit, ensure that we send over the alias in
// the funding_locked message. We'll send the first
Expand Down Expand Up @@ -1929,7 +1935,10 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
// We just received a new updates to our local commitment
// chain, validate this new commitment, closing the link if
// invalid.
err = l.channel.ReceiveNewCommitment(msg.CommitSig, msg.HtlcSigs)
err = l.channel.ReceiveNewCommitment(&lnwallet.CommitSigs{
CommitSig: msg.CommitSig,
HtlcSigs: msg.HtlcSigs,
})
if err != nil {
// If we were unable to reconstruct their proposed
// commitment, then we'll examine the type of error. If
Expand Down Expand Up @@ -2259,7 +2268,7 @@ func (l *channelLink) updateCommitTx() error {
return nil
}

theirCommitSig, htlcSigs, pendingHTLCs, err := l.channel.SignNextCommitment()
newCommit, err := l.channel.SignNextCommitment()
if err == lnwallet.ErrNoWindow {
l.cfg.PendingCommitTicker.Resume()
l.log.Trace("PendingCommitTicker resumed")
Expand Down Expand Up @@ -2291,7 +2300,7 @@ func (l *channelLink) updateCommitTx() error {
// pending).
newUpdate := &contractcourt.ContractUpdate{
HtlcKey: contractcourt.RemotePendingHtlcSet,
Htlcs: pendingHTLCs,
Htlcs: newCommit.PendingHTLCs,
}
err = l.cfg.NotifyContractUpdate(newUpdate)
if err != nil {
Expand All @@ -2307,8 +2316,8 @@ func (l *channelLink) updateCommitTx() error {

commitSig := &lnwire.CommitSig{
ChanID: l.ChanID(),
CommitSig: theirCommitSig,
HtlcSigs: htlcSigs,
CommitSig: newCommit.CommitSig,
HtlcSigs: newCommit.HtlcSigs,
}
l.cfg.Peer.SendMessage(false, commitSig)

Expand Down
13 changes: 7 additions & 6 deletions htlcswitch/link_isolated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ func (l *linkTestContext) receiveHtlcAliceToBob() {
func (l *linkTestContext) sendCommitSigBobToAlice(expHtlcs int) {
l.t.Helper()

sig, htlcSigs, _, err := l.bobChannel.SignNextCommitment()
sigs, err := l.bobChannel.SignNextCommitment()
if err != nil {
l.t.Fatalf("error signing commitment: %v", err)
}

commitSig := &lnwire.CommitSig{
CommitSig: sig,
HtlcSigs: htlcSigs,
CommitSig: sigs.CommitSig,
HtlcSigs: sigs.HtlcSigs,
}

if len(commitSig.HtlcSigs) != expHtlcs {
Expand Down Expand Up @@ -141,9 +141,10 @@ func (l *linkTestContext) receiveCommitSigAliceToBob(expHtlcs int) {

comSig := l.receiveCommitSigAlice(expHtlcs)

err := l.bobChannel.ReceiveNewCommitment(
comSig.CommitSig, comSig.HtlcSigs,
)
err := l.bobChannel.ReceiveNewCommitment(&lnwallet.CommitSigs{
CommitSig: comSig.CommitSig,
HtlcSigs: comSig.HtlcSigs,
})
if err != nil {
l.t.Fatalf("bob failed receiving commitment: %v", err)
}
Expand Down