From 5e511e35fd06d224e9bb0cf1e7b76e40d6fe3764 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 19 Jan 2023 17:06:38 -0800 Subject: [PATCH] lnwallet: update CommitScriptAnchors to add taproot awareness We also update some of the resolutions (even though they aren't hooked up yet), as they need to be able to properly re-create the set of scripts. --- contractcourt/chain_watcher.go | 2 +- lnwallet/channel.go | 43 +++++++++++++---- lnwallet/commitment.go | 88 +++++++++++++++++++++++++++------- 3 files changed, 106 insertions(+), 27 deletions(-) diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 99472e0c1..7fd45e1c2 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -834,7 +834,7 @@ func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail, // Create an AnchorResolution for the breached state. anchorRes, err := lnwallet.NewAnchorResolution( - c.cfg.chanState, commitSpend.SpendingTx, + c.cfg.chanState, commitSpend.SpendingTx, nil, ) if err != nil { return false, fmt.Errorf("unable to create anchor "+ diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 9406e0869..d29ffae75 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -5943,7 +5943,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si } anchorResolution, err := NewAnchorResolution( - chanState, commitTxBroadcast, + chanState, commitTxBroadcast, keyRing, ) if err != nil { return nil, err @@ -6640,7 +6640,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, } anchorResolution, err := NewAnchorResolution( - chanState, commitTx, + chanState, commitTx, keyRing, ) if err != nil { return nil, err @@ -6824,7 +6824,7 @@ type AnchorResolutions struct { // NewAnchorResolutions returns a set of anchor resolutions wrapped in the // struct AnchorResolutions. Because we have no view on the mempool, we can -// only blindly anchor all of these txes down. Caller needs to check the +// only blindly anchor all of these txes down. The caller needs to check the // returned values against nil to decide whether there exists an anchor // resolution for local/remote/pending remote commitment txes. func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, @@ -6833,11 +6833,23 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, lc.Lock() defer lc.Unlock() - resolutions := &AnchorResolutions{} + var resolutions AnchorResolutions // Add anchor for local commitment tx, if any. + revocation, err := lc.channelState.RevocationProducer.AtIndex( + lc.currentHeight, + ) + if err != nil { + return nil, err + } + localCommitPoint := input.ComputeCommitmentPoint(revocation[:]) + localKeyRing := DeriveCommitmentKeys( + localCommitPoint, true, lc.channelState.ChanType, + &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, + ) localRes, err := NewAnchorResolution( lc.channelState, lc.channelState.LocalCommitment.CommitTx, + localKeyRing, ) if err != nil { return nil, err @@ -6845,8 +6857,14 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, resolutions.Local = localRes // Add anchor for remote commitment tx, if any. + remoteKeyRing := DeriveCommitmentKeys( + lc.channelState.RemoteCurrentRevocation, false, + lc.channelState.ChanType, &lc.channelState.LocalChanCfg, + &lc.channelState.RemoteChanCfg, + ) remoteRes, err := NewAnchorResolution( lc.channelState, lc.channelState.RemoteCommitment.CommitTx, + remoteKeyRing, ) if err != nil { return nil, err @@ -6860,9 +6878,15 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, } if remotePendingCommit != nil { + pendingRemoteKeyRing := DeriveCommitmentKeys( + lc.channelState.RemoteNextRevocation, false, + lc.channelState.ChanType, &lc.channelState.LocalChanCfg, + &lc.channelState.RemoteChanCfg, + ) remotePendingRes, err := NewAnchorResolution( lc.channelState, remotePendingCommit.Commitment.CommitTx, + pendingRemoteKeyRing, ) if err != nil { return nil, err @@ -6870,13 +6894,14 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, resolutions.RemotePending = remotePendingRes } - return resolutions, nil + return &resolutions, nil } // NewAnchorResolution returns the information that is required to sweep the // local anchor. func NewAnchorResolution(chanState *channeldb.OpenChannel, - commitTx *wire.MsgTx) (*AnchorResolution, error) { + commitTx *wire.MsgTx, + keyRing *CommitmentKeyRing) (*AnchorResolution, error) { // Return nil resolution if the channel has no anchors. if !chanState.ChanType.HasAnchors() { @@ -6885,7 +6910,8 @@ func NewAnchorResolution(chanState *channeldb.OpenChannel, // Derive our local anchor script. localAnchor, _, err := CommitScriptAnchors( - &chanState.LocalChanCfg, &chanState.RemoteChanCfg, + chanState.ChanType, &chanState.LocalChanCfg, + &chanState.RemoteChanCfg, keyRing, ) if err != nil { return nil, err @@ -7237,7 +7263,8 @@ func (lc *LightningChannel) generateRevocation(height uint64) (*lnwire.RevokeAnd revocationMsg.NextRevocationKey = input.ComputeCommitmentPoint(nextCommitSecret[:]) revocationMsg.ChanID = lnwire.NewChanIDFromOutPoint( - &lc.channelState.FundingOutpoint) + &lc.channelState.FundingOutpoint, + ) return revocationMsg, nil } diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 5ae507589..996e3486d 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -490,37 +490,89 @@ func HtlcSuccessFee(chanType channeldb.ChannelType, // CommitScriptAnchors return the scripts to use for the local and remote // anchor. -func CommitScriptAnchors(localChanCfg, - remoteChanCfg *channeldb.ChannelConfig) (*ScriptInfo, - *ScriptInfo, error) { +func CommitScriptAnchors(chanType channeldb.ChannelType, + localChanCfg, remoteChanCfg *channeldb.ChannelConfig, + keyRing *CommitmentKeyRing) (*ScriptInfo, *ScriptInfo, error) { - // Helper to create anchor ScriptInfo from key. - anchorScript := func(key *btcec.PublicKey) (*ScriptInfo, error) { - script, err := input.CommitScriptAnchor(key) - if err != nil { - return nil, err + var ( + anchorScript func(key *btcec.PublicKey) (*ScriptInfo, error) + keySelector func(*channeldb.ChannelConfig, + bool) *btcec.PublicKey + ) + + switch { + // For taproot channels, the anchor is slightly different: the top + // level key is now the (relative) local delay and remote public key, + // since these are fully revealed once the commitment hits the chain. + case chanType.IsTaproot(): + anchorScript = func(key *btcec.PublicKey) (*ScriptInfo, error) { + anchorKey, err := input.TaprootOutputKeyAnchor(key) + if err != nil { + return nil, err + } + + anchorPkScript, err := input.PayToTaprootScript( + anchorKey, + ) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: anchorPkScript, + }, nil } - scriptHash, err := input.WitnessScriptHash(script) - if err != nil { - return nil, err + keySelector = func(cfg *channeldb.ChannelConfig, + local bool) *btcec.PublicKey { + + if local { + return keyRing.ToLocalKey + } + + return keyRing.ToRemoteKey } - return &ScriptInfo{ - PkScript: scriptHash, - WitnessScript: script, - }, nil + // For normal channels we'll use the multi-sig keys since those are + // revealed when the channel closes + default: + // For normal channels, we'll create a p2wsh script based on + // the target key. + anchorScript = func(key *btcec.PublicKey) (*ScriptInfo, error) { + script, err := input.CommitScriptAnchor(key) + if err != nil { + return nil, err + } + + scriptHash, err := input.WitnessScriptHash(script) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: scriptHash, + WitnessScript: script, + }, nil + } + + // For the existing channels, we'll always select the multi-sig + // key from the party's channel config. + keySelector = func(cfg *channeldb.ChannelConfig, + _ bool) *btcec.PublicKey { + + return cfg.MultiSigKey.PubKey + } } // Get the script used for the anchor output spendable by the local // node. - localAnchor, err := anchorScript(localChanCfg.MultiSigKey.PubKey) + localAnchor, err := anchorScript(keySelector(localChanCfg, true)) if err != nil { return nil, nil, err } // And the anchor spendable by the remote node. - remoteAnchor, err := anchorScript(remoteChanCfg.MultiSigKey.PubKey) + remoteAnchor, err := anchorScript(keySelector(remoteChanCfg, false)) if err != nil { return nil, nil, err } @@ -848,7 +900,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, // If this channel type has anchors, we'll also add those. if chanType.HasAnchors() { localAnchor, remoteAnchor, err := CommitScriptAnchors( - localChanCfg, remoteChanCfg, + chanType, localChanCfg, remoteChanCfg, keyRing, ) if err != nil { return nil, err