From 01e9bb2bff40945977b268685e235f5ce89589a0 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 14 Jul 2021 17:06:13 -0700 Subject: [PATCH] lnwallet: coalesce different supported output scripts into single method --- contractcourt/chain_watcher.go | 11 ++---- lnwallet/channel.go | 50 ++++++++--------------- lnwallet/commitment.go | 72 +++++++++++++++++++++++++++------- lnwallet/transactions.go | 25 +++++------- 4 files changed, 88 insertions(+), 70 deletions(-) diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index ebe7e5fd9..2bf8990eb 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -372,19 +372,14 @@ func (c *chainWatcher) handleUnknownLocalState( // Next, we'll derive our script that includes the revocation base for // the remote party allowing them to claim this output before the CSV // delay if we breach. - localScript, err := input.CommitScriptToSelf( - uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), + localScript, err := lnwallet.CommitScriptToSelf( commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey, + uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), ) if err != nil { return false, err } - localPkScript, err := input.WitnessScriptHash(localScript) - if err != nil { - return false, err - } - // With all our scripts assembled, we'll examine the outputs of the // commitment transaction to determine if this is a local force close // or not. @@ -393,7 +388,7 @@ func (c *chainWatcher) handleUnknownLocalState( pkScript := output.PkScript switch { - case bytes.Equal(localPkScript, pkScript): + case bytes.Equal(localScript.PkScript, pkScript): ourCommit = true case bytes.Equal(remoteScript.PkScript, pkScript): diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 96f29f884..e448d3863 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -2315,16 +2315,12 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // number so we can have the proper witness script to sign and include // within the final witness. theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay) - theirPkScript, err := input.CommitScriptToSelf( - theirDelay, keyRing.ToLocalKey, keyRing.RevocationKey, + theirScript, err := CommitScriptToSelf( + keyRing.ToLocalKey, keyRing.RevocationKey, theirDelay, ) if err != nil { return nil, err } - theirWitnessHash, err := input.WitnessScriptHash(theirPkScript) - if err != nil { - return nil, err - } // Since it is the remote breach we are reconstructing, the output going // to us will be a to-remote script with our local params. @@ -2347,7 +2343,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, switch { case bytes.Equal(txOut.PkScript, ourScript.PkScript): ourOutpoint.Index = uint32(i) - case bytes.Equal(txOut.PkScript, theirWitnessHash): + case bytes.Equal(txOut.PkScript, theirScript.PkScript): theirOutpoint.Index = uint32(i) } } @@ -2385,9 +2381,9 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, theirSignDesc = &input.SignDescriptor{ KeyDesc: chanState.LocalChanCfg.RevocationBasePoint, DoubleTweak: commitmentSecret, - WitnessScript: theirPkScript, + WitnessScript: theirScript.WitnessScript, Output: &wire.TxOut{ - PkScript: theirWitnessHash, + PkScript: theirScript.PkScript, Value: int64(theirAmt), }, HashType: txscript.SigHashAll, @@ -2413,7 +2409,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // as we'll need it if we're revoking an HTLC output on the // remote commitment transaction, and *they* go to the second // level. - secondLevelWitnessScript, err := input.SecondLevelHtlcScript( + secondLevelScript, err := SecondLevelHtlcScript( keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay, ) if err != nil { @@ -2448,7 +2444,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, Hash: commitHash, Index: uint32(htlc.OutputIndex), }, - SecondLevelWitnessScript: secondLevelWitnessScript, + SecondLevelWitnessScript: secondLevelScript.WitnessScript, IsIncoming: htlc.Incoming, }) } @@ -5904,16 +5900,12 @@ func newOutgoingHtlcResolution(signer input.Signer, // Finally, we'll generate the script output that the timeout // transaction creates so we can generate the signDesc required to // complete the claim process after a delay period. - htlcSweepScript, err := input.SecondLevelHtlcScript( + htlcSweepScript, err := SecondLevelHtlcScript( keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, ) if err != nil { return nil, err } - htlcSweepScriptHash, err := input.WitnessScriptHash(htlcSweepScript) - if err != nil { - return nil, err - } localDelayTweak := input.SingleTweakBytes( keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey, @@ -5930,9 +5922,9 @@ func newOutgoingHtlcResolution(signer input.Signer, SweepSignDesc: input.SignDescriptor{ KeyDesc: localChanCfg.DelayBasePoint, SingleTweak: localDelayTweak, - WitnessScript: htlcSweepScript, + WitnessScript: htlcSweepScript.WitnessScript, Output: &wire.TxOut{ - PkScript: htlcSweepScriptHash, + PkScript: htlcSweepScript.PkScript, Value: int64(secondLevelOutputAmt), }, HashType: txscript.SigHashAll, @@ -6043,16 +6035,12 @@ func newIncomingHtlcResolution(signer input.Signer, // Finally, we'll generate the script that the second-level transaction // creates so we can generate the proper signDesc to sweep it after the // CSV delay has passed. - htlcSweepScript, err := input.SecondLevelHtlcScript( + htlcSweepScript, err := SecondLevelHtlcScript( keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, ) if err != nil { return nil, err } - htlcSweepScriptHash, err := input.WitnessScriptHash(htlcSweepScript) - if err != nil { - return nil, err - } localDelayTweak := input.SingleTweakBytes( keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey, @@ -6068,9 +6056,9 @@ func newIncomingHtlcResolution(signer input.Signer, SweepSignDesc: input.SignDescriptor{ KeyDesc: localChanCfg.DelayBasePoint, SingleTweak: localDelayTweak, - WitnessScript: htlcSweepScript, + WitnessScript: htlcSweepScript.WitnessScript, Output: &wire.TxOut{ - PkScript: htlcSweepScriptHash, + PkScript: htlcSweepScript.PkScript, Value: int64(secondLevelOutputAmt), }, HashType: txscript.SigHashAll, @@ -6293,16 +6281,12 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, ) - selfScript, err := input.CommitScriptToSelf( - csvTimeout, keyRing.ToLocalKey, keyRing.RevocationKey, + toLocalScript, err := CommitScriptToSelf( + keyRing.ToLocalKey, keyRing.RevocationKey, csvTimeout, ) if err != nil { return nil, err } - payToUsScriptHash, err := input.WitnessScriptHash(selfScript) - if err != nil { - return nil, err - } // Locate the output index of the delayed commitment output back to us. // We'll return the details of this output to the caller so they can @@ -6312,7 +6296,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, delayOut *wire.TxOut ) for i, txOut := range commitTx.TxOut { - if !bytes.Equal(payToUsScriptHash, txOut.PkScript) { + if !bytes.Equal(toLocalScript.PkScript, txOut.PkScript) { continue } @@ -6338,7 +6322,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, SelfOutputSignDesc: input.SignDescriptor{ KeyDesc: chanState.LocalChanCfg.DelayBasePoint, SingleTweak: keyRing.LocalCommitKeyTweak, - WitnessScript: selfScript, + WitnessScript: toLocalScript.WitnessScript, Output: &wire.TxOut{ PkScript: delayOut.PkScript, Value: localBalance, diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index a93efa90e..24bde047b 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -190,10 +190,34 @@ type ScriptInfo struct { WitnessScript []byte } -// CommitScriptToRemote creates the script that will pay to the non-owner of -// the commitment transaction, adding a delay to the script based on the -// channel type. The second return value is the CSV deleay of the output -// script, what must be satisfied in order to spend the output. +// CommitScriptToSelf constructs the public key script for the output on the +// commitment transaction paying to the "owner" of said commitment transaction. +// If the other 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(selfKey, revokeKey *btcec.PublicKey, csvDelay uint32) ( + *ScriptInfo, error) { + + toLocalRedeemScript, err := input.CommitScriptToSelf( + csvDelay, selfKey, revokeKey, + ) + if err != nil { + return nil, err + } + + toLocalScriptHash, err := input.WitnessScriptHash(toLocalRedeemScript) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: toLocalScriptHash, + WitnessScript: toLocalRedeemScript, + }, nil +} + +// CommitScriptToRemote derives the appropriate to_remote script based on the +// channel's commitment type. The second return value is the CSV delay of the +// output script, what must be satisfied in order to spend the output. func CommitScriptToRemote(chanType channeldb.ChannelType, key *btcec.PublicKey) (*ScriptInfo, uint32, error) { @@ -268,6 +292,32 @@ func HtlcSecondLevelInputSequence(chanType channeldb.ChannelType) uint32 { return 0 } +// SecondLevelHtlcScript derives the appropriate second level HTLC script based +// on the channel's commitment type. It is the uniform script that's used as the +// output for the second-level HTLC transactions. The second level transaction +// act as a sort of covenant, ensuring that a 2-of-2 multi-sig output can only +// be spent in a particular way, and to a particular output. +func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, + csvDelay uint32) (*ScriptInfo, error) { + + witnessScript, err := input.SecondLevelHtlcScript( + revocationKey, delayKey, csvDelay, + ) + if err != nil { + return nil, err + } + + pkScript, err := input.WitnessScriptHash(witnessScript) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: pkScript, + WitnessScript: witnessScript, + }, nil +} + // CommitWeight returns the base commitment weight before adding HTLCs. func CommitWeight(chanType channeldb.ChannelType) int64 { // If this commitment has anchors, it will be slightly heavier. @@ -622,15 +672,9 @@ func CreateCommitTx(chanType channeldb.ChannelType, // output after a relative block delay, or the remote node can claim // the funds with the revocation key if we broadcast a revoked // commitment transaction. - toLocalRedeemScript, err := input.CommitScriptToSelf( - uint32(localChanCfg.CsvDelay), keyRing.ToLocalKey, - keyRing.RevocationKey, - ) - if err != nil { - return nil, err - } - toLocalScriptHash, err := input.WitnessScriptHash( - toLocalRedeemScript, + toLocalScript, err := CommitScriptToSelf( + keyRing.ToLocalKey, keyRing.RevocationKey, + uint32(localChanCfg.CsvDelay), ) if err != nil { return nil, err @@ -654,7 +698,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, localOutput := amountToLocal >= localChanCfg.DustLimit if localOutput { commitTx.AddTxOut(&wire.TxOut{ - PkScript: toLocalScriptHash, + PkScript: toLocalScript.PkScript, Value: int64(amountToLocal), }) } diff --git a/lnwallet/transactions.go b/lnwallet/transactions.go index 861fdebce..2072deb6a 100644 --- a/lnwallet/transactions.go +++ b/lnwallet/transactions.go @@ -8,7 +8,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/input" ) const ( @@ -65,12 +64,9 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, // 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. - witnessScript, err := input.SecondLevelHtlcScript(revocationKey, delayKey, - csvDelay) - if err != nil { - return nil, err - } - pkScript, err := input.WitnessScriptHash(witnessScript) + script, err := SecondLevelHtlcScript( + revocationKey, delayKey, csvDelay, + ) if err != nil { return nil, err } @@ -79,7 +75,7 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, // required fees), paying to the timeout script. successTx.AddTxOut(&wire.TxOut{ Value: int64(htlcAmt), - PkScript: pkScript, + PkScript: script.PkScript, }) return successTx, nil @@ -117,6 +113,8 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, // sequence number based on the channel type. txin := &wire.TxIn{ PreviousOutPoint: htlcOutput, + SignatureScript: []byte{}, + Witness: [][]byte{}, Sequence: HtlcSecondLevelInputSequence(chanType), } timeoutTx.AddTxIn(txin) @@ -124,12 +122,9 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, // 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. - witnessScript, err := input.SecondLevelHtlcScript(revocationKey, delayKey, - csvDelay) - if err != nil { - return nil, err - } - pkScript, err := input.WitnessScriptHash(witnessScript) + script, err := SecondLevelHtlcScript( + revocationKey, delayKey, csvDelay, + ) if err != nil { return nil, err } @@ -138,7 +133,7 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, // required fees), paying to the regular second level HTLC script. timeoutTx.AddTxOut(&wire.TxOut{ Value: int64(htlcAmt), - PkScript: pkScript, + PkScript: script.PkScript, }) return timeoutTx, nil