diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 19207c95d..71f2f9a83 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -451,10 +451,10 @@ func (c *chainWatcher) handleUnknownLocalState( pkScript := output.PkScript switch { - case bytes.Equal(localScript.PkScript, pkScript): + case bytes.Equal(localScript.PkScript(), pkScript): ourCommit = true - case bytes.Equal(remoteScript.PkScript, pkScript): + case bytes.Equal(remoteScript.PkScript(), pkScript): ourCommit = true } } diff --git a/input/script_utils.go b/input/script_utils.go index 6543d7a5b..b81278dae 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -13,6 +13,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/lnutils" "golang.org/x/crypto/ripemd160" ) @@ -605,12 +606,31 @@ func SenderHTLCTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, return txscript.NewBaseTapLeaf(successLeafScript), nil } +// htlcType is an enum value that denotes what type of HTLC script this is. +type htlcType uint8 + +const ( + // htlcLocalIncoming represents an incoming HTLC on the local + // commitment transaction. + htlcLocalIncoming htlcType = iota + + // htlcLocalOutgoing represents an outgoing HTLC on the local + // commitment transaction. + htlcLocalOutgoing + + // htlcRemoteIncoming represents an incoming HTLC on the remote + // commitment transaction. + htlcRemoteIncoming + + // htlcRemoteOutgoing represents an outgoing HTLC on the remote + // commitment transaction. + htlcRemoteOutgoing +) + // HtlcScriptTree holds the taproot output key, as well as the two script path // leaves that every taproot HTLC script depends on. type HtlcScriptTree struct { - // TaprootKey is the key that will be used to generate the taproot - // output. - TaprootKey *btcec.PublicKey + ScriptTree // SuccessTapLeaf is the tapleaf for the redemption path. SuccessTapLeaf txscript.TapLeaf @@ -618,18 +638,83 @@ type HtlcScriptTree struct { // TimeoutTapLeaf is the tapleaf for the timeout path. TimeoutTapLeaf txscript.TapLeaf - // TapscriptTree is the full tapscript tree that also includes the - // control block needed to spend each of the leaves. - TapscriptTree *txscript.IndexedTapScriptTree - - // TapscriptTreeRoot is the root hash of the tapscript tree. - TapscriptRoot []byte + htlcType htlcType } +// WitnessScriptToSign returns the witness script that we'll use when signing +// for the remote party, and also verifying signatures on our transactions. As +// an example, when we create an outgoing HTLC for the remote party, we want to +// sign the success path for them, so we'll return the success path leaf. +func (h *HtlcScriptTree) WitnessScriptToSign() []byte { + switch h.htlcType { + // For incoming HLTCs on our local commitment, we care about verifying + // the sucess path. + case htlcLocalIncoming: + return h.SuccessTapLeaf.Script + + // For incoming HTLCs on the remote party's commitment, we want to sign + // the the timeout path for them. + case htlcRemoteIncoming: + return h.TimeoutTapLeaf.Script + + // For outgoing HTLCs on our local commitment, we want to verify the + // timeout path. + case htlcLocalOutgoing: + return h.TimeoutTapLeaf.Script + + // For outgoing HTLCs on the remote party's commitment, we want to sign + // the success path for them. + case htlcRemoteOutgoing: + return h.SuccessTapLeaf.Script + + default: + panic(fmt.Sprintf("unknown htlc type: %v", h.htlcType)) + } +} + +// WitnessScriptForPath returns the witness script for the given spending path. +// An error is returned if the path is unknown. +func (h *HtlcScriptTree) WitnessScriptForPath(path ScriptPath) ([]byte, error) { + switch path { + case ScriptPathSuccess: + return h.SuccessTapLeaf.Script, nil + case ScriptPathTimeout: + return h.TimeoutTapLeaf.Script, nil + default: + return nil, fmt.Errorf("unknown script path: %v", path) + } +} + +// CtrlBlockForPath returns the control block for the given spending path. For +// script types that don't have a control block, nil is returned. +func (h *HtlcScriptTree) CtrlBlockForPath(path ScriptPath, +) (*txscript.ControlBlock, error) { + + switch path { + case ScriptPathSuccess: + return lnutils.Ptr(MakeTaprootCtrlBlock( + h.SuccessTapLeaf.Script, h.InternalKey, + h.TapscriptTree, + )), nil + case ScriptPathTimeout: + return lnutils.Ptr(MakeTaprootCtrlBlock( + h.TimeoutTapLeaf.Script, h.InternalKey, + h.TapscriptTree, + )), nil + default: + return nil, fmt.Errorf("unknown script path: %v", path) + } +} + +// A compile time check to ensure HtlcScriptTree implements the +// TapscriptMultiplexer interface. +var _ TapscriptDescriptor = (*HtlcScriptTree)(nil) + // senderHtlcTapScriptTree builds the tapscript tree which is used to anchor // the HTLC key for HTLCs on the sender's commitment. func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, - revokeKey *btcec.PublicKey, payHash []byte) (*HtlcScriptTree, error) { + revokeKey *btcec.PublicKey, payHash []byte, + hType htlcType) (*HtlcScriptTree, error) { // First, we'll obtain the tap leaves for both the success and timeout // path. @@ -661,11 +746,15 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, ) return &HtlcScriptTree{ - TaprootKey: htlcKey, + ScriptTree: ScriptTree{ + TaprootKey: htlcKey, + TapscriptTree: tapscriptTree, + TapscriptRoot: tapScriptRoot[:], + InternalKey: revokeKey, + }, SuccessTapLeaf: successTapLeaf, TimeoutTapLeaf: timeoutTapLeaf, - TapscriptTree: tapscriptTree, - TapscriptRoot: tapScriptRoot[:], + htlcType: hType, }, nil } @@ -698,13 +787,22 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, // The top level keyspend key is the revocation key, which allows a defender to // unilaterally spend the created output. func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey, - revokeKey *btcec.PublicKey, payHash []byte) (*HtlcScriptTree, error) { + revokeKey *btcec.PublicKey, payHash []byte, + localCommit bool) (*HtlcScriptTree, error) { + + var hType htlcType + if localCommit { + hType = htlcLocalOutgoing + } else { + hType = htlcRemoteIncoming + } // Given all the necessary parameters, we'll return the HTLC script // tree that includes the top level output script, as well as the two // tap leaf paths. return senderHtlcTapScriptTree( senderHtlcKey, receiverHtlcKey, revokeKey, payHash, + hType, ) } @@ -1175,7 +1273,7 @@ func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, // the HTLC key for HTLCs on the receiver's commitment. func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, revokeKey *btcec.PublicKey, payHash []byte, - cltvExpiry uint32) (*HtlcScriptTree, error) { + cltvExpiry uint32, hType htlcType) (*HtlcScriptTree, error) { // First, we'll obtain the tap leaves for both the success and timeout // path. @@ -1207,11 +1305,15 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, ) return &HtlcScriptTree{ - TaprootKey: htlcKey, + ScriptTree: ScriptTree{ + TaprootKey: htlcKey, + TapscriptTree: tapscriptTree, + TapscriptRoot: tapScriptRoot[:], + InternalKey: revokeKey, + }, SuccessTapLeaf: successTapLeaf, TimeoutTapLeaf: timeoutTapLeaf, - TapscriptTree: tapscriptTree, - TapscriptRoot: tapScriptRoot[:], + htlcType: hType, }, nil } @@ -1245,14 +1347,21 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, // the tap leaf are returned. func ReceiverHTLCScriptTaproot(cltvExpiry uint32, senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, - payHash []byte) (*HtlcScriptTree, error) { + payHash []byte, ourCommit bool) (*HtlcScriptTree, error) { + + var hType htlcType + if ourCommit { + hType = htlcLocalIncoming + } else { + hType = htlcRemoteOutgoing + } // Given all the necessary parameters, we'll return the HTLC script // tree that includes the top level output script, as well as the two // tap leaf paths. return receiverHtlcTapScriptTree( senderHtlcKey, receiverHtlcKey, revocationKey, payHash, - cltvExpiry, + cltvExpiry, hType, ) } @@ -1540,21 +1649,10 @@ func TaprootSecondLevelHtlcScript(revokeKey, delayKey *btcec.PublicKey, // SecondLevelScriptTree is a tapscript tree used to spend the second level // HTLC output after the CSV delay has passed. type SecondLevelScriptTree struct { - // TaprootKey is the key that will be used to generate the taproot output. - TaprootKey *btcec.PublicKey - - // PkScript is the pkScript of the second level output. - PkScript []byte + ScriptTree // SuccessTapLeaf is the tapleaf for the redemption path. SuccessTapLeaf txscript.TapLeaf - - // TapscriptTree is the full tapscript tree that also includes the - // control block needed to spend each of the leaves. - TapscriptTree *txscript.IndexedTapScriptTree - - // TapscriptTreeRoot is the root hash of the tapscript tree. - TapscriptRoot []byte } // TaprootSecondLevelScriptTree constructs the tapscript tree used to spend the @@ -1577,21 +1675,65 @@ func TaprootSecondLevelScriptTree(revokeKey, delayKey *btcec.PublicKey, outputKey := txscript.ComputeTaprootOutputKey( revokeKey, tapScriptRoot[:], ) - pkScript, err := PayToTaprootScript(outputKey) - if err != nil { - return nil, fmt.Errorf("unable to make taproot "+ - "pkscript: %w", err) - } return &SecondLevelScriptTree{ - TaprootKey: outputKey, - PkScript: pkScript, + ScriptTree: ScriptTree{ + TaprootKey: outputKey, + TapscriptTree: tapScriptTree, + TapscriptRoot: tapScriptRoot[:], + InternalKey: revokeKey, + }, SuccessTapLeaf: tapScriptTree.LeafMerkleProofs[0].TapLeaf, - TapscriptTree: tapScriptTree, - TapscriptRoot: tapScriptRoot[:], }, nil } +// WitnessScript returns the witness script that we'll use when signing for the +// remote party, and also verifying signatures on our transactions. As an +// example, when we create an outgoing HTLC for the remote party, we want to +// sign their success path. +func (s *SecondLevelScriptTree) WitnessScriptToSign() []byte { + return s.SuccessTapLeaf.Script +} + +// WitnessScriptForPath returns the witness script for the given spending path. +// An error is returned if the path is unknown. +func (s *SecondLevelScriptTree) WitnessScriptForPath(path ScriptPath, +) ([]byte, error) { + + switch path { + case ScriptPathDelay: + fallthrough + case ScriptPathSuccess: + return s.SuccessTapLeaf.Script, nil + + default: + return nil, fmt.Errorf("unknown script path: %v", path) + } +} + +// CtrlBlockForPath returns the control block for the given spending path. For +// script types that don't have a control block, nil is returned. +func (s *SecondLevelScriptTree) CtrlBlockForPath(path ScriptPath, +) (*txscript.ControlBlock, error) { + + switch path { + case ScriptPathDelay: + fallthrough + case ScriptPathSuccess: + return lnutils.Ptr(MakeTaprootCtrlBlock( + s.SuccessTapLeaf.Script, s.InternalKey, + s.TapscriptTree, + )), nil + + default: + return nil, fmt.Errorf("unknown script path: %v", path) + } +} + +// A compile time check to ensure SecondLevelScriptTree implements the +// TapscriptDescriptor interface. +var _ TapscriptDescriptor = (*SecondLevelScriptTree)(nil) + // TaprootHtlcSpendRevoke spends a second-level HTLC output via the revocation // path. This uses the top level keyspend path to redeem the contested output. // @@ -1910,9 +2052,7 @@ func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) // key, or a NUMs point for the remote output) along with the tapscript leaf // that can spend the output after a delay. type CommitScriptTree struct { - // TaprootKey is the key that will be used to generate the taproot - // output. - TaprootKey *btcec.PublicKey + ScriptTree // SettleLeaf is the leaf used to settle the output after the delay. SettleLeaf txscript.TapLeaf @@ -1920,13 +2060,61 @@ type CommitScriptTree struct { // RevocationLeaf is the leaf used to spend the output with the // revocation key signature. RevocationLeaf txscript.TapLeaf +} - // TapscriptTree is the full tapscript tree that also includes the - // control block needed to spend each of the leaves. - TapscriptTree *txscript.IndexedTapScriptTree +// A compile time check to ensure CommitScriptTree implements the +// TapscriptDescriptor interface. +var _ TapscriptDescriptor = (*CommitScriptTree)(nil) - // TapscriptTreeRoot is the root hash of the tapscript tree. - TapscriptRoot []byte +// WitnessScript returns the witness script that we'll use when signing for the +// remote party, and also verifying signatures on our transactions. As an +// example, when we create an outgoing HTLC for the remote party, we want to +// sign their success path. +func (s *CommitScriptTree) WitnessScriptToSign() []byte { + // TODO(roasbeef): abstraction leak here? always dependent + return nil +} + +// WitnessScriptForPath returns the witness script for the given spending path. +// An error is returned if the path is unknown. +func (c *CommitScriptTree) WitnessScriptForPath(path ScriptPath, +) ([]byte, error) { + + switch path { + // For the commitment output, the delay and success path are the same, + // so we'll fall through here to success. + case ScriptPathDelay: + fallthrough + case ScriptPathSuccess: + return c.SettleLeaf.Script, nil + case ScriptPathRevocation: + return c.RevocationLeaf.Script, nil + default: + return nil, fmt.Errorf("unknown script path: %v", path) + } +} + +// CtrlBlockForPath returns the control block for the given spending path. For +// script types that don't have a control block, nil is returned. +func (c *CommitScriptTree) CtrlBlockForPath(path ScriptPath, +) (*txscript.ControlBlock, error) { + + switch path { + case ScriptPathDelay: + fallthrough + case ScriptPathSuccess: + return lnutils.Ptr(MakeTaprootCtrlBlock( + c.SettleLeaf.Script, c.InternalKey, + c.TapscriptTree, + )), nil + case ScriptPathRevocation: + return lnutils.Ptr(MakeTaprootCtrlBlock( + c.RevocationLeaf.Script, c.InternalKey, + c.TapscriptTree, + )), nil + default: + return nil, fmt.Errorf("unknown script path: %v", path) + } } // NewLocalCommitScriptTree returns a new CommitScript tree that can be used to @@ -1977,11 +2165,14 @@ func NewLocalCommitScriptTree(csvTimeout uint32, ) return &CommitScriptTree{ + ScriptTree: ScriptTree{ + TaprootKey: toLocalOutputKey, + TapscriptTree: tapScriptTree, + TapscriptRoot: tapScriptRoot[:], + InternalKey: &TaprootNUMSKey, + }, SettleLeaf: delayTapLeaf, RevocationLeaf: revokeTapLeaf, - TaprootKey: toLocalOutputKey, - TapscriptTree: tapScriptTree, - TapscriptRoot: tapScriptRoot[:], }, nil } @@ -2380,10 +2571,13 @@ func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey, ) return &CommitScriptTree{ - TaprootKey: toRemoteOutputKey, - SettleLeaf: tapLeaf, - TapscriptTree: tapScriptTree, - TapscriptRoot: tapScriptRoot[:], + ScriptTree: ScriptTree{ + TaprootKey: toRemoteOutputKey, + TapscriptTree: tapScriptTree, + TapscriptRoot: tapScriptRoot[:], + InternalKey: &TaprootNUMSKey, + }, + SettleLeaf: tapLeaf, }, nil } @@ -2552,19 +2746,10 @@ func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) { // AnchorScriptTree holds all the contents needed to sweep a taproot anchor // output on chain. type AnchorScriptTree struct { - // TaprootKey is the key that will be used to generate the taproot - // output. - TaprootKey *btcec.PublicKey + ScriptTree // SweepLeaf is the leaf used to settle the output after the delay. SweepLeaf txscript.TapLeaf - - // TapscriptTree is the full tapscript tree that also includes the - // control block needed to spend each of the leaves. - TapscriptTree *txscript.IndexedTapScriptTree - - // TapscriptTreeRoot is the root hash of the tapscript tree. - TapscriptRoot []byte } // NewAnchorScriptTree makes a new script tree for an anchor output with the @@ -2596,13 +2781,63 @@ func NewAnchorScriptTree(anchorKey *btcec.PublicKey, ) return &AnchorScriptTree{ - TaprootKey: anchorOutputKey, - SweepLeaf: tapLeaf, - TapscriptTree: tapScriptTree, - TapscriptRoot: tapScriptRoot[:], + ScriptTree: ScriptTree{ + TaprootKey: anchorOutputKey, + TapscriptTree: tapScriptTree, + TapscriptRoot: tapScriptRoot[:], + InternalKey: anchorKey, + }, + SweepLeaf: tapLeaf, }, nil } +// WitnessScript returns the witness script that we'll use when signing for the +// remote party, and also verifying signatures on our transactions. As an +// example, when we create an outgoing HTLC for the remote party, we want to +// sign their success path. +func (s *AnchorScriptTree) WitnessScriptToSign() []byte { + return s.SweepLeaf.Script +} + +// WitnessScriptForPath returns the witness script for the given spending path. +// An error is returned if the path is unknown. +func (s *AnchorScriptTree) WitnessScriptForPath(path ScriptPath, +) ([]byte, error) { + + switch path { + case ScriptPathDelay: + fallthrough + case ScriptPathSuccess: + return s.SweepLeaf.Script, nil + + default: + return nil, fmt.Errorf("unknown script path: %v", path) + } +} + +// CtrlBlockForPath returns the control block for the given spending path. For +// script types that don't have a control block, nil is returned. +func (a *AnchorScriptTree) CtrlBlockForPath(path ScriptPath, +) (*txscript.ControlBlock, error) { + + switch path { + case ScriptPathDelay: + fallthrough + case ScriptPathSuccess: + return lnutils.Ptr(MakeTaprootCtrlBlock( + a.SweepLeaf.Script, a.InternalKey, + a.TapscriptTree, + )), nil + + default: + return nil, fmt.Errorf("unknown script path: %v", path) + } +} + +// A compile time check to ensure AnchorScriptTree implements the +// TapscriptDescriptor interface. +var _ TapscriptDescriptor = (*AnchorScriptTree)(nil) + // TaprootOutputKeyAnchor returns the segwit v1 (taproot) witness program that // encodes the anchor output spending conditions: the passed key can be used // for keyspend, with the OP_CSV 16 clause living within an internal tapscript diff --git a/input/size_test.go b/input/size_test.go index f1a65963e..0edcff28b 100644 --- a/input/size_test.go +++ b/input/size_test.go @@ -1060,7 +1060,7 @@ var witnessSizeTests = []witnessSizeTest{ htlcScriptTree, err := input.SenderHTLCScriptTaproot( senderKey.PubKey(), receiverKey.PubKey(), - revokeKey.PubKey(), payHash[:], + revokeKey.PubKey(), payHash[:], false, ) signDesc := &input.SignDescriptor{ @@ -1100,7 +1100,7 @@ var witnessSizeTests = []witnessSizeTest{ htlcScriptTree, err := input.ReceiverHTLCScriptTaproot( testCLTVExpiry, senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), - payHash[:], + payHash[:], false, ) signDesc := &input.SignDescriptor{ @@ -1140,7 +1140,7 @@ var witnessSizeTests = []witnessSizeTest{ htlcScriptTree, err := input.ReceiverHTLCScriptTaproot( testCLTVExpiry, senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), - payHash[:], + payHash[:], false, ) timeoutLeaf := htlcScriptTree.TimeoutTapLeaf @@ -1183,7 +1183,7 @@ var witnessSizeTests = []witnessSizeTest{ htlcScriptTree, err := input.SenderHTLCScriptTaproot( senderKey.PubKey(), receiverKey.PubKey(), - revokeKey.PubKey(), payHash[:], + revokeKey.PubKey(), payHash[:], false, ) require.NoError(t, err) @@ -1242,8 +1242,7 @@ var witnessSizeTests = []witnessSizeTest{ htlcScriptTree, err := input.SenderHTLCScriptTaproot( senderKey.PubKey(), receiverKey.PubKey(), - revokeKey.PubKey(), - payHash[:], + revokeKey.PubKey(), payHash[:], false, ) require.NoError(t, err) @@ -1289,7 +1288,7 @@ var witnessSizeTests = []witnessSizeTest{ htlcScriptTree, err := input.ReceiverHTLCScriptTaproot( testCLTVExpiry, senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), - payHash[:], + payHash[:], false, ) successsLeaf := htlcScriptTree.SuccessTapLeaf @@ -1373,7 +1372,7 @@ func genTimeoutTx(t *testing.T, ) if chanType.IsTaproot() { tapscriptTree, err = input.SenderHTLCScriptTaproot( - testPubkey, testPubkey, testPubkey, testHash160, + testPubkey, testPubkey, testPubkey, testHash160, false, ) require.NoError(t, err) @@ -1442,7 +1441,7 @@ func genSuccessTx(t *testing.T, chanType channeldb.ChannelType) *wire.MsgTx { if chanType.IsTaproot() { tapscriptTree, err = input.ReceiverHTLCScriptTaproot( testCLTVExpiry, testPubkey, testPubkey, testPubkey, - testHash160, + testHash160, false, ) require.NoError(t, err) diff --git a/input/taproot_test.go b/input/taproot_test.go index 3a0f85d41..bcc5695c7 100644 --- a/input/taproot_test.go +++ b/input/taproot_test.go @@ -48,7 +48,7 @@ func newTestSenderHtlcScriptTree(t *testing.T) *testSenderHtlcScriptTree { payHash := preImage.Hash() htlcScriptTree, err := SenderHTLCScriptTaproot( senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), - payHash[:], + payHash[:], false, ) require.NoError(t, err) @@ -471,7 +471,7 @@ func newTestReceiverHtlcScriptTree(t *testing.T) *testReceiverHtlcScriptTree { payHash := preImage.Hash() htlcScriptTree, err := ReceiverHTLCScriptTaproot( cltvExpiry, senderKey.PubKey(), receiverKey.PubKey(), - revokeKey.PubKey(), payHash[:], + revokeKey.PubKey(), payHash[:], false, ) require.NoError(t, err) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 3025b1d2c..f283ed837 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -821,8 +821,8 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, if err != nil { return pd, err } - ourP2WSH = scriptInfo.PkScript - ourWitnessScript = scriptInfo.WitnessScript + ourP2WSH = scriptInfo.PkScript() + ourWitnessScript = scriptInfo.WitnessScriptToSign() } isDustRemote := HtlcIsDust( chanType, htlc.Incoming, false, feeRate, @@ -836,8 +836,8 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, if err != nil { return pd, err } - theirP2WSH = scriptInfo.PkScript - theirWitnessScript = scriptInfo.WitnessScript + theirP2WSH = scriptInfo.PkScript() + theirWitnessScript = scriptInfo.WitnessScriptToSign() } // Reconstruct the proper local/remote output indexes from the HTLC's @@ -1575,8 +1575,8 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate, return nil, err } - pd.theirPkScript = scriptInfo.PkScript - pd.theirWitnessScript = scriptInfo.WitnessScript + pd.theirPkScript = scriptInfo.PkScript() + pd.theirWitnessScript = scriptInfo.WitnessScriptToSign() } // For HTLC's we're offered we'll fetch the original offered HTLC @@ -2480,7 +2480,6 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // If the returned *RevocationLog is non-nil, use it to derive the info // we need. - isTaproot := chanState.ChanType.IsTaproot() if revokedLog != nil { br, ourAmt, theirAmt, err = createBreachRetribution( revokedLog, spendTx, chanState, keyRing, @@ -2513,12 +2512,21 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // If our balance exceeds the remote party's dust limit, instantiate // the sign descriptor for our output. if ourAmt >= int64(chanState.RemoteChanCfg.DustLimit) { + // As we're about to sweep our own output w/o a delay, we'll obtain + // the witness script for the success/delay path. + witnessScript, err := ourScript.WitnessScriptForPath( + input.ScriptPathDelay, + ) + if err != nil { + return nil, err + } + br.LocalOutputSignDesc = &input.SignDescriptor{ SingleTweak: keyRing.LocalCommitKeyTweak, KeyDesc: chanState.LocalChanCfg.PaymentBasePoint, - WitnessScript: ourScript.WitnessScript, + WitnessScript: witnessScript, Output: &wire.TxOut{ - PkScript: ourScript.PkScript, + PkScript: ourScript.PkScript(), Value: ourAmt, }, HashType: txscript.SigHashAll, @@ -2527,13 +2535,16 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // For taproot channels, we'll make sure to set the script path // spend (as our output on their revoked tx still needs the // delay), and set the control block. - if isTaproot { + if scriptTree, ok := ourScript.(input.TapscriptDescriptor); ok { br.LocalOutputSignDesc.SignMethod = input.TaprootScriptSpendSignMethod - ctrlBlock := input.MakeTaprootCtrlBlock( - br.LocalOutputSignDesc.WitnessScript, - &input.TaprootNUMSKey, ourScript.ScriptTree, + ctrlBlock, err := scriptTree.CtrlBlockForPath( + input.ScriptPathDelay, ) + if err != nil { + return nil, err + } + br.LocalOutputSignDesc.ControlBlock, err = ctrlBlock.ToBytes() if err != nil { return nil, err @@ -2544,28 +2555,45 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // Similarly, if their balance exceeds the remote party's dust limit, // assemble the sign descriptor for their output, which we can sweep. if theirAmt >= int64(chanState.RemoteChanCfg.DustLimit) { + // As we're trying to defend the channel against a breach + // attempt from the remote party, we want to obain the + // revocation witness script here. + witnessScript, err := theirScript.WitnessScriptForPath( + input.ScriptPathRevocation, + ) + if err != nil { + return nil, err + } + br.RemoteOutputSignDesc = &input.SignDescriptor{ KeyDesc: chanState.LocalChanCfg. RevocationBasePoint, DoubleTweak: commitmentSecret, - WitnessScript: theirScript.WitnessScript, + WitnessScript: witnessScript, Output: &wire.TxOut{ - PkScript: theirScript.PkScript, + PkScript: theirScript.PkScript(), Value: theirAmt, }, HashType: txscript.SigHashAll, } - // For taproot channels, the remote ouput (the revoked outuput) - // can be spent using a single key spend, now that we know the - // revocation key. - if isTaproot { - br.RemoteOutputSignDesc.SignMethod = input.TaprootKeySpendSignMethod + // For taproot channels, the remote output (the revoked output) + // is spent with a script path to ensure all information 3rd + // parties need to sweep anchors is revealed on chain. + if scriptTree, ok := theirScript.(input.TapscriptDescriptor); ok { + //nolint:lll + br.RemoteOutputSignDesc.SignMethod = input.TaprootScriptSpendSignMethod - // We'll also need to set the taptweak as we'll be - // signing with the full output key. - tapscriptRoot := theirScript.ScriptTree.RootNode.TapHash() - br.RemoteOutputSignDesc.TapTweak = tapscriptRoot[:] + ctrlBlock, err := scriptTree.CtrlBlockForPath( + input.ScriptPathRevocation, + ) + if err != nil { + return nil, err + } + br.RemoteOutputSignDesc.ControlBlock, err = ctrlBlock.ToBytes() + if err != nil { + return nil, err + } } } @@ -2621,9 +2649,9 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel, KeyDesc: chanState.LocalChanCfg. RevocationBasePoint, DoubleTweak: commitmentSecret, - WitnessScript: scriptInfo.WitnessScript, + WitnessScript: scriptInfo.WitnessScriptToSign(), Output: &wire.TxOut{ - PkScript: scriptInfo.PkScript, + PkScript: scriptInfo.PkScript(), Value: int64(htlc.Amt), }, HashType: txscript.SigHashAll, @@ -2632,20 +2660,26 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel, // For taproot HTLC outputs, we need to set the sign method to key // spend, and also set the tap tweak root needed to derive the proper // private key. - if chanState.ChanType.IsTaproot() { + if scriptTree, ok := scriptInfo.(input.TapscriptDescriptor); ok { signDesc.SignMethod = input.TaprootKeySpendSignMethod - tapscriptRoot := scriptInfo.ScriptTree.RootNode.TapHash() - signDesc.TapTweak = tapscriptRoot[:] + signDesc.TapTweak = scriptTree.TapTweak() + } + + // The second levle script we sign will always be the sucess path. + secondLevelWitnessScript, err := secondLevelScript.WitnessScriptForPath( + input.ScriptPathSuccess, + ) + if err != nil { + return emptyRetribution, err } // If this is a taproot output, we'll also need to obtain the second // level tap tweak as well. var secondLevelTapTweak [32]byte - if chanState.ChanType.IsTaproot() { - tapscriptRoot := secondLevelScript.ScriptTree.RootNode.TapHash() + if scriptTree, ok := secondLevelScript.(input.TapscriptDescriptor); ok { copy( - secondLevelTapTweak[:], tapscriptRoot[:], + secondLevelTapTweak[:], scriptTree.TapTweak(), ) } @@ -2655,7 +2689,8 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel, Hash: commitHash, Index: uint32(htlc.OutputIndex), }, - SecondLevelWitnessScript: secondLevelScript.WitnessScript, + SecondLevelWitnessScript: secondLevelWitnessScript, + SecondLevelTapTweak: secondLevelTapTweak, IsIncoming: htlc.Incoming, }, nil } @@ -2778,7 +2813,7 @@ func createBreachRetribution(revokedLog *channeldb.RevocationLog, func createBreachRetributionLegacy(revokedLog *channeldb.ChannelCommitment, chanState *channeldb.OpenChannel, keyRing *CommitmentKeyRing, commitmentSecret *btcec.PrivateKey, - ourScript, theirScript *ScriptInfo, + ourScript, theirScript input.ScriptDescriptor, leaseExpiry uint32) (*BreachRetribution, int64, int64, error) { commitHash := revokedLog.CommitTx.TxHash() @@ -2793,9 +2828,9 @@ func createBreachRetributionLegacy(revokedLog *channeldb.ChannelCommitment, // to find the exact index of the commitment outputs. for i, txOut := range revokedLog.CommitTx.TxOut { switch { - case bytes.Equal(txOut.PkScript, ourScript.PkScript): + case bytes.Equal(txOut.PkScript, ourScript.PkScript()): ourOutpoint.Index = uint32(i) - case bytes.Equal(txOut.PkScript, theirScript.PkScript): + case bytes.Equal(txOut.PkScript, theirScript.PkScript()): theirOutpoint.Index = uint32(i) } } @@ -6398,7 +6433,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si ) for outputIndex, txOut := range commitTxBroadcast.TxOut { - if bytes.Equal(txOut.PkScript, selfScript.PkScript) { + if bytes.Equal(txOut.PkScript, selfScript.PkScript()) { selfPoint = &wire.OutPoint{ Hash: *commitSpend.SpenderTxHash, Index: uint32(outputIndex), @@ -6414,15 +6449,25 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si var commitResolution *CommitOutputResolution if selfPoint != nil { localPayBase := chanState.LocalChanCfg.PaymentBasePoint + + // As the remote party has force closed, we just need the + // success witness script. + witnessScript, err := selfScript.WitnessScriptForPath( + input.ScriptPathSuccess, + ) + if err != nil { + return nil, err + } + commitResolution = &CommitOutputResolution{ SelfOutPoint: *selfPoint, SelfOutputSignDesc: input.SignDescriptor{ KeyDesc: localPayBase, SingleTweak: keyRing.LocalCommitKeyTweak, - WitnessScript: selfScript.WitnessScript, + WitnessScript: witnessScript, Output: &wire.TxOut{ Value: localBalance, - PkScript: selfScript.PkScript, + PkScript: selfScript.PkScript(), }, HashType: txscript.SigHashAll, }, @@ -6431,16 +6476,16 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si // For taproot channels, we'll need to set some additional // fields to ensure the output can be swept. - // - // TODO(roasbef): abstract into new func - if chanState.ChanType.IsTaproot() { + if scriptTree, ok := selfScript.(input.TapscriptDescriptor); ok { commitResolution.SelfOutputSignDesc.SignMethod = input.TaprootScriptSpendSignMethod - ctrlBlock := input.MakeTaprootCtrlBlock( - commitResolution.SelfOutputSignDesc.WitnessScript, - &input.TaprootNUMSKey, selfScript.ScriptTree, + ctrlBlock, err := scriptTree.CtrlBlockForPath( + input.ScriptPathSuccess, ) + if err != nil { + return nil, err + } commitResolution.SelfOutputSignDesc.ControlBlock, err = ctrlBlock.ToBytes() if err != nil { return nil, err @@ -6630,8 +6675,17 @@ func newOutgoingHtlcResolution(signer input.Signer, if err != nil { return nil, err } - htlcPkScript := htlcScriptInfo.PkScript - htlcWitnessScript := htlcScriptInfo.WitnessScript + htlcPkScript := htlcScriptInfo.PkScript() + + // As this is an outgoing HTLC, we just care about the timeout path + // here. + scriptPath := input.ScriptPathTimeout + htlcWitnessScript, err := htlcScriptInfo.WitnessScriptForPath( + scriptPath, + ) + if err != nil { + return nil, err + } // If we're spending this HTLC output from the remote node's // commitment, then we won't need to go to the second level as our @@ -6650,12 +6704,16 @@ func newOutgoingHtlcResolution(signer input.Signer, HashType: txscript.SigHashAll, } - if chanType.IsTaproot() { + scriptTree, ok := htlcScriptInfo.(input.TapscriptDescriptor) + if ok { signDesc.SignMethod = input.TaprootScriptSpendSignMethod - ctrlBlock := input.MakeTaprootCtrlBlock( - htlcWitnessScript, keyRing.RevocationKey, - htlcScriptInfo.ScriptTree, + + ctrlBlock, err := scriptTree.CtrlBlockForPath( + scriptPath, ) + if err != nil { + return nil, err + } signDesc.ControlBlock, err = ctrlBlock.ToBytes() if err != nil { return nil, err @@ -6718,7 +6776,7 @@ func newOutgoingHtlcResolution(signer input.Signer, // for the timeout transaction, and populate it as well. sigHashType := HtlcSigHashType(chanType) var timeoutWitness wire.TxWitness - if chanType.IsTaproot() { + if scriptTree, ok := htlcScriptInfo.(input.TapscriptDescriptor); ok { // TODO(roasbeef): make sure default elsewhere timeoutSignDesc.SignMethod = input.TaprootScriptSpendSignMethod timeoutSignDesc.HashType = txscript.SigHashDefault @@ -6726,7 +6784,7 @@ func newOutgoingHtlcResolution(signer input.Signer, timeoutWitness, err = input.SenderHTLCScriptTaprootTimeout( htlcSig, sigHashType, signer, &timeoutSignDesc, timeoutTx, keyRing.RevocationKey, - htlcScriptInfo.ScriptTree, + scriptTree.TapScriptTree(), ) if err != nil { return nil, err @@ -6761,7 +6819,7 @@ func newOutgoingHtlcResolution(signer input.Signer, // transaction creates so we can generate the signDesc required to // complete the claim process after a delay period. var ( - htlcSweepScript *ScriptInfo + htlcSweepScript input.ScriptDescriptor signMethod input.SignMethod ctrlBlock []byte ) @@ -6781,21 +6839,29 @@ func newOutgoingHtlcResolution(signer input.Signer, return nil, err } - htlcSweepScript = &ScriptInfo{ - PkScript: secondLevelScriptTree.PkScript, - WitnessScript: secondLevelScriptTree.SuccessTapLeaf.Script, - } - signMethod = input.TaprootScriptSpendSignMethod - controlBlock := input.MakeTaprootCtrlBlock( - htlcSweepScript.WitnessScript, keyRing.RevocationKey, - secondLevelScriptTree.TapscriptTree, + controlBlock, err := secondLevelScriptTree.CtrlBlockForPath( + input.ScriptPathSuccess, ) + if err != nil { + return nil, err + } ctrlBlock, err = controlBlock.ToBytes() if err != nil { return nil, err } + + htlcSweepScript = secondLevelScriptTree + } + + // In this case, the witness script that needs to be signed will always + // be that of the success path. + htlcSweepWitnessScript, err := htlcSweepScript.WitnessScriptForPath( + input.ScriptPathSuccess, + ) + if err != nil { + return nil, err } localDelayTweak := input.SingleTweakBytes( @@ -6813,9 +6879,9 @@ func newOutgoingHtlcResolution(signer input.Signer, SweepSignDesc: input.SignDescriptor{ KeyDesc: localChanCfg.DelayBasePoint, SingleTweak: localDelayTweak, - WitnessScript: htlcSweepScript.WitnessScript, + WitnessScript: htlcSweepWitnessScript, Output: &wire.TxOut{ - PkScript: htlcSweepScript.PkScript, + PkScript: htlcSweepScript.PkScript(), Value: int64(secondLevelOutputAmt), }, HashType: txscript.SigHashAll, @@ -6854,8 +6920,17 @@ func newIncomingHtlcResolution(signer input.Signer, return nil, err } - htlcPkScript := scriptInfo.PkScript - htlcWitnessScript := scriptInfo.WitnessScript + htlcPkScript := scriptInfo.PkScript() + + // As this is an incoming HTLC, we're attempting to sweep with the + // success path. + scriptPath := input.ScriptPathSuccess + htlcWitnessScript, err := scriptInfo.WitnessScriptForPath( + scriptPath, + ) + if err != nil { + return nil, err + } // If we're spending this output from the remote node's commitment, // then we can skip the second layer and spend the output directly. @@ -6873,12 +6948,12 @@ func newIncomingHtlcResolution(signer input.Signer, HashType: txscript.SigHashAll, } - if chanType.IsTaproot() { + if scriptTree, ok := scriptInfo.(input.TapscriptDescriptor); ok { signDesc.SignMethod = input.TaprootScriptSpendSignMethod - ctrlBlock := input.MakeTaprootCtrlBlock( - htlcWitnessScript, keyRing.RevocationKey, - scriptInfo.ScriptTree, - ) + ctrlBlock, err := scriptTree.CtrlBlockForPath(scriptPath) + if err != nil { + return nil, err + } signDesc.ControlBlock, err = ctrlBlock.ToBytes() if err != nil { return nil, err @@ -6935,14 +7010,14 @@ func newIncomingHtlcResolution(signer input.Signer, // becomes known. var successWitness wire.TxWitness sigHashType := HtlcSigHashType(chanType) - if chanType.IsTaproot() { - // TODO(roasbeef): make sure default elsewhere + if scriptTree, ok := scriptInfo.(input.TapscriptDescriptor); ok { successSignDesc.HashType = txscript.SigHashDefault successSignDesc.SignMethod = input.TaprootScriptSpendSignMethod successWitness, err = input.ReceiverHTLCScriptTaprootRedeem( htlcSig, sigHashType, nil, signer, &successSignDesc, - successTx, keyRing.RevocationKey, scriptInfo.ScriptTree, + successTx, keyRing.RevocationKey, + scriptTree.TapScriptTree(), ) if err != nil { return nil, err @@ -6976,7 +7051,7 @@ func newIncomingHtlcResolution(signer input.Signer, // creates so we can generate the proper signDesc to sweep it after the // CSV delay has passed. var ( - htlcSweepScript *ScriptInfo + htlcSweepScript input.ScriptDescriptor signMethod input.SignMethod ctrlBlock []byte ) @@ -6996,23 +7071,31 @@ func newIncomingHtlcResolution(signer input.Signer, return nil, err } - htlcSweepScript = &ScriptInfo{ - PkScript: secondLevelScriptTree.PkScript, - WitnessScript: secondLevelScriptTree.SuccessTapLeaf.Script, - } - signMethod = input.TaprootScriptSpendSignMethod - controlBlock := input.MakeTaprootCtrlBlock( - htlcSweepScript.WitnessScript, keyRing.RevocationKey, - secondLevelScriptTree.TapscriptTree, + controlBlock, err := secondLevelScriptTree.CtrlBlockForPath( + input.ScriptPathSuccess, ) + if err != nil { + return nil, err + } ctrlBlock, err = controlBlock.ToBytes() if err != nil { return nil, err } // TODO(roasbeef): conslidate logic to reduce vertical noise + + htlcSweepScript = secondLevelScriptTree + } + + // In this case, the witness script that needs to be signed will always + // be that of the success path. + htlcSweepWitnessScript, err := htlcSweepScript.WitnessScriptForPath( + input.ScriptPathSuccess, + ) + if err != nil { + return nil, err } localDelayTweak := input.SingleTweakBytes( @@ -7029,9 +7112,9 @@ func newIncomingHtlcResolution(signer input.Signer, SweepSignDesc: input.SignDescriptor{ KeyDesc: localChanCfg.DelayBasePoint, SingleTweak: localDelayTweak, - WitnessScript: htlcSweepScript.WitnessScript, + WitnessScript: htlcSweepWitnessScript, Output: &wire.TxOut{ - PkScript: htlcSweepScript.PkScript, + PkScript: htlcSweepScript.PkScript(), Value: int64(secondLevelOutputAmt), }, HashType: txscript.SigHashAll, @@ -7281,7 +7364,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, delayOut *wire.TxOut ) for i, txOut := range commitTx.TxOut { - if !bytes.Equal(toLocalScript.PkScript, txOut.PkScript) { + if !bytes.Equal(toLocalScript.PkScript(), txOut.PkScript) { continue } @@ -7298,6 +7381,16 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, // nil. var commitResolution *CommitOutputResolution if delayOut != nil { + // When attempting to sweep our own output, we only need the + // witness script for the delay path + scriptPath := input.ScriptPathDelay + witnessScript, err := toLocalScript.WitnessScriptForPath( + scriptPath, + ) + if err != nil { + return nil, err + } + localBalance := delayOut.Value commitResolution = &CommitOutputResolution{ SelfOutPoint: wire.OutPoint{ @@ -7307,7 +7400,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, SelfOutputSignDesc: input.SignDescriptor{ KeyDesc: chanState.LocalChanCfg.DelayBasePoint, SingleTweak: keyRing.LocalCommitKeyTweak, - WitnessScript: toLocalScript.WitnessScript, + WitnessScript: witnessScript, Output: &wire.TxOut{ PkScript: delayOut.PkScript, Value: localBalance, @@ -7321,14 +7414,17 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, // fields to ensure the output can be swept. // // TODO(roasbef): abstract into new func - if chanState.ChanType.IsTaproot() { + scriptTree, ok := toLocalScript.(input.TapscriptDescriptor) + if ok { commitResolution.SelfOutputSignDesc.SignMethod = input.TaprootScriptSpendSignMethod - ctrlBlock := input.MakeTaprootCtrlBlock( - commitResolution.SelfOutputSignDesc.WitnessScript, - keyRing.RevocationKey, toLocalScript.ScriptTree, + ctrlBlock, err := scriptTree.CtrlBlockForPath( + scriptPath, ) + if err != nil { + return nil, err + } commitResolution.SelfOutputSignDesc.ControlBlock, err = ctrlBlock.ToBytes() if err != nil { return nil, err @@ -7712,15 +7808,26 @@ func NewAnchorResolution(chanState *channeldb.OpenChannel, localAnchor, remoteAnchor = remoteAnchor, localAnchor } + // TODO(roasbeef): remote anchor not needed above + // Look up the script on the commitment transaction. It may not be // present if there is no output paying to us. found, index := input.FindScriptOutputIndex( - commitTx, localAnchor.PkScript, + commitTx, localAnchor.PkScript(), ) if !found { return nil, nil } + // For anchor outputs, we'll only ever care about the success path. + // script (sweep after 1 block csv delay). + anchorWitnessScript, err := localAnchor.WitnessScriptForPath( + input.ScriptPathSuccess, + ) + if err != nil { + return nil, err + } + outPoint := &wire.OutPoint{ Hash: commitTx.TxHash(), Index: index, @@ -7729,9 +7836,9 @@ func NewAnchorResolution(chanState *channeldb.OpenChannel, // Instantiate the sign descriptor that allows sweeping of the anchor. signDesc := &input.SignDescriptor{ KeyDesc: chanState.LocalChanCfg.MultiSigKey, - WitnessScript: localAnchor.WitnessScript, + WitnessScript: anchorWitnessScript, Output: &wire.TxOut{ - PkScript: localAnchor.PkScript, + PkScript: localAnchor.PkScript(), Value: int64(anchorSize), }, HashType: txscript.SigHashAll, @@ -7739,12 +7846,12 @@ func NewAnchorResolution(chanState *channeldb.OpenChannel, // For taproot outputs, we'll need to ensure that the proper sign // method is used, and the tweak as well. - if chanState.ChanType.IsTaproot() { + if scriptTree, ok := localAnchor.(input.TapscriptDescriptor); ok { signDesc.SignMethod = input.TaprootKeySpendSignMethod signDesc.HashType = txscript.SigHashDefault signDesc.PrevOutputFetcher = txscript.NewCannedPrevOutputFetcher( - localAnchor.PkScript, int64(anchorSize), + localAnchor.PkScript(), int64(anchorSize), ) // For anchor outputs with taproot channels, the key desc is @@ -7766,13 +7873,9 @@ func NewAnchorResolution(chanState *channeldb.OpenChannel, signDesc.KeyDesc = chanState.LocalChanCfg.PaymentBasePoint } - // TODO(roasbeef): need to be payment point if remote, delay - // point otherwise? - // Finally, as this is a keyspend method, we'll need to also // include the taptweak as well. - tapscriptRoot := localAnchor.ScriptTree.RootNode.TapHash() - signDesc.TapTweak = tapscriptRoot[:] + signDesc.TapTweak = scriptTree.TapTweak() } var witnessWeight int64 diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 276b378bc..713598c52 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -9915,11 +9915,11 @@ func TestCreateBreachRetributionLegacy(t *testing.T) { theirOp := revokedLog.CommitTx.TxOut[1] // Create the dummy scripts. - ourScript := &ScriptInfo{ - PkScript: ourOp.PkScript, + ourScript := &WitnessScriptDesc{ + OutputScript: ourOp.PkScript, } - theirScript := &ScriptInfo{ - PkScript: theirOp.PkScript, + theirScript := &WitnessScriptDesc{ + OutputScript: theirOp.PkScript, } // Create the breach retribution using the legacy format. diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 4c75b0817..c17ff9183 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -180,20 +180,41 @@ func DeriveCommitmentKeys(commitPoint *btcec.PublicKey, return keyRing } -// ScriptInfo holds a redeem script and hash. -type ScriptInfo struct { - // PkScript is the output's PkScript. - PkScript []byte +// WitnessScriptDesc holds the output script and the witness script for p2wsh +// outputs. +type WitnessScriptDesc struct { + // OutputScript is the output's PkScript. + OutputScript []byte // WitnessScript is the full script required to properly redeem the // output. This field should be set to the full script if a p2wsh // output is being signed. For p2wkh it should be set equal to the // PkScript. WitnessScript []byte +} - // ScriptTree is the script tree that stores all the scripts that are - // committed to by the above PkScript, if it's a P2TR script template. - ScriptTree *txscript.IndexedTapScriptTree +// PkScript is the public key script that commits to the final +// contract. +func (w *WitnessScriptDesc) PkScript() []byte { + return w.OutputScript +} + +// WitnessScript returns the witness script that we'll use when signing for the +// remote party, and also verifying signatures on our transactions. As an +// example, when we create an outgoing HTLC for the remote party, we want to +// sign their success path. +func (w *WitnessScriptDesc) WitnessScriptToSign() []byte { + return w.WitnessScript +} + +// WitnessScriptForPath returns the witness script for the given spending path. +// An error is returned if the path is unknown. This is useful as when +// constructing a contrl block for a given path, one also needs witness script +// being signed. +func (w *WitnessScriptDesc) WitnessScriptForPath(path input.ScriptPath, +) ([]byte, error) { + + return w.WitnessScript, nil } // CommitScriptToSelf constructs the public key script for the output on the @@ -203,8 +224,9 @@ type ScriptInfo struct { // party learns of the preimage to the revocation hash, then they can claim all // the settled funds in the channel, plus the unsettled funds. func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, - selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32) ( - *ScriptInfo, error) { + selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32, +) ( + input.ScriptDescriptor, error) { switch { // For taproot scripts, we'll need to make a slightly modified script @@ -213,28 +235,9 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, // // Our "redeem" script here is just the taproot witness program. case chanType.IsTaproot(): - toLocalScriptTree, err := input.NewLocalCommitScriptTree( + return input.NewLocalCommitScriptTree( csvDelay, selfKey, revokeKey, ) - if err != nil { - return nil, fmt.Errorf("unable to generate taproot "+ - "key: %w", err) - } - - toLocalPkScript, err := input.PayToTaprootScript( - toLocalScriptTree.TaprootKey, - ) - if err != nil { - return nil, fmt.Errorf("unable to gen taproot "+ - "pkscript: %w", err) - } - - // TODO(rosabeef): recator to be able to get script key - return &ScriptInfo{ - WitnessScript: toLocalScriptTree.SettleLeaf.Script, - PkScript: toLocalPkScript, - ScriptTree: toLocalScriptTree.TapscriptTree, - }, nil // If we are the initiator of a leased channel, then we have an // additional CLTV requirement in addition to the usual CSV @@ -254,8 +257,8 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, return nil, err } - return &ScriptInfo{ - PkScript: toLocalScriptHash, + return &WitnessScriptDesc{ + OutputScript: toLocalScriptHash, WitnessScript: toLocalRedeemScript, }, nil @@ -274,8 +277,8 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, return nil, err } - return &ScriptInfo{ - PkScript: toLocalScriptHash, + return &WitnessScriptDesc{ + OutputScript: toLocalScriptHash, WitnessScript: toLocalRedeemScript, }, nil } @@ -288,7 +291,7 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, // what must be satisfied in order to spend the output. func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, remoteKey *btcec.PublicKey, - leaseExpiry uint32) (*ScriptInfo, uint32, error) { + leaseExpiry uint32) (input.ScriptDescriptor, uint32, error) { switch { // If we are not the initiator of a leased channel, then the remote @@ -307,8 +310,8 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, return nil, 0, err } - return &ScriptInfo{ - PkScript: p2wsh, + return &WitnessScriptDesc{ + OutputScript: p2wsh, WitnessScript: script, }, 1, nil @@ -323,18 +326,7 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, return nil, 0, err } - toRemotePkScript, err := input.PayToTaprootScript( - toRemoteScriptTree.TaprootKey, - ) - if err != nil { - return nil, 0, err - } - - return &ScriptInfo{ - WitnessScript: toRemoteScriptTree.SettleLeaf.Script, - PkScript: toRemotePkScript, - ScriptTree: toRemoteScriptTree.TapscriptTree, - }, 1, nil + return toRemoteScriptTree, 1, nil // If this channel type has anchors, we derive the delayed to_remote // script. @@ -349,8 +341,8 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, return nil, 0, err } - return &ScriptInfo{ - PkScript: p2wsh, + return &WitnessScriptDesc{ + OutputScript: p2wsh, WitnessScript: script, }, 1, nil @@ -363,9 +355,9 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, // Since this is a regular P2WKH, the WitnessScipt and PkScript // should both be set to the script hash. - return &ScriptInfo{ + return &WitnessScriptDesc{ + OutputScript: p2wkh, WitnessScript: p2wkh, - PkScript: p2wkh, }, 0, nil } } @@ -417,59 +409,48 @@ func HtlcSecondLevelInputSequence(chanType channeldb.ChannelType) uint32 { // we are generating the to_local script for. func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool, revocationKey, delayKey *btcec.PublicKey, - csvDelay, leaseExpiry uint32) (*ScriptInfo, error) { + csvDelay, leaseExpiry uint32) (input.ScriptDescriptor, error) { - var ( - witnessScript []byte - pkScript []byte - err error - ) switch { // For taproot channels, the pkScript is a segwit v1 p2tr output. case chanType.IsTaproot(): - taprootOutputKey, err := input.TaprootSecondLevelHtlcScript( + return input.TaprootSecondLevelScriptTree( revocationKey, delayKey, csvDelay, ) - if err != nil { - return nil, err - } - - pkScript, err = input.PayToTaprootScript(taprootOutputKey) - if err != nil { - return nil, err - } // If we are the initiator of a leased channel, then we have an // additional CLTV requirement in addition to the usual CSV // requirement. case initiator && chanType.HasLeaseExpiration(): - witnessScript, err = input.LeaseSecondLevelHtlcScript( + witnessScript, err := input.LeaseSecondLevelHtlcScript( revocationKey, delayKey, csvDelay, leaseExpiry, ) - pkScript, err = input.WitnessScriptHash(witnessScript) + pkScript, err := input.WitnessScriptHash(witnessScript) if err != nil { return nil, err } + return &WitnessScriptDesc{ + OutputScript: pkScript, + WitnessScript: witnessScript, + }, nil + default: - witnessScript, err = input.SecondLevelHtlcScript( + witnessScript, err := input.SecondLevelHtlcScript( revocationKey, delayKey, csvDelay, ) - pkScript, err = input.WitnessScriptHash(witnessScript) + pkScript, err := input.WitnessScriptHash(witnessScript) if err != nil { return nil, err } - } - if err != nil { - return nil, err - } - return &ScriptInfo{ - PkScript: pkScript, - WitnessScript: witnessScript, - }, nil + return &WitnessScriptDesc{ + OutputScript: pkScript, + WitnessScript: witnessScript, + }, nil + } } // CommitWeight returns the base commitment weight before adding HTLCs. @@ -529,11 +510,14 @@ func HtlcSuccessFee(chanType channeldb.ChannelType, // anchor. func CommitScriptAnchors(chanType channeldb.ChannelType, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, - keyRing *CommitmentKeyRing) (*ScriptInfo, *ScriptInfo, error) { + keyRing *CommitmentKeyRing) ( + input.ScriptDescriptor, input.ScriptDescriptor, error) { var ( - anchorScript func(key *btcec.PublicKey) (*ScriptInfo, error) - keySelector func(*channeldb.ChannelConfig, + anchorScript func(key *btcec.PublicKey) ( + input.ScriptDescriptor, error) + + keySelector func(*channeldb.ChannelConfig, bool) *btcec.PublicKey ) @@ -542,25 +526,11 @@ func CommitScriptAnchors(chanType channeldb.ChannelType, // 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) { - anchorScriptTree, err := input.NewAnchorScriptTree( + anchorScript = func(key *btcec.PublicKey, + ) (input.ScriptDescriptor, error) { + return input.NewAnchorScriptTree( key, ) - if err != nil { - return nil, err - } - - anchorPkScript, err := input.PayToTaprootScript( - anchorScriptTree.TaprootKey, - ) - if err != nil { - return nil, err - } - - return &ScriptInfo{ - PkScript: anchorPkScript, - ScriptTree: anchorScriptTree.TapscriptTree, - }, nil } keySelector = func(cfg *channeldb.ChannelConfig, @@ -578,7 +548,9 @@ func CommitScriptAnchors(chanType channeldb.ChannelType, default: // For normal channels, we'll create a p2wsh script based on // the target key. - anchorScript = func(key *btcec.PublicKey) (*ScriptInfo, error) { + anchorScript = func(key *btcec.PublicKey, + ) (input.ScriptDescriptor, error) { + script, err := input.CommitScriptAnchor(key) if err != nil { return nil, err @@ -589,8 +561,8 @@ func CommitScriptAnchors(chanType channeldb.ChannelType, return nil, err } - return &ScriptInfo{ - PkScript: scriptHash, + return &WitnessScriptDesc{ + OutputScript: scriptHash, WitnessScript: script, }, nil } @@ -924,7 +896,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, localOutput := amountToLocal >= localChanCfg.DustLimit if localOutput { commitTx.AddTxOut(&wire.TxOut{ - PkScript: toLocalScript.PkScript, + PkScript: toLocalScript.PkScript(), Value: int64(amountToLocal), }) } @@ -932,7 +904,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, remoteOutput := amountToRemote >= localChanCfg.DustLimit if remoteOutput { commitTx.AddTxOut(&wire.TxOut{ - PkScript: toRemoteScript.PkScript, + PkScript: toRemoteScript.PkScript(), Value: int64(amountToRemote), }) } @@ -950,7 +922,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, // or there are HTLCs. if localOutput || numHTLCs > 0 { commitTx.AddTxOut(&wire.TxOut{ - PkScript: localAnchor.PkScript, + PkScript: localAnchor.PkScript(), Value: int64(anchorSize), }) } @@ -959,7 +931,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, // output or there are HTLCs. if remoteOutput || numHTLCs > 0 { commitTx.AddTxOut(&wire.TxOut{ - PkScript: remoteAnchor.PkScript, + PkScript: remoteAnchor.PkScript(), Value: int64(anchorSize), }) } @@ -1013,7 +985,7 @@ func CoopCloseBalance(chanType channeldb.ChannelType, isInitiator bool, // channel. func genSegwitV0HtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool, timeout uint32, rHash [32]byte, - keyRing *CommitmentKeyRing) (*ScriptInfo, error) { + keyRing *CommitmentKeyRing) (*WitnessScriptDesc, error) { var ( witnessScript []byte @@ -1077,8 +1049,8 @@ func genSegwitV0HtlcScript(chanType channeldb.ChannelType, return nil, err } - return &ScriptInfo{ - PkScript: htlcP2WSH, + return &WitnessScriptDesc{ + OutputScript: htlcP2WSH, WitnessScript: witnessScript, }, nil } @@ -1086,12 +1058,12 @@ func genSegwitV0HtlcScript(chanType channeldb.ChannelType, // genTaprootHtlcScript generates the HTLC scripts for a taproot+musig2 // channel. func genTaprootHtlcScript(isIncoming, ourCommit bool, timeout uint32, - rHash [32]byte, keyRing *CommitmentKeyRing) (*ScriptInfo, error) { + rHash [32]byte, + keyRing *CommitmentKeyRing) (*input.HtlcScriptTree, error) { var ( - taprootKey *btcec.PublicKey - secondLevelScript []byte - tapScriptTree *txscript.IndexedTapScriptTree + htlcScriptTree *input.HtlcScriptTree + err error ) // Generate the proper redeem scripts for the HTLC output modified by @@ -1102,127 +1074,68 @@ func genTaprootHtlcScript(isIncoming, ourCommit bool, timeout uint32, // transaction. So we need to use the receiver's version of HTLC the // script. case isIncoming && ourCommit: - scriptTree, err := input.ReceiverHTLCScriptTaproot( + htlcScriptTree, err = input.ReceiverHTLCScriptTaproot( timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, - keyRing.RevocationKey, rHash[:], + keyRing.RevocationKey, rHash[:], ourCommit, ) - if err != nil { - return nil, err - } - - tapScriptTree = scriptTree.TapscriptTree - taprootKey = scriptTree.TaprootKey - - // As this is an HTLC on our commitment transaction, the second - // level path we care about here is the success path. - // Therefore, we'll grab the tapLeaf corresponding to the - // success path. - secondLevelScript = scriptTree.SuccessTapLeaf.Script // We're being paid via an HTLC by the remote party, and the HTLC is // being added to their commitment transaction, so we use the sender's // version of the HTLC script. case isIncoming && !ourCommit: - scriptTree, err := input.SenderHTLCScriptTaproot( + htlcScriptTree, err = input.SenderHTLCScriptTaproot( keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, - keyRing.RevocationKey, rHash[:], + keyRing.RevocationKey, rHash[:], ourCommit, ) - if err != nil { - return nil, err - } - - tapScriptTree = scriptTree.TapscriptTree - taprootKey = scriptTree.TaprootKey - - // In this case, this is an incoming HTLC on the commitment - // transaction of the remote party, so we'll return the timeout - // tapleaf since that's the second level spend they need in the - // case of a broadcast. - secondLevelScript = scriptTree.TimeoutTapLeaf.Script // We're sending an HTLC which is being added to our commitment // transaction. Therefore, we need to use the sender's version of the // HTLC script. case !isIncoming && ourCommit: - scriptTree, err := input.SenderHTLCScriptTaproot( + htlcScriptTree, err = input.SenderHTLCScriptTaproot( keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, - keyRing.RevocationKey, rHash[:], + keyRing.RevocationKey, rHash[:], ourCommit, ) if err != nil { return nil, err } - tapScriptTree = scriptTree.TapscriptTree - taprootKey = scriptTree.TaprootKey - - // This is an outgoing HTLC on our commitment transaction, so - // we need to be able to generate/verify signatures for the - // timeout path. - secondLevelScript = scriptTree.TimeoutTapLeaf.Script - // Finally, we're paying the remote party via an HTLC, which is being // added to their commitment transaction. Therefore, we use the // receiver's version of the HTLC script. case !isIncoming && !ourCommit: - scriptTree, err := input.ReceiverHTLCScriptTaproot( + htlcScriptTree, err = input.ReceiverHTLCScriptTaproot( timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, - keyRing.RevocationKey, rHash[:], + keyRing.RevocationKey, rHash[:], ourCommit, ) if err != nil { return nil, err } - - tapScriptTree = scriptTree.TapscriptTree - taprootKey = scriptTree.TaprootKey - - // This is an outgoing HTLC on the remote party's commitment - // transaction. In this case if they go on chain, they'll need - // the second level success spend, so we grab that tapscript - // path. - secondLevelScript = scriptTree.SuccessTapLeaf.Script } - // Now that we have the redeem scripts, create the P2TR public key - // script for the output itself. - p2trOutput, err := input.PayToTaprootScript(taprootKey) - if err != nil { - return nil, err - } - - return &ScriptInfo{ - PkScript: p2trOutput, - WitnessScript: secondLevelScript, - ScriptTree: tapScriptTree, - }, nil + return htlcScriptTree, nil } // genHtlcScript generates the proper P2WSH public key scripts for the HTLC // output modified by two-bits denoting if this is an incoming HTLC, and if the -// HTLC is being applied to their commitment transaction or ours. +// HTLC is being applied to their commitment transaction or ours. A script +// multiplexer for the various spending paths is returned. The script path that +// we need to sign for the remote party (2nd level HTLCs) is also returned +// along side the multiplexer. func genHtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool, - timeout uint32, rHash [32]byte, - keyRing *CommitmentKeyRing) (*ScriptInfo, error) { - - var ( - scriptInfo *ScriptInfo - err error - ) + timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing, +) (input.ScriptDescriptor, error) { if !chanType.IsTaproot() { - scriptInfo, err = genSegwitV0HtlcScript( + return genSegwitV0HtlcScript( chanType, isIncoming, ourCommit, timeout, rHash, keyRing, ) } else { - scriptInfo, err = genTaprootHtlcScript( + return genTaprootHtlcScript( isIncoming, ourCommit, timeout, rHash, keyRing, ) } - if err != nil { - return nil, err - } - - return scriptInfo, nil } // addHTLC adds a new HTLC to the passed commitment transaction. One of four @@ -1246,21 +1159,22 @@ func addHTLC(commitTx *wire.MsgTx, ourCommit bool, return err } - witnessProgram := scriptInfo.PkScript - witnessScript := scriptInfo.WitnessScript + pkScript := scriptInfo.PkScript() // Add the new HTLC outputs to the respective commitment transactions. amountPending := int64(paymentDesc.Amount.ToSatoshis()) - commitTx.AddTxOut(wire.NewTxOut(amountPending, witnessProgram)) + commitTx.AddTxOut(wire.NewTxOut(amountPending, pkScript)) // Store the pkScript of this particular PaymentDescriptor so we can // quickly locate it within the commitment transaction later. if ourCommit { - paymentDesc.ourPkScript = witnessProgram - paymentDesc.ourWitnessScript = witnessScript + paymentDesc.ourPkScript = pkScript + + paymentDesc.ourWitnessScript = scriptInfo.WitnessScriptToSign() } else { - paymentDesc.theirPkScript = witnessProgram - paymentDesc.theirWitnessScript = witnessScript + paymentDesc.theirPkScript = pkScript + + paymentDesc.theirWitnessScript = scriptInfo.WitnessScriptToSign() } return nil @@ -1329,9 +1243,9 @@ func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash, // Now compare the scripts to find our/their output index. for i, txOut := range chanCommit.CommitTx.TxOut { switch { - case bytes.Equal(txOut.PkScript, ourScript.PkScript): + case bytes.Equal(txOut.PkScript, ourScript.PkScript()): ourIndex = uint32(i) - case bytes.Equal(txOut.PkScript, theirScript.PkScript): + case bytes.Equal(txOut.PkScript, theirScript.PkScript()): theirIndex = uint32(i) } } diff --git a/lnwallet/transactions.go b/lnwallet/transactions.go index f66312a39..1cf954d3c 100644 --- a/lnwallet/transactions.go +++ b/lnwallet/transactions.go @@ -66,8 +66,6 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, } successTx.AddTxIn(txin) - var pkScript []byte - // Next, we'll generate the script used as the output for all second // level HTLC which forces a covenant w.r.t what can be done with all // HTLC outputs. @@ -79,13 +77,11 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, return nil, err } - pkScript = scriptInfo.PkScript - // Finally, the output is simply the amount of the HTLC (minus the // required fees), paying to the timeout script. successTx.AddTxOut(&wire.TxOut{ Value: int64(htlcAmt), - PkScript: pkScript, + PkScript: scriptInfo.PkScript(), }) return successTx, nil @@ -133,8 +129,6 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool, } timeoutTx.AddTxIn(txin) - var pkScript []byte - // Next, we'll generate the script used as the output for all second // level HTLC which forces a covenant w.r.t what can be done with all // HTLC outputs. @@ -146,13 +140,11 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool, return nil, err } - pkScript = scriptInfo.PkScript - // Finally, the output is simply the amount of the HTLC (minus the // required fees), paying to the regular second level HTLC script. timeoutTx.AddTxOut(&wire.TxOut{ Value: int64(htlcAmt), - PkScript: pkScript, + PkScript: scriptInfo.PkScript(), }) return timeoutTx, nil