package lnwallet import ( "bytes" "fmt" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" ) // anchorSize is the constant anchor output size. const anchorSize = btcutil.Amount(330) // DefaultAnchorsCommitMaxFeeRateSatPerVByte is the default max fee rate in // sat/vbyte the initiator will use for anchor channels. This should be enough // to ensure propagation before anchoring down the commitment transaction. const DefaultAnchorsCommitMaxFeeRateSatPerVByte = 10 // 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. // // NOTE: This will always refer to "our" local key, regardless of // whether this is our commit or not. LocalCommitKeyTweak []byte // TODO(roasbeef): need delay tweak as well? // LocalHtlcKeyTweak is the tweak 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. // // NOTE: This will always refer to "our" local HTLC key, regardless of // whether this is our commit or not. LocalHtlcKeyTweak []byte // LocalHtlcKey is the key that will be used in any clause paying to // our node of any HTLC scripts within the commitment transaction for // this key ring set. // // NOTE: This will always refer to "our" local HTLC key, regardless of // whether this is our commit or not. LocalHtlcKey *btcec.PublicKey // RemoteHtlcKey is the key that will be used in clauses within the // HTLC script that send money to the remote party. // // NOTE: This will always refer to "their" remote HTLC key, regardless // of whether this is our commit or not. RemoteHtlcKey *btcec.PublicKey // ToLocalKey is the commitment transaction owner's key which is // included in HTLC success and timeout transaction scripts. This is // the public key used for the to_local output of the commitment // transaction. // // NOTE: Who's key this is depends on the current perspective. If this // is our commitment this will be our key. ToLocalKey *btcec.PublicKey // ToRemoteKey is the non-owner's payment key in the commitment tx. // This is the key used to generate the to_remote output within the // commitment transaction. // // NOTE: Who's key this is depends on the current perspective. If this // is our commitment this will be their key. ToRemoteKey *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. // // NOTE: Who can sign for this key depends on the current perspective. // If this is our commitment, it means the remote node can sign for // this key in case of a breach. RevocationKey *btcec.PublicKey } // DeriveCommitmentKeys generates a new commitment key set using the base points // and commitment point. The keys are derived differently depending on the type // of channel, and whether the commitment transaction is ours or the remote // peer's. func DeriveCommitmentKeys(commitPoint *btcec.PublicKey, whoseCommit lntypes.ChannelParty, chanType channeldb.ChannelType, localChanCfg, remoteChanCfg *channeldb.ChannelConfig) *CommitmentKeyRing { tweaklessCommit := chanType.IsTweakless() // Depending on if this is our commit or not, we'll choose the correct // base point. localBasePoint := localChanCfg.PaymentBasePoint if whoseCommit.IsLocal() { localBasePoint = localChanCfg.DelayBasePoint } // 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, localBasePoint.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 to_local, to_remote, 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 ( toLocalBasePoint *btcec.PublicKey toRemoteBasePoint *btcec.PublicKey revocationBasePoint *btcec.PublicKey ) if whoseCommit.IsLocal() { toLocalBasePoint = localChanCfg.DelayBasePoint.PubKey toRemoteBasePoint = remoteChanCfg.PaymentBasePoint.PubKey revocationBasePoint = remoteChanCfg.RevocationBasePoint.PubKey } else { toLocalBasePoint = remoteChanCfg.DelayBasePoint.PubKey toRemoteBasePoint = 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.ToLocalKey = input.TweakPubKey(toLocalBasePoint, 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.ToRemoteKey = toRemoteBasePoint // If this is not our commitment, the above ToRemoteKey will be // ours, and we blank out the local commitment tweak to // indicate that the key should not be tweaked when signing. if whoseCommit.IsRemote() { keyRing.LocalCommitKeyTweak = nil } } else { keyRing.ToRemoteKey = input.TweakPubKey( toRemoteBasePoint, commitPoint, ) } return keyRing } // 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 } // 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(_ input.ScriptPath, ) ([]byte, error) { return w.WitnessScript, nil } // CommitScriptToSelf constructs the public key script for the output on the // commitment transaction paying to the "owner" of said commitment transaction. // The `initiator` argument should correspond to the owner of the commitment // transaction which we are generating the to_local script for. 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(chanType channeldb.ChannelType, initiator bool, selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32, ) ( input.ScriptDescriptor, error) { switch { // For taproot scripts, we'll need to make a slightly modified script // where a NUMS key is used to force a script path reveal of either the // revocation or the CSV timeout. // // Our "redeem" script here is just the taproot witness program. case chanType.IsTaproot(): return input.NewLocalCommitScriptTree( csvDelay, selfKey, revokeKey, ) // 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(): toLocalRedeemScript, err := input.LeaseCommitScriptToSelf( selfKey, revokeKey, csvDelay, leaseExpiry, ) if err != nil { return nil, err } toLocalScriptHash, err := input.WitnessScriptHash( toLocalRedeemScript, ) if err != nil { return nil, err } return &WitnessScriptDesc{ OutputScript: toLocalScriptHash, WitnessScript: toLocalRedeemScript, }, nil default: 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 &WitnessScriptDesc{ OutputScript: toLocalScriptHash, WitnessScript: toLocalRedeemScript, }, nil } } // CommitScriptToRemote derives the appropriate to_remote script based on the // channel's commitment type. The `initiator` argument should correspond to the // owner of the commitment transaction which we are generating the to_remote // script for. 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, initiator bool, remoteKey *btcec.PublicKey, leaseExpiry uint32) (input.ScriptDescriptor, uint32, error) { switch { // If we are not the initiator of a leased channel, then the remote // party has an additional CLTV requirement in addition to the 1 block // CSV requirement. case chanType.HasLeaseExpiration() && !initiator: script, err := input.LeaseCommitScriptToRemoteConfirmed( remoteKey, leaseExpiry, ) if err != nil { return nil, 0, err } p2wsh, err := input.WitnessScriptHash(script) if err != nil { return nil, 0, err } return &WitnessScriptDesc{ OutputScript: p2wsh, WitnessScript: script, }, 1, nil // For taproot channels, we'll use a slightly different format, where // we use a NUMS key to force the remote party to take a script path, // with the sole tap leaf enforcing the 1 CSV delay. case chanType.IsTaproot(): toRemoteScriptTree, err := input.NewRemoteCommitScriptTree( remoteKey, ) if err != nil { return nil, 0, err } return toRemoteScriptTree, 1, nil // If this channel type has anchors, we derive the delayed to_remote // script. case chanType.HasAnchors(): script, err := input.CommitScriptToRemoteConfirmed(remoteKey) if err != nil { return nil, 0, err } p2wsh, err := input.WitnessScriptHash(script) if err != nil { return nil, 0, err } return &WitnessScriptDesc{ OutputScript: p2wsh, WitnessScript: script, }, 1, nil default: // Otherwise the to_remote will be a simple p2wkh. p2wkh, err := input.CommitScriptUnencumbered(remoteKey) if err != nil { return nil, 0, err } // Since this is a regular P2WKH, the WitnessScipt and PkScript // should both be set to the script hash. return &WitnessScriptDesc{ OutputScript: p2wkh, WitnessScript: p2wkh, }, 0, nil } } // HtlcSigHashType returns the sighash type to use for HTLC success and timeout // transactions given the channel type. func HtlcSigHashType(chanType channeldb.ChannelType) txscript.SigHashType { if chanType.HasAnchors() { return txscript.SigHashSingle | txscript.SigHashAnyOneCanPay } return txscript.SigHashAll } // HtlcSignDetails converts the passed parameters to a SignDetails valid for // this channel type. For non-anchor channels this will return nil. func HtlcSignDetails(chanType channeldb.ChannelType, signDesc input.SignDescriptor, sigHash txscript.SigHashType, peerSig input.Signature) *input.SignDetails { // Non-anchor channels don't need sign details, as the HTLC second // level cannot be altered. if !chanType.HasAnchors() { return nil } return &input.SignDetails{ SignDesc: signDesc, SigHashType: sigHash, PeerSig: peerSig, } } // HtlcSecondLevelInputSequence dictates the sequence number we must use on the // input to a second level HTLC transaction. func HtlcSecondLevelInputSequence(chanType channeldb.ChannelType) uint32 { if chanType.HasAnchors() { return 1 } return 0 } // sweepSigHash returns the sign descriptor to use when signing a sweep // transaction. For taproot channels, we'll use this to always sweep with // sighash default. func sweepSigHash(chanType channeldb.ChannelType) txscript.SigHashType { if chanType.IsTaproot() { return txscript.SigHashDefault } return txscript.SigHashAll } // 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. The `initiator` // argument should correspond to the owner of the commitment transaction which // we are generating the to_local script for. func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool, revocationKey, delayKey *btcec.PublicKey, csvDelay, leaseExpiry uint32) (input.ScriptDescriptor, error) { switch { // For taproot channels, the pkScript is a segwit v1 p2tr output. case chanType.IsTaproot(): return input.TaprootSecondLevelScriptTree( revocationKey, delayKey, csvDelay, ) // 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( revocationKey, delayKey, csvDelay, leaseExpiry, ) if err != nil { return nil, err } pkScript, err := input.WitnessScriptHash(witnessScript) if err != nil { return nil, err } return &WitnessScriptDesc{ OutputScript: pkScript, WitnessScript: witnessScript, }, nil default: 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 &WitnessScriptDesc{ OutputScript: pkScript, WitnessScript: witnessScript, }, nil } } // CommitWeight returns the base commitment weight before adding HTLCs. func CommitWeight(chanType channeldb.ChannelType) lntypes.WeightUnit { switch { case chanType.IsTaproot(): return input.TaprootCommitWeight // If this commitment has anchors, it will be slightly heavier. case chanType.HasAnchors(): return input.AnchorCommitWeight default: return input.CommitWeight } } // HtlcTimeoutFee returns the fee in satoshis required for an HTLC timeout // transaction based on the current fee rate. func HtlcTimeoutFee(chanType channeldb.ChannelType, feePerKw chainfee.SatPerKWeight) btcutil.Amount { switch { // For zero-fee HTLC channels, this will always be zero, regardless of // feerate. case chanType.ZeroHtlcTxFee() || chanType.IsTaproot(): return 0 case chanType.HasAnchors(): return feePerKw.FeeForWeight(input.HtlcTimeoutWeightConfirmed) default: return feePerKw.FeeForWeight(input.HtlcTimeoutWeight) } } // HtlcSuccessFee returns the fee in satoshis required for an HTLC success // transaction based on the current fee rate. func HtlcSuccessFee(chanType channeldb.ChannelType, feePerKw chainfee.SatPerKWeight) btcutil.Amount { switch { // For zero-fee HTLC channels, this will always be zero, regardless of // feerate. case chanType.ZeroHtlcTxFee() || chanType.IsTaproot(): return 0 case chanType.HasAnchors(): return feePerKw.FeeForWeight(input.HtlcSuccessWeightConfirmed) default: return feePerKw.FeeForWeight(input.HtlcSuccessWeight) } } // CommitScriptAnchors return the scripts to use for the local and remote // anchor. func CommitScriptAnchors(chanType channeldb.ChannelType, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, keyRing *CommitmentKeyRing) ( input.ScriptDescriptor, input.ScriptDescriptor, error) { var ( anchorScript func(key *btcec.PublicKey) ( input.ScriptDescriptor, error) keySelector func(*channeldb.ChannelConfig, bool) *btcec.PublicKey ) switch { // For taproot channels, the anchor is slightly different: the top // level key is now the (relative) local delay and remote public key, // since these are fully revealed once the commitment hits the chain. case chanType.IsTaproot(): anchorScript = func(key *btcec.PublicKey, ) (input.ScriptDescriptor, error) { return input.NewAnchorScriptTree( key, ) } keySelector = func(cfg *channeldb.ChannelConfig, local bool) *btcec.PublicKey { if local { return keyRing.ToLocalKey } return keyRing.ToRemoteKey } // For normal channels we'll use the multi-sig keys since those are // revealed when the channel closes default: // For normal channels, we'll create a p2wsh script based on // the target key. anchorScript = func(key *btcec.PublicKey, ) (input.ScriptDescriptor, error) { script, err := input.CommitScriptAnchor(key) if err != nil { return nil, err } scriptHash, err := input.WitnessScriptHash(script) if err != nil { return nil, err } return &WitnessScriptDesc{ OutputScript: scriptHash, WitnessScript: script, }, nil } // For the existing channels, we'll always select the multi-sig // key from the party's channel config. keySelector = func(cfg *channeldb.ChannelConfig, _ bool) *btcec.PublicKey { return cfg.MultiSigKey.PubKey } } // Get the script used for the anchor output spendable by the local // node. localAnchor, err := anchorScript(keySelector(localChanCfg, true)) if err != nil { return nil, nil, err } // And the anchor spendable by the remote node. remoteAnchor, err := anchorScript(keySelector(remoteChanCfg, false)) if err != nil { return nil, nil, err } return localAnchor, remoteAnchor, nil } // CommitmentBuilder is a type that wraps the type of channel we are dealing // with, and abstracts the various ways of constructing commitment // transactions. type CommitmentBuilder struct { // chanState is the underlying channels's state struct, used to // determine the type of channel we are dealing with, and relevant // parameters. chanState *channeldb.OpenChannel // obfuscator is a 48-bit state hint that's used to obfuscate the // current state number on the commitment transactions. obfuscator [StateHintSize]byte } // NewCommitmentBuilder creates a new CommitmentBuilder from chanState. func NewCommitmentBuilder(chanState *channeldb.OpenChannel) *CommitmentBuilder { // The anchor channel type MUST be tweakless. if chanState.ChanType.HasAnchors() && !chanState.ChanType.IsTweakless() { panic("invalid channel type combination") } return &CommitmentBuilder{ chanState: chanState, obfuscator: createStateHintObfuscator(chanState), } } // 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 createStateHintObfuscator(state *channeldb.OpenChannel) [StateHintSize]byte { if state.IsInitiator { return DeriveStateHintObfuscator( state.LocalChanCfg.PaymentBasePoint.PubKey, state.RemoteChanCfg.PaymentBasePoint.PubKey, ) } return DeriveStateHintObfuscator( state.RemoteChanCfg.PaymentBasePoint.PubKey, state.LocalChanCfg.PaymentBasePoint.PubKey, ) } // unsignedCommitmentTx is the final commitment created from evaluating an HTLC // view at a given height, along with some meta data. type unsignedCommitmentTx struct { // txn is the final, unsigned commitment transaction for this view. txn *wire.MsgTx // fee is the total fee of the commitment transaction. fee btcutil.Amount // ourBalance is our balance on this commitment *after* subtracting // commitment fees and anchor outputs. This can be different than the // balances before creating the commitment transaction as one party must // pay the commitment fee. ourBalance lnwire.MilliSatoshi // theirBalance is their balance of this commitment *after* subtracting // commitment fees and anchor outputs. This can be different than the // balances before creating the commitment transaction as one party must // pay the commitment fee. theirBalance lnwire.MilliSatoshi // cltvs is a sorted list of CLTV deltas for each HTLC on the commitment // transaction. Any non-htlc outputs will have a CLTV delay of zero. cltvs []uint32 } // createUnsignedCommitmentTx generates the unsigned commitment transaction for // a commitment view and returns it as part of the unsignedCommitmentTx. The // passed in balances should be balances *before* subtracting any commitment // fees, but after anchor outputs. func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, theirBalance lnwire.MilliSatoshi, whoseCommit lntypes.ChannelParty, feePerKw chainfee.SatPerKWeight, height uint64, filteredHTLCView *htlcView, keyRing *CommitmentKeyRing) (*unsignedCommitmentTx, error) { dustLimit := cb.chanState.LocalChanCfg.DustLimit if whoseCommit.IsRemote() { dustLimit = cb.chanState.RemoteChanCfg.DustLimit } numHTLCs := int64(0) for _, htlc := range filteredHTLCView.ourUpdates { if HtlcIsDust( cb.chanState.ChanType, false, whoseCommit, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, ) { continue } numHTLCs++ } for _, htlc := range filteredHTLCView.theirUpdates { if HtlcIsDust( cb.chanState.ChanType, true, whoseCommit, feePerKw, htlc.Amount.ToSatoshis(), 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 := CommitWeight(cb.chanState.ChanType) + lntypes.WeightUnit(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 := 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 cb.chanState.IsInitiator && commitFee > ourBalance.ToSatoshis(): ourBalance = 0 case cb.chanState.IsInitiator: ourBalance -= commitFeeMSat case !cb.chanState.IsInitiator && commitFee > theirBalance.ToSatoshis(): theirBalance = 0 case !cb.chanState.IsInitiator: theirBalance -= commitFeeMSat } var ( commitTx *wire.MsgTx err error ) // Depending on whether the transaction is ours or not, we call // CreateCommitTx with parameters matching the perspective, to generate // a new commitment transaction with all the latest unsettled/un-timed // out HTLCs. var leaseExpiry uint32 if cb.chanState.ChanType.HasLeaseExpiration() { leaseExpiry = cb.chanState.ThawHeight } if whoseCommit.IsLocal() { commitTx, err = CreateCommitTx( cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing, &cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg, ourBalance.ToSatoshis(), theirBalance.ToSatoshis(), numHTLCs, cb.chanState.IsInitiator, leaseExpiry, ) } else { commitTx, err = CreateCommitTx( cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing, &cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg, theirBalance.ToSatoshis(), ourBalance.ToSatoshis(), numHTLCs, !cb.chanState.IsInitiator, leaseExpiry, ) } if err != nil { return nil, 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( cb.chanState.ChanType, false, whoseCommit, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, ) { continue } err := addHTLC( commitTx, whoseCommit, false, htlc, keyRing, cb.chanState.ChanType, ) if err != nil { return nil, err } cltvs = append(cltvs, htlc.Timeout) // nolint:makezero } for _, htlc := range filteredHTLCView.theirUpdates { if HtlcIsDust( cb.chanState.ChanType, true, whoseCommit, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, ) { continue } err := addHTLC( commitTx, whoseCommit, true, htlc, keyRing, cb.chanState.ChanType, ) if err != nil { return nil, err } cltvs = append(cltvs, htlc.Timeout) // nolint:makezero } // 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, height, cb.obfuscator) if err != nil { return nil, 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 nil, 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+commitFee > cb.chanState.Capacity { return nil, fmt.Errorf("height=%v, for ChannelPoint(%v) "+ "attempts to consume %v while channel capacity is %v", height, cb.chanState.FundingOutpoint, totalOut+commitFee, cb.chanState.Capacity) } return &unsignedCommitmentTx{ txn: commitTx, fee: commitFee, ourBalance: ourBalance, theirBalance: theirBalance, cltvs: cltvs, }, 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. The `initiator` argument // should correspond to the owner of the commitment transaction we are creating. func CreateCommitTx(chanType channeldb.ChannelType, fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, amountToLocal, amountToRemote btcutil.Amount, numHTLCs int64, initiator bool, leaseExpiry uint32) (*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. toLocalScript, err := CommitScriptToSelf( chanType, initiator, keyRing.ToLocalKey, keyRing.RevocationKey, uint32(localChanCfg.CsvDelay), leaseExpiry, ) if err != nil { return nil, err } // Next, we create the script paying to the remote. toRemoteScript, _, err := CommitScriptToRemote( chanType, initiator, keyRing.ToRemoteKey, leaseExpiry, ) 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. localOutput := amountToLocal >= localChanCfg.DustLimit if localOutput { commitTx.AddTxOut(&wire.TxOut{ PkScript: toLocalScript.PkScript(), Value: int64(amountToLocal), }) } remoteOutput := amountToRemote >= localChanCfg.DustLimit if remoteOutput { commitTx.AddTxOut(&wire.TxOut{ PkScript: toRemoteScript.PkScript(), Value: int64(amountToRemote), }) } // If this channel type has anchors, we'll also add those. if chanType.HasAnchors() { localAnchor, remoteAnchor, err := CommitScriptAnchors( chanType, localChanCfg, remoteChanCfg, keyRing, ) if err != nil { return nil, err } // Add local anchor output only if we have a commitment output // or there are HTLCs. if localOutput || numHTLCs > 0 { commitTx.AddTxOut(&wire.TxOut{ PkScript: localAnchor.PkScript(), Value: int64(anchorSize), }) } // Add anchor output to remote only if they have a commitment // output or there are HTLCs. if remoteOutput || numHTLCs > 0 { commitTx.AddTxOut(&wire.TxOut{ PkScript: remoteAnchor.PkScript(), Value: int64(anchorSize), }) } } return commitTx, nil } // CoopCloseBalance returns the final balances that should be used to create // the cooperative close tx, given the channel type and transaction fee. func CoopCloseBalance(chanType channeldb.ChannelType, isInitiator bool, coopCloseFee btcutil.Amount, localCommit channeldb.ChannelCommitment) ( btcutil.Amount, btcutil.Amount, error) { // Get both parties' balances from the latest commitment. ourBalance := localCommit.LocalBalance.ToSatoshis() theirBalance := localCommit.RemoteBalance.ToSatoshis() // We'll make sure we account for the complete balance by adding the // current dangling commitment fee to the balance of the initiator. initiatorDelta := localCommit.CommitFee // Since the initiator's balance also is stored after subtracting the // anchor values, add that back in case this was an anchor commitment. if chanType.HasAnchors() { initiatorDelta += 2 * anchorSize } // The initiator will pay the full coop close fee, subtract that value // from their balance. initiatorDelta -= coopCloseFee if isInitiator { ourBalance += initiatorDelta } else { theirBalance += initiatorDelta } // During fee negotiation it should always be verified that the // initiator can pay the proposed fee, but we do a sanity check just to // be sure here. if ourBalance < 0 || theirBalance < 0 { return 0, 0, fmt.Errorf("initiator cannot afford proposed " + "coop close fee") } return ourBalance, theirBalance, nil } // genSegwitV0HtlcScript generates the HTLC scripts for a normal segwit v0 // channel. func genSegwitV0HtlcScript(chanType channeldb.ChannelType, isIncoming bool, whoseCommit lntypes.ChannelParty, timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing, ) (*WitnessScriptDesc, error) { var ( witnessScript []byte err error ) // Choose scripts based on channel type. confirmedHtlcSpends := false if chanType.HasAnchors() { confirmedHtlcSpends = true } // 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 the HTLC // script. case isIncoming && whoseCommit.IsLocal(): witnessScript, err = input.ReceiverHTLCScript( timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, keyRing.RevocationKey, rHash[:], confirmedHtlcSpends, ) // 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 && whoseCommit.IsRemote(): witnessScript, err = input.SenderHTLCScript( keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, keyRing.RevocationKey, rHash[:], confirmedHtlcSpends, ) // 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 && whoseCommit.IsLocal(): witnessScript, err = input.SenderHTLCScript( keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:], confirmedHtlcSpends, ) // 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 && whoseCommit.IsRemote(): witnessScript, err = input.ReceiverHTLCScript( timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:], confirmedHtlcSpends, ) } if err != nil { return 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, err } return &WitnessScriptDesc{ OutputScript: htlcP2WSH, WitnessScript: witnessScript, }, nil } // genTaprootHtlcScript generates the HTLC scripts for a taproot+musig2 // channel. func genTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing, ) (*input.HtlcScriptTree, error) { var ( htlcScriptTree *input.HtlcScriptTree 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 && whoseCommit.IsLocal(): htlcScriptTree, err = input.ReceiverHTLCScriptTaproot( timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, keyRing.RevocationKey, rHash[:], whoseCommit, ) // 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 && whoseCommit.IsRemote(): htlcScriptTree, err = input.SenderHTLCScriptTaproot( keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, keyRing.RevocationKey, rHash[:], whoseCommit, ) // 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 && whoseCommit.IsLocal(): htlcScriptTree, err = input.SenderHTLCScriptTaproot( keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:], whoseCommit, ) // 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 && whoseCommit.IsRemote(): htlcScriptTree, err = input.ReceiverHTLCScriptTaproot( timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:], whoseCommit, ) } return htlcScriptTree, err } // 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. 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 bool, whoseCommit lntypes.ChannelParty, timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing, ) (input.ScriptDescriptor, error) { if !chanType.IsTaproot() { return genSegwitV0HtlcScript( chanType, isIncoming, whoseCommit, timeout, rHash, keyRing, ) } return genTaprootHtlcScript( isIncoming, whoseCommit, timeout, rHash, keyRing, ) } // 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, whoseCommit lntypes.ChannelParty, isIncoming bool, paymentDesc *PaymentDescriptor, keyRing *CommitmentKeyRing, chanType channeldb.ChannelType) error { timeout := paymentDesc.Timeout rHash := paymentDesc.RHash scriptInfo, err := genHtlcScript( chanType, isIncoming, whoseCommit, timeout, rHash, keyRing, ) if err != nil { return err } pkScript := scriptInfo.PkScript() // Add the new HTLC outputs to the respective commitment transactions. amountPending := int64(paymentDesc.Amount.ToSatoshis()) 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 whoseCommit.IsLocal() { paymentDesc.ourPkScript = pkScript paymentDesc.ourWitnessScript = scriptInfo.WitnessScriptToSign() } else { paymentDesc.theirPkScript = pkScript //nolint:lll paymentDesc.theirWitnessScript = scriptInfo.WitnessScriptToSign() } return nil } // findOutputIndexesFromRemote finds the index of our and their outputs from // the remote commitment transaction. It derives the key ring to compute the // output scripts and compares them against the outputs inside the commitment // to find the match. func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash, chanState *channeldb.OpenChannel) (uint32, uint32, error) { // Init the output indexes as empty. ourIndex := uint32(channeldb.OutputIndexEmpty) theirIndex := uint32(channeldb.OutputIndexEmpty) chanCommit := chanState.RemoteCommitment _, commitmentPoint := btcec.PrivKeyFromBytes(revocationPreimage[:]) // With the commitment point generated, we can now derive the king ring // which will be used to generate the output scripts. keyRing := DeriveCommitmentKeys( commitmentPoint, lntypes.Remote, chanState.ChanType, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, ) // Since it's remote commitment chain, we'd used the mirrored values. // // We use the remote's channel config for the csv delay. theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay) // If we are the initiator of this channel, then it's be false from the // remote's PoV. isRemoteInitiator := !chanState.IsInitiator var leaseExpiry uint32 if chanState.ChanType.HasLeaseExpiration() { leaseExpiry = chanState.ThawHeight } // Map the scripts from our PoV. When facing a local commitment, the to // local output belongs to us and the to remote output belongs to them. // When facing a remote commitment, the to local output belongs to them // and the to remote output belongs to us. // Compute the to local script. From our PoV, when facing a remote // commitment, the to local output belongs to them. theirScript, err := CommitScriptToSelf( chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey, keyRing.RevocationKey, theirDelay, leaseExpiry, ) if err != nil { return ourIndex, theirIndex, err } // Compute the to remote script. From our PoV, when facing a remote // commitment, the to remote output belongs to us. ourScript, _, err := CommitScriptToRemote( chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, leaseExpiry, ) if err != nil { return ourIndex, theirIndex, err } // 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()): ourIndex = uint32(i) case bytes.Equal(txOut.PkScript, theirScript.PkScript()): theirIndex = uint32(i) } } return ourIndex, theirIndex, nil }