diff --git a/lnwallet/channel.go b/lnwallet/channel.go index ab8410313..1c2b10d51 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -937,121 +937,6 @@ func (lc *LightningChannel) diskCommitToMemCommit(isLocal bool, return commit, nil } -// CommitmentKeyRing holds all derived keys needed to construct commitment and -// HTLC transactions. The keys are derived differently depending whether the -// commitment transaction is ours or the remote peer's. Private keys associated -// with each key may belong to the commitment owner or the "other party" which -// is referred to in the field comments, regardless of which is local and which -// is remote. -type CommitmentKeyRing struct { - // commitPoint is the "per commitment point" used to derive the tweak - // for each base point. - CommitPoint *btcec.PublicKey - - // LocalCommitKeyTweak is the tweak used to derive the local public key - // from the local payment base point or the local private key from the - // base point secret. This may be included in a SignDescriptor to - // generate signatures for the local payment key. - LocalCommitKeyTweak []byte - - // TODO(roasbeef): need delay tweak as well? - - // LocalHtlcKeyTweak is the teak used to derive the local HTLC key from - // the local HTLC base point. This value is needed in order to - // derive the final key used within the HTLC scripts in the commitment - // transaction. - LocalHtlcKeyTweak []byte - - // LocalHtlcKey is the key that will be used in the "to self" clause of - // any HTLC scripts within the commitment transaction for this key ring - // set. - LocalHtlcKey *btcec.PublicKey - - // RemoteHtlcKey is the key that will be used in clauses within the - // HTLC script that send money to the remote party. - RemoteHtlcKey *btcec.PublicKey - - // DelayKey is the commitment transaction owner's key which is included - // in HTLC success and timeout transaction scripts. - DelayKey *btcec.PublicKey - - // NoDelayKey is the other party's payment key in the commitment tx. - // This is the key used to generate the unencumbered output within the - // commitment transaction. - NoDelayKey *btcec.PublicKey - - // RevocationKey is the key that can be used by the other party to - // redeem outputs from a revoked commitment transaction if it were to - // be published. - RevocationKey *btcec.PublicKey -} - -// DeriveCommitmentKey generates a new commitment key set using the base points -// and commitment point. The keys are derived differently depending whether the -// commitment transaction is ours or the remote peer's. -func DeriveCommitmentKeys(commitPoint *btcec.PublicKey, - isOurCommit, tweaklessCommit bool, - localChanCfg, remoteChanCfg *channeldb.ChannelConfig) *CommitmentKeyRing { - - // First, we'll derive all the keys that don't depend on the context of - // whose commitment transaction this is. - keyRing := &CommitmentKeyRing{ - CommitPoint: commitPoint, - - LocalCommitKeyTweak: input.SingleTweakBytes( - commitPoint, localChanCfg.PaymentBasePoint.PubKey, - ), - LocalHtlcKeyTweak: input.SingleTweakBytes( - commitPoint, localChanCfg.HtlcBasePoint.PubKey, - ), - LocalHtlcKey: input.TweakPubKey( - localChanCfg.HtlcBasePoint.PubKey, commitPoint, - ), - RemoteHtlcKey: input.TweakPubKey( - remoteChanCfg.HtlcBasePoint.PubKey, commitPoint, - ), - } - - // We'll now compute the delay, no delay, and revocation key based on - // the current commitment point. All keys are tweaked each state in - // order to ensure the keys from each state are unlinkable. To create - // the revocation key, we take the opposite party's revocation base - // point and combine that with the current commitment point. - var ( - delayBasePoint *btcec.PublicKey - noDelayBasePoint *btcec.PublicKey - revocationBasePoint *btcec.PublicKey - ) - if isOurCommit { - delayBasePoint = localChanCfg.DelayBasePoint.PubKey - noDelayBasePoint = remoteChanCfg.PaymentBasePoint.PubKey - revocationBasePoint = remoteChanCfg.RevocationBasePoint.PubKey - } else { - delayBasePoint = remoteChanCfg.DelayBasePoint.PubKey - noDelayBasePoint = localChanCfg.PaymentBasePoint.PubKey - revocationBasePoint = localChanCfg.RevocationBasePoint.PubKey - } - - // With the base points assigned, we can now derive the actual keys - // using the base point, and the current commitment tweak. - keyRing.DelayKey = input.TweakPubKey(delayBasePoint, commitPoint) - keyRing.RevocationKey = input.DeriveRevocationPubkey( - revocationBasePoint, commitPoint, - ) - - // If this commitment should omit the tweak for the remote point, then - // we'll use that directly, and ignore the commitPoint tweak. - if tweaklessCommit { - keyRing.NoDelayKey = noDelayBasePoint - } else { - keyRing.NoDelayKey = input.TweakPubKey( - noDelayBasePoint, commitPoint, - ) - } - - return keyRing -} - // commitmentChain represents a chain of unrevoked commitments. The tail of the // chain is the latest fully signed, yet unrevoked commitment. Two chains are // tracked, one for the local node, and another for the remote node. New @@ -1473,24 +1358,6 @@ func (lc *LightningChannel) createSignDesc() error { return nil } -// createStateHintObfuscator derives and assigns the state hint obfuscator for -// the channel, which is used to encode the commitment height in the sequence -// number of commitment transaction inputs. -func (lc *LightningChannel) createStateHintObfuscator() { - state := lc.channelState - if state.IsInitiator { - lc.stateHintObfuscator = DeriveStateHintObfuscator( - state.LocalChanCfg.PaymentBasePoint.PubKey, - state.RemoteChanCfg.PaymentBasePoint.PubKey, - ) - } else { - lc.stateHintObfuscator = DeriveStateHintObfuscator( - state.RemoteChanCfg.PaymentBasePoint.PubKey, - state.LocalChanCfg.PaymentBasePoint.PubKey, - ) - } -} - // ResetState resets the state of the channel back to the default state. This // ensures that any active goroutines which need to act based on on-chain // events do so properly. @@ -2370,164 +2237,6 @@ func (lc *LightningChannel) fundingTxIn() wire.TxIn { return *wire.NewTxIn(&lc.channelState.FundingOutpoint, nil, nil) } -// createCommitmentTx generates the unsigned commitment transaction for a -// commitment view and assigns to txn field. -func (lc *LightningChannel) createCommitmentTx(c *commitment, - filteredHTLCView *htlcView, keyRing *CommitmentKeyRing) error { - - ourBalance := c.ourBalance - theirBalance := c.theirBalance - - numHTLCs := int64(0) - for _, htlc := range filteredHTLCView.ourUpdates { - if htlcIsDust(false, c.isOurs, c.feePerKw, - htlc.Amount.ToSatoshis(), c.dustLimit) { - - continue - } - - numHTLCs++ - } - for _, htlc := range filteredHTLCView.theirUpdates { - if htlcIsDust(true, c.isOurs, c.feePerKw, - htlc.Amount.ToSatoshis(), c.dustLimit) { - - continue - } - - numHTLCs++ - } - - // Next, we'll calculate the fee for the commitment transaction based - // on its total weight. Once we have the total weight, we'll multiply - // by the current fee-per-kw, then divide by 1000 to get the proper - // fee. - totalCommitWeight := input.CommitWeight + (input.HtlcWeight * numHTLCs) - - // With the weight known, we can now calculate the commitment fee, - // ensuring that we account for any dust outputs trimmed above. - commitFee := c.feePerKw.FeeForWeight(totalCommitWeight) - commitFeeMSat := lnwire.NewMSatFromSatoshis(commitFee) - - // Currently, within the protocol, the initiator always pays the fees. - // So we'll subtract the fee amount from the balance of the current - // initiator. If the initiator is unable to pay the fee fully, then - // their entire output is consumed. - switch { - case lc.channelState.IsInitiator && commitFee > ourBalance.ToSatoshis(): - ourBalance = 0 - - case lc.channelState.IsInitiator: - ourBalance -= commitFeeMSat - - case !lc.channelState.IsInitiator && commitFee > theirBalance.ToSatoshis(): - theirBalance = 0 - - case !lc.channelState.IsInitiator: - theirBalance -= commitFeeMSat - } - - var ( - commitTx *wire.MsgTx - err error - ) - - // Depending on whether the transaction is ours or not, we call - // CreateCommitTx with parameters mathcing the perspective, to generate - // a new commitment transaction with all the latest unsettled/un-timed - // out HTLCs. - if c.isOurs { - commitTx, err = CreateCommitTx( - lc.fundingTxIn(), keyRing, &lc.channelState.LocalChanCfg, - &lc.channelState.RemoteChanCfg, ourBalance.ToSatoshis(), - theirBalance.ToSatoshis(), - ) - } else { - commitTx, err = CreateCommitTx( - lc.fundingTxIn(), keyRing, &lc.channelState.RemoteChanCfg, - &lc.channelState.LocalChanCfg, theirBalance.ToSatoshis(), - ourBalance.ToSatoshis(), - ) - } - if err != nil { - return err - } - - // We'll now add all the HTLC outputs to the commitment transaction. - // Each output includes an off-chain 2-of-2 covenant clause, so we'll - // need the objective local/remote keys for this particular commitment - // as well. For any non-dust HTLCs that are manifested on the commitment - // transaction, we'll also record its CLTV which is required to sort the - // commitment transaction below. The slice is initially sized to the - // number of existing outputs, since any outputs already added are - // commitment outputs and should correspond to zero values for the - // purposes of sorting. - cltvs := make([]uint32, len(commitTx.TxOut)) - for _, htlc := range filteredHTLCView.ourUpdates { - if htlcIsDust(false, c.isOurs, c.feePerKw, - htlc.Amount.ToSatoshis(), c.dustLimit) { - continue - } - - err := addHTLC(commitTx, c.isOurs, false, htlc, keyRing) - if err != nil { - return err - } - cltvs = append(cltvs, htlc.Timeout) - } - for _, htlc := range filteredHTLCView.theirUpdates { - if htlcIsDust(true, c.isOurs, c.feePerKw, - htlc.Amount.ToSatoshis(), c.dustLimit) { - continue - } - - err := addHTLC(commitTx, c.isOurs, true, htlc, keyRing) - if err != nil { - return err - } - cltvs = append(cltvs, htlc.Timeout) - } - - // Set the state hint of the commitment transaction to facilitate - // quickly recovering the necessary penalty state in the case of an - // uncooperative broadcast. - err = SetStateNumHint(commitTx, c.height, lc.stateHintObfuscator) - if err != nil { - return err - } - - // Sort the transactions according to the agreed upon canonical - // ordering. This lets us skip sending the entire transaction over, - // instead we'll just send signatures. - InPlaceCommitSort(commitTx, cltvs) - - // Next, we'll ensure that we don't accidentally create a commitment - // transaction which would be invalid by consensus. - uTx := btcutil.NewTx(commitTx) - if err := blockchain.CheckTransactionSanity(uTx); err != nil { - return err - } - - // Finally, we'll assert that were not attempting to draw more out of - // the channel that was originally placed within it. - var totalOut btcutil.Amount - for _, txOut := range commitTx.TxOut { - totalOut += btcutil.Amount(txOut.Value) - } - if totalOut > lc.channelState.Capacity { - return fmt.Errorf("height=%v, for ChannelPoint(%v) attempts "+ - "to consume %v while channel capacity is %v", - c.height, lc.channelState.FundingOutpoint, - totalOut, lc.channelState.Capacity) - } - - c.txn = commitTx - c.fee = commitFee - c.ourBalance = ourBalance - c.theirBalance = theirBalance - return nil -} - // evaluateHTLCView processes all update entries in both HTLC update logs, // producing a final view which is the result of properly applying all adds, // settles, timeouts and fee updates found in both logs. The resulting view @@ -4933,101 +4642,6 @@ func (lc *LightningChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress return lc.channelState.RemoteShutdownScript } -// 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. -func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte, - keyRing *CommitmentKeyRing) ([]byte, []byte, error) { - - var ( - witnessScript []byte - err error - ) - - // Generate the proper redeem 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. - switch { - // The HTLC is paying to us, and being applied to our commitment - // transaction. So we need to use the receiver's version of HTLC the - // script. - case isIncoming && ourCommit: - witnessScript, err = input.ReceiverHTLCScript(timeout, - keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, - keyRing.RevocationKey, rHash[:]) - - // 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: - witnessScript, err = input.SenderHTLCScript(keyRing.RemoteHtlcKey, - keyRing.LocalHtlcKey, keyRing.RevocationKey, rHash[:]) - - // 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: - witnessScript, err = input.SenderHTLCScript(keyRing.LocalHtlcKey, - keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:]) - - // 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: - witnessScript, err = input.ReceiverHTLCScript(timeout, keyRing.LocalHtlcKey, - keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:]) - } - if err != nil { - return nil, nil, err - } - - // Now that we have the redeem scripts, create the P2WSH public key - // script for the output itself. - htlcP2WSH, err := input.WitnessScriptHash(witnessScript) - if err != nil { - return nil, nil, err - } - - return htlcP2WSH, witnessScript, nil -} - -// addHTLC adds a new HTLC to the passed commitment transaction. One of four -// full scripts will be generated for the HTLC output depending on if the HTLC -// is incoming and if it's being applied to our commitment transaction or that -// of the remote node's. Additionally, in order to be able to efficiently -// locate the added HTLC on the commitment transaction from the -// PaymentDescriptor that generated it, the generated script is stored within -// the descriptor itself. -func addHTLC(commitTx *wire.MsgTx, ourCommit bool, - isIncoming bool, paymentDesc *PaymentDescriptor, - keyRing *CommitmentKeyRing) error { - - timeout := paymentDesc.Timeout - rHash := paymentDesc.RHash - - p2wsh, witnessScript, err := genHtlcScript(isIncoming, ourCommit, - timeout, rHash, keyRing) - if err != nil { - return err - } - - // Add the new HTLC outputs to the respective commitment transactions. - amountPending := int64(paymentDesc.Amount.ToSatoshis()) - commitTx.AddTxOut(wire.NewTxOut(amountPending, p2wsh)) - - // Store the pkScript of this particular PaymentDescriptor so we can - // quickly locate it within the commitment transaction later. - if ourCommit { - paymentDesc.ourPkScript = p2wsh - paymentDesc.ourWitnessScript = witnessScript - } else { - paymentDesc.theirPkScript = p2wsh - paymentDesc.theirWitnessScript = witnessScript - } - - return nil -} - // getSignedCommitTx function take the latest commitment transaction and // populate it with witness data. func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { @@ -6219,67 +5833,6 @@ func (lc *LightningChannel) generateRevocation(height uint64) (*lnwire.RevokeAnd return revocationMsg, nil } -// CreateCommitTx creates a commitment transaction, spending from specified -// funding output. The commitment transaction contains two outputs: one local -// output paying to the "owner" of the commitment transaction which can be -// spent after a relative block delay or revocation event, and a remote output -// paying the counterparty within the channel, which can be spent immediately -// or after a delay depending on the commitment type.. -func CreateCommitTx(fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, - localChanCfg, remoteChanCfg *channeldb.ChannelConfig, - amountToLocal, amountToRemote btcutil.Amount) (*wire.MsgTx, error) { - - // First, we create the script for the delayed "pay-to-self" output. - // This output has 2 main redemption clauses: either we can redeem the - // 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.DelayKey, - keyRing.RevocationKey, - ) - if err != nil { - return nil, err - } - toLocalScriptHash, err := input.WitnessScriptHash( - toLocalRedeemScript, - ) - if err != nil { - return nil, err - } - - // Next, we create the script paying to the remote. This is just a - // regular P2WPKH output, without any added CSV delay. - toRemoteWitnessKeyHash, err := input.CommitScriptUnencumbered( - keyRing.NoDelayKey, - ) - if err != nil { - return nil, err - } - - // Now that both output scripts have been created, we can finally create - // the transaction itself. We use a transaction version of 2 since CSV - // will fail unless the tx version is >= 2. - commitTx := wire.NewMsgTx(2) - commitTx.AddTxIn(&fundingOutput) - - // Avoid creating dust outputs within the commitment transaction. - if amountToLocal >= localChanCfg.DustLimit { - commitTx.AddTxOut(&wire.TxOut{ - PkScript: toLocalScriptHash, - Value: int64(amountToLocal), - }) - } - if amountToRemote >= localChanCfg.DustLimit { - commitTx.AddTxOut(&wire.TxOut{ - PkScript: toRemoteWitnessKeyHash, - Value: int64(amountToRemote), - }) - } - - return commitTx, nil -} - // CreateCooperativeCloseTx creates a transaction which if signed by both // parties, then broadcast cooperatively closes an active channel. The creation // of the closure transaction is modified by a boolean indicating if the party diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go new file mode 100644 index 000000000..bc8bd1615 --- /dev/null +++ b/lnwallet/commitment.go @@ -0,0 +1,460 @@ +package lnwallet + +import ( + "fmt" + + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwire" +) + +// CommitmentKeyRing holds all derived keys needed to construct commitment and +// HTLC transactions. The keys are derived differently depending whether the +// commitment transaction is ours or the remote peer's. Private keys associated +// with each key may belong to the commitment owner or the "other party" which +// is referred to in the field comments, regardless of which is local and which +// is remote. +type CommitmentKeyRing struct { + // commitPoint is the "per commitment point" used to derive the tweak + // for each base point. + CommitPoint *btcec.PublicKey + + // LocalCommitKeyTweak is the tweak used to derive the local public key + // from the local payment base point or the local private key from the + // base point secret. This may be included in a SignDescriptor to + // generate signatures for the local payment key. + LocalCommitKeyTweak []byte + + // TODO(roasbeef): need delay tweak as well? + + // LocalHtlcKeyTweak is the teak used to derive the local HTLC key from + // the local HTLC base point. This value is needed in order to + // derive the final key used within the HTLC scripts in the commitment + // transaction. + LocalHtlcKeyTweak []byte + + // LocalHtlcKey is the key that will be used in the "to self" clause of + // any HTLC scripts within the commitment transaction for this key ring + // set. + LocalHtlcKey *btcec.PublicKey + + // RemoteHtlcKey is the key that will be used in clauses within the + // HTLC script that send money to the remote party. + RemoteHtlcKey *btcec.PublicKey + + // DelayKey is the commitment transaction owner's key which is included + // in HTLC success and timeout transaction scripts. + DelayKey *btcec.PublicKey + + // NoDelayKey is the other party's payment key in the commitment tx. + // This is the key used to generate the unencumbered output within the + // commitment transaction. + NoDelayKey *btcec.PublicKey + + // RevocationKey is the key that can be used by the other party to + // redeem outputs from a revoked commitment transaction if it were to + // be published. + RevocationKey *btcec.PublicKey +} + +// DeriveCommitmentKey generates a new commitment key set using the base points +// and commitment point. The keys are derived differently depending whether the +// commitment transaction is ours or the remote peer's. +func DeriveCommitmentKeys(commitPoint *btcec.PublicKey, + isOurCommit, tweaklessCommit bool, + localChanCfg, remoteChanCfg *channeldb.ChannelConfig) *CommitmentKeyRing { + + // First, we'll derive all the keys that don't depend on the context of + // whose commitment transaction this is. + keyRing := &CommitmentKeyRing{ + CommitPoint: commitPoint, + + LocalCommitKeyTweak: input.SingleTweakBytes( + commitPoint, localChanCfg.PaymentBasePoint.PubKey, + ), + LocalHtlcKeyTweak: input.SingleTweakBytes( + commitPoint, localChanCfg.HtlcBasePoint.PubKey, + ), + LocalHtlcKey: input.TweakPubKey( + localChanCfg.HtlcBasePoint.PubKey, commitPoint, + ), + RemoteHtlcKey: input.TweakPubKey( + remoteChanCfg.HtlcBasePoint.PubKey, commitPoint, + ), + } + + // We'll now compute the delay, no delay, and revocation key based on + // the current commitment point. All keys are tweaked each state in + // order to ensure the keys from each state are unlinkable. To create + // the revocation key, we take the opposite party's revocation base + // point and combine that with the current commitment point. + var ( + delayBasePoint *btcec.PublicKey + noDelayBasePoint *btcec.PublicKey + revocationBasePoint *btcec.PublicKey + ) + if isOurCommit { + delayBasePoint = localChanCfg.DelayBasePoint.PubKey + noDelayBasePoint = remoteChanCfg.PaymentBasePoint.PubKey + revocationBasePoint = remoteChanCfg.RevocationBasePoint.PubKey + } else { + delayBasePoint = remoteChanCfg.DelayBasePoint.PubKey + noDelayBasePoint = localChanCfg.PaymentBasePoint.PubKey + revocationBasePoint = localChanCfg.RevocationBasePoint.PubKey + } + + // With the base points assigned, we can now derive the actual keys + // using the base point, and the current commitment tweak. + keyRing.DelayKey = input.TweakPubKey(delayBasePoint, commitPoint) + keyRing.RevocationKey = input.DeriveRevocationPubkey( + revocationBasePoint, commitPoint, + ) + + // If this commitment should omit the tweak for the remote point, then + // we'll use that directly, and ignore the commitPoint tweak. + if tweaklessCommit { + keyRing.NoDelayKey = noDelayBasePoint + } else { + keyRing.NoDelayKey = input.TweakPubKey( + noDelayBasePoint, commitPoint, + ) + } + + return keyRing +} + +// createStateHintObfuscator derives and assigns the state hint obfuscator for +// the channel, which is used to encode the commitment height in the sequence +// number of commitment transaction inputs. +func (lc *LightningChannel) createStateHintObfuscator() { + state := lc.channelState + if state.IsInitiator { + lc.stateHintObfuscator = DeriveStateHintObfuscator( + state.LocalChanCfg.PaymentBasePoint.PubKey, + state.RemoteChanCfg.PaymentBasePoint.PubKey, + ) + } else { + lc.stateHintObfuscator = DeriveStateHintObfuscator( + state.RemoteChanCfg.PaymentBasePoint.PubKey, + state.LocalChanCfg.PaymentBasePoint.PubKey, + ) + } +} + +// createCommitmentTx generates the unsigned commitment transaction for a +// commitment view and assigns to txn field. +func (lc *LightningChannel) createCommitmentTx(c *commitment, + filteredHTLCView *htlcView, keyRing *CommitmentKeyRing) error { + + ourBalance := c.ourBalance + theirBalance := c.theirBalance + + numHTLCs := int64(0) + for _, htlc := range filteredHTLCView.ourUpdates { + if htlcIsDust(false, c.isOurs, c.feePerKw, + htlc.Amount.ToSatoshis(), c.dustLimit) { + + continue + } + + numHTLCs++ + } + for _, htlc := range filteredHTLCView.theirUpdates { + if htlcIsDust(true, c.isOurs, c.feePerKw, + htlc.Amount.ToSatoshis(), c.dustLimit) { + + continue + } + + numHTLCs++ + } + + // Next, we'll calculate the fee for the commitment transaction based + // on its total weight. Once we have the total weight, we'll multiply + // by the current fee-per-kw, then divide by 1000 to get the proper + // fee. + totalCommitWeight := input.CommitWeight + (input.HtlcWeight * numHTLCs) + + // With the weight known, we can now calculate the commitment fee, + // ensuring that we account for any dust outputs trimmed above. + commitFee := c.feePerKw.FeeForWeight(totalCommitWeight) + commitFeeMSat := lnwire.NewMSatFromSatoshis(commitFee) + + // Currently, within the protocol, the initiator always pays the fees. + // So we'll subtract the fee amount from the balance of the current + // initiator. If the initiator is unable to pay the fee fully, then + // their entire output is consumed. + switch { + case lc.channelState.IsInitiator && commitFee > ourBalance.ToSatoshis(): + ourBalance = 0 + + case lc.channelState.IsInitiator: + ourBalance -= commitFeeMSat + + case !lc.channelState.IsInitiator && commitFee > theirBalance.ToSatoshis(): + theirBalance = 0 + + case !lc.channelState.IsInitiator: + theirBalance -= commitFeeMSat + } + + var ( + commitTx *wire.MsgTx + err error + ) + + // Depending on whether the transaction is ours or not, we call + // CreateCommitTx with parameters mathcing the perspective, to generate + // a new commitment transaction with all the latest unsettled/un-timed + // out HTLCs. + if c.isOurs { + commitTx, err = CreateCommitTx( + lc.fundingTxIn(), keyRing, &lc.channelState.LocalChanCfg, + &lc.channelState.RemoteChanCfg, ourBalance.ToSatoshis(), + theirBalance.ToSatoshis(), + ) + } else { + commitTx, err = CreateCommitTx( + lc.fundingTxIn(), keyRing, &lc.channelState.RemoteChanCfg, + &lc.channelState.LocalChanCfg, theirBalance.ToSatoshis(), + ourBalance.ToSatoshis(), + ) + } + if err != nil { + return err + } + + // We'll now add all the HTLC outputs to the commitment transaction. + // Each output includes an off-chain 2-of-2 covenant clause, so we'll + // need the objective local/remote keys for this particular commitment + // as well. For any non-dust HTLCs that are manifested on the commitment + // transaction, we'll also record its CLTV which is required to sort the + // commitment transaction below. The slice is initially sized to the + // number of existing outputs, since any outputs already added are + // commitment outputs and should correspond to zero values for the + // purposes of sorting. + cltvs := make([]uint32, len(commitTx.TxOut)) + for _, htlc := range filteredHTLCView.ourUpdates { + if htlcIsDust(false, c.isOurs, c.feePerKw, + htlc.Amount.ToSatoshis(), c.dustLimit) { + continue + } + + err := addHTLC(commitTx, c.isOurs, false, htlc, keyRing) + if err != nil { + return err + } + cltvs = append(cltvs, htlc.Timeout) + } + for _, htlc := range filteredHTLCView.theirUpdates { + if htlcIsDust(true, c.isOurs, c.feePerKw, + htlc.Amount.ToSatoshis(), c.dustLimit) { + continue + } + + err := addHTLC(commitTx, c.isOurs, true, htlc, keyRing) + if err != nil { + return err + } + cltvs = append(cltvs, htlc.Timeout) + } + + // Set the state hint of the commitment transaction to facilitate + // quickly recovering the necessary penalty state in the case of an + // uncooperative broadcast. + err = SetStateNumHint(commitTx, c.height, lc.stateHintObfuscator) + if err != nil { + return err + } + + // Sort the transactions according to the agreed upon canonical + // ordering. This lets us skip sending the entire transaction over, + // instead we'll just send signatures. + InPlaceCommitSort(commitTx, cltvs) + + // Next, we'll ensure that we don't accidentally create a commitment + // transaction which would be invalid by consensus. + uTx := btcutil.NewTx(commitTx) + if err := blockchain.CheckTransactionSanity(uTx); err != nil { + return err + } + + // Finally, we'll assert that were not attempting to draw more out of + // the channel that was originally placed within it. + var totalOut btcutil.Amount + for _, txOut := range commitTx.TxOut { + totalOut += btcutil.Amount(txOut.Value) + } + if totalOut > lc.channelState.Capacity { + return fmt.Errorf("height=%v, for ChannelPoint(%v) attempts "+ + "to consume %v while channel capacity is %v", + c.height, lc.channelState.FundingOutpoint, + totalOut, lc.channelState.Capacity) + } + + c.txn = commitTx + c.fee = commitFee + c.ourBalance = ourBalance + c.theirBalance = theirBalance + return nil +} + +// CreateCommitTx creates a commitment transaction, spending from specified +// funding output. The commitment transaction contains two outputs: one local +// output paying to the "owner" of the commitment transaction which can be +// spent after a relative block delay or revocation event, and a remote output +// paying the counterparty within the channel, which can be spent immediately +// or after a delay depending on the commitment type.. +func CreateCommitTx(fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, + localChanCfg, remoteChanCfg *channeldb.ChannelConfig, + amountToLocal, amountToRemote btcutil.Amount) (*wire.MsgTx, error) { + + // First, we create the script for the delayed "pay-to-self" output. + // This output has 2 main redemption clauses: either we can redeem the + // 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.DelayKey, + keyRing.RevocationKey, + ) + if err != nil { + return nil, err + } + toLocalScriptHash, err := input.WitnessScriptHash( + toLocalRedeemScript, + ) + if err != nil { + return nil, err + } + + // Next, we create the script paying to the remote. This is just a + // regular P2WPKH output, without any added CSV delay. + toRemoteWitnessKeyHash, err := input.CommitScriptUnencumbered( + keyRing.NoDelayKey, + ) + if err != nil { + return nil, err + } + + // Now that both output scripts have been created, we can finally create + // the transaction itself. We use a transaction version of 2 since CSV + // will fail unless the tx version is >= 2. + commitTx := wire.NewMsgTx(2) + commitTx.AddTxIn(&fundingOutput) + + // Avoid creating dust outputs within the commitment transaction. + if amountToLocal >= localChanCfg.DustLimit { + commitTx.AddTxOut(&wire.TxOut{ + PkScript: toLocalScriptHash, + Value: int64(amountToLocal), + }) + } + if amountToRemote >= localChanCfg.DustLimit { + commitTx.AddTxOut(&wire.TxOut{ + PkScript: toRemoteWitnessKeyHash, + Value: int64(amountToRemote), + }) + } + + return commitTx, 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. +func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte, + keyRing *CommitmentKeyRing) ([]byte, []byte, error) { + + var ( + witnessScript []byte + err error + ) + + // Generate the proper redeem 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. + switch { + // The HTLC is paying to us, and being applied to our commitment + // transaction. So we need to use the receiver's version of HTLC the + // script. + case isIncoming && ourCommit: + witnessScript, err = input.ReceiverHTLCScript(timeout, + keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, + keyRing.RevocationKey, rHash[:]) + + // 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: + witnessScript, err = input.SenderHTLCScript(keyRing.RemoteHtlcKey, + keyRing.LocalHtlcKey, keyRing.RevocationKey, rHash[:]) + + // 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: + witnessScript, err = input.SenderHTLCScript(keyRing.LocalHtlcKey, + keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:]) + + // 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: + witnessScript, err = input.ReceiverHTLCScript(timeout, keyRing.LocalHtlcKey, + keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:]) + } + if err != nil { + return nil, nil, err + } + + // Now that we have the redeem scripts, create the P2WSH public key + // script for the output itself. + htlcP2WSH, err := input.WitnessScriptHash(witnessScript) + if err != nil { + return nil, nil, err + } + + return htlcP2WSH, witnessScript, nil +} + +// addHTLC adds a new HTLC to the passed commitment transaction. One of four +// full scripts will be generated for the HTLC output depending on if the HTLC +// is incoming and if it's being applied to our commitment transaction or that +// of the remote node's. Additionally, in order to be able to efficiently +// locate the added HTLC on the commitment transaction from the +// PaymentDescriptor that generated it, the generated script is stored within +// the descriptor itself. +func addHTLC(commitTx *wire.MsgTx, ourCommit bool, + isIncoming bool, paymentDesc *PaymentDescriptor, + keyRing *CommitmentKeyRing) error { + + timeout := paymentDesc.Timeout + rHash := paymentDesc.RHash + + p2wsh, witnessScript, err := genHtlcScript(isIncoming, ourCommit, + timeout, rHash, keyRing) + if err != nil { + return err + } + + // Add the new HTLC outputs to the respective commitment transactions. + amountPending := int64(paymentDesc.Amount.ToSatoshis()) + commitTx.AddTxOut(wire.NewTxOut(amountPending, p2wsh)) + + // Store the pkScript of this particular PaymentDescriptor so we can + // quickly locate it within the commitment transaction later. + if ourCommit { + paymentDesc.ourPkScript = p2wsh + paymentDesc.ourWitnessScript = witnessScript + } else { + paymentDesc.theirPkScript = p2wsh + paymentDesc.theirWitnessScript = witnessScript + } + + return nil +}