Skip to content

Commit

Permalink
Merge pull request #7345 from Roasbeef/simple-taproot-chans-channel-s…
Browse files Browse the repository at this point in the history
…tatemachine

[6/?] - lnwallet+chancloser: update channel state machine and co-op close for musig2 flow
  • Loading branch information
Roasbeef authored Jul 23, 2023
2 parents d91ae68 + 7764f65 commit 09200ca
Show file tree
Hide file tree
Showing 24 changed files with 2,436 additions and 747 deletions.
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.
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
Loading

0 comments on commit 09200ca

Please sign in to comment.