lnd/input/input.go
Olaoluwa Osuntokun 0720abca39 input: add production taproot HTLC succeed input constructor
This commit adds MakeTaprootHtlcSucceedInputFinal, a new input constructor
specifically for creating HTLC success inputs that use production taproot
witness types. This function parallels the existing MakeTaprootHtlcSucceedInput
but creates inputs with the TaprootHtlcAcceptedRemoteSuccessFinal witness type
instead of the staging variant.

The new constructor follows the same pattern and signature as its staging
counterpart, ensuring consistency in the input creation API. This allows
contract resolvers to create the appropriate input type based on whether
they are handling a staging or production taproot channel, ensuring that
the correct witness generation logic is applied during transaction creation.

This addition provides the necessary infrastructure for production taproot
channels to properly construct inputs for sweeping HTLC outputs on remote
commitment transactions with the optimized script structure.
2026-04-13 12:17:55 -07:00

646 lines
20 KiB
Go

package input
import (
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/tlv"
)
// EmptyOutPoint is a zeroed outpoint.
var EmptyOutPoint wire.OutPoint
// Input represents an abstract UTXO which is to be spent using a sweeping
// transaction. The method provided give the caller all information needed to
// construct a valid input within a sweeping transaction to sweep this
// lingering UTXO.
type Input interface {
// OutPoint returns the reference to the output being spent, used to
// construct the corresponding transaction input.
OutPoint() wire.OutPoint
// RequiredTxOut returns a non-nil TxOut if input commits to a certain
// transaction output. This is used in the SINGLE|ANYONECANPAY case to
// make sure any presigned input is still valid by including the
// output.
RequiredTxOut() *wire.TxOut
// RequiredLockTime returns whether this input commits to a tx locktime
// that must be used in the transaction including it.
RequiredLockTime() (uint32, bool)
// WitnessType returns an enum specifying the type of witness that must
// be generated in order to spend this output.
WitnessType() WitnessType
// SignDesc returns a reference to a spendable output's sign
// descriptor, which is used during signing to compute a valid witness
// that spends this output.
SignDesc() *SignDescriptor
// CraftInputScript returns a valid set of input scripts allowing this
// output to be spent. The returns input scripts should target the
// input at location txIndex within the passed transaction. The input
// scripts generated by this method support spending p2wkh, p2wsh, and
// also nested p2sh outputs.
CraftInputScript(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (*Script, error)
// BlocksToMaturity returns the relative timelock, as a number of
// blocks, that must be built on top of the confirmation height before
// the output can be spent. For non-CSV locked inputs this is always
// zero.
BlocksToMaturity() uint32
// HeightHint returns the minimum height at which a confirmed spending
// tx can occur.
HeightHint() uint32
// UnconfParent returns information about a possibly unconfirmed parent
// tx.
UnconfParent() *TxInfo
// ResolutionBlob returns a special opaque blob to be used to
// sweep/resolve this input.
ResolutionBlob() fn.Option[tlv.Blob]
// Preimage returns the preimage for the input if it is an HTLC input.
Preimage() fn.Option[lntypes.Preimage]
}
// TxInfo describes properties of a parent tx that are relevant for CPFP.
type TxInfo struct {
// Fee is the fee of the tx.
Fee btcutil.Amount
// Weight is the weight of the tx.
Weight lntypes.WeightUnit
}
// String returns a human readable version of the tx info.
func (t *TxInfo) String() string {
return fmt.Sprintf("fee=%v, weight=%v", t.Fee, t.Weight)
}
// SignDetails is a struct containing information needed to resign certain
// inputs. It is used to re-sign 2nd level HTLC transactions that uses the
// SINGLE|ANYONECANPAY sighash type, as we have a signature provided by our
// peer, but we can aggregate multiple of these 2nd level transactions into a
// new transaction, that needs to be signed by us.
type SignDetails struct {
// SignDesc is the sign descriptor needed for us to sign the input.
SignDesc SignDescriptor
// PeerSig is the peer's signature for this input.
PeerSig Signature
// SigHashType is the sighash signed by the peer.
SigHashType txscript.SigHashType
}
type inputKit struct {
outpoint wire.OutPoint
witnessType WitnessType
signDesc SignDescriptor
heightHint uint32
blockToMaturity uint32
cltvExpiry uint32
// unconfParent contains information about a potential unconfirmed
// parent transaction.
unconfParent *TxInfo
// resolutionBlob is an optional blob that can be used to resolve an
// input.
resolutionBlob fn.Option[tlv.Blob]
}
// OutPoint returns the breached output's identifier that is to be included as
// a transaction input.
func (i *inputKit) OutPoint() wire.OutPoint {
return i.outpoint
}
// RequiredTxOut returns a nil for the base input type.
func (i *inputKit) RequiredTxOut() *wire.TxOut {
return nil
}
// RequiredLockTime returns whether this input commits to a tx locktime that
// must be used in the transaction including it. This will be false for the
// base input type since we can re-sign for any lock time.
func (i *inputKit) RequiredLockTime() (uint32, bool) {
return i.cltvExpiry, i.cltvExpiry > 0
}
// WitnessType returns the type of witness that must be generated to spend the
// breached output.
func (i *inputKit) WitnessType() WitnessType {
return i.witnessType
}
// SignDesc returns the breached output's SignDescriptor, which is used during
// signing to compute the witness.
func (i *inputKit) SignDesc() *SignDescriptor {
return &i.signDesc
}
// HeightHint returns the minimum height at which a confirmed spending
// tx can occur.
func (i *inputKit) HeightHint() uint32 {
return i.heightHint
}
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
// must be built on top of the confirmation height before the output can be
// spent. For non-CSV locked inputs this is always zero.
func (i *inputKit) BlocksToMaturity() uint32 {
return i.blockToMaturity
}
// Cpfp returns information about a possibly unconfirmed parent tx.
func (i *inputKit) UnconfParent() *TxInfo {
return i.unconfParent
}
// ResolutionBlob returns a special opaque blob to be used to sweep/resolve
// this input.
func (i *inputKit) ResolutionBlob() fn.Option[tlv.Blob] {
return i.resolutionBlob
}
// inputOpts contains options for constructing a new input.
type inputOpts struct {
// resolutionBlob is an optional blob that can be used to resolve an
// input.
resolutionBlob fn.Option[tlv.Blob]
}
// defaultInputOpts returns a new inputOpts with default values.
func defaultInputOpts() *inputOpts {
return &inputOpts{}
}
// InputOpt is a functional option that can be used to modify the default input
// options.
type InputOpt func(*inputOpts) //nolint:revive
// WithResolutionBlob is an option that can be used to set a resolution blob on
// for an input.
func WithResolutionBlob(b fn.Option[tlv.Blob]) InputOpt {
return func(o *inputOpts) {
o.resolutionBlob = b
}
}
// BaseInput contains all the information needed to sweep a basic
// output (CSV/CLTV/no time lock).
type BaseInput struct {
inputKit
}
// MakeBaseInput assembles a new BaseInput that can be used to construct a
// sweep transaction.
func MakeBaseInput(outpoint *wire.OutPoint, witnessType WitnessType,
signDescriptor *SignDescriptor, heightHint uint32,
unconfParent *TxInfo, opts ...InputOpt) BaseInput {
opt := defaultInputOpts()
for _, optF := range opts {
optF(opt)
}
return BaseInput{
inputKit{
outpoint: *outpoint,
witnessType: witnessType,
signDesc: *signDescriptor,
heightHint: heightHint,
unconfParent: unconfParent,
resolutionBlob: opt.resolutionBlob,
},
}
}
// NewBaseInput allocates and assembles a new *BaseInput that can be used to
// construct a sweep transaction.
func NewBaseInput(outpoint *wire.OutPoint, witnessType WitnessType,
signDescriptor *SignDescriptor, heightHint uint32,
opts ...InputOpt) *BaseInput {
input := MakeBaseInput(
outpoint, witnessType, signDescriptor, heightHint, nil, opts...,
)
return &input
}
// NewCsvInput assembles a new csv-locked input that can be used to
// construct a sweep transaction.
func NewCsvInput(outpoint *wire.OutPoint, witnessType WitnessType,
signDescriptor *SignDescriptor, heightHint uint32,
blockToMaturity uint32, opts ...InputOpt) *BaseInput {
input := MakeBaseInput(
outpoint, witnessType, signDescriptor, heightHint, nil, opts...,
)
input.blockToMaturity = blockToMaturity
return &input
}
// NewCsvInputWithCltv assembles a new csv and cltv locked input that can be
// used to construct a sweep transaction.
func NewCsvInputWithCltv(outpoint *wire.OutPoint, witnessType WitnessType,
signDescriptor *SignDescriptor, heightHint uint32,
csvDelay uint32, cltvExpiry uint32, opts ...InputOpt) *BaseInput {
input := MakeBaseInput(
outpoint, witnessType, signDescriptor, heightHint, nil, opts...,
)
input.blockToMaturity = csvDelay
input.cltvExpiry = cltvExpiry
return &input
}
// CraftInputScript returns a valid set of input scripts allowing this output
// to be spent. The returned input scripts should target the input at location
// txIndex within the passed transaction. The input scripts generated by this
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (bi *BaseInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
error) {
signDesc := bi.SignDesc()
signDesc.PrevOutputFetcher = prevOutputFetcher
witnessFunc := bi.witnessType.WitnessGenerator(signer, signDesc)
return witnessFunc(txn, hashCache, txinIdx)
}
// Preimage returns the preimage for the input if it is an HTLC input.
func (bi *BaseInput) Preimage() fn.Option[lntypes.Preimage] {
return fn.None[lntypes.Preimage]()
}
// HtlcSucceedInput constitutes a sweep input that needs a pre-image. The input
// is expected to reside on the commitment tx of the remote party and should
// not be a second level tx output.
type HtlcSucceedInput struct {
inputKit
preimage []byte
}
// MakeHtlcSucceedInput assembles a new redeem input that can be used to
// construct a sweep transaction.
func MakeHtlcSucceedInput(outpoint *wire.OutPoint,
signDescriptor *SignDescriptor, preimage []byte, heightHint,
blocksToMaturity uint32, opts ...InputOpt) HtlcSucceedInput {
input := MakeBaseInput(
outpoint, HtlcAcceptedRemoteSuccess, signDescriptor,
heightHint, nil, opts...,
)
input.blockToMaturity = blocksToMaturity
return HtlcSucceedInput{
inputKit: input.inputKit,
preimage: preimage,
}
}
// MakeTaprootHtlcSucceedInput creates a new HtlcSucceedInput that can be used
// to spend an HTLC output for a taproot channel on the remote party's
// commitment transaction.
func MakeTaprootHtlcSucceedInput(op *wire.OutPoint, signDesc *SignDescriptor,
preimage []byte, heightHint, blocksToMaturity uint32,
opts ...InputOpt) HtlcSucceedInput {
input := MakeBaseInput(
op, TaprootHtlcAcceptedRemoteSuccess, signDesc,
heightHint, nil, opts...,
)
input.blockToMaturity = blocksToMaturity
return HtlcSucceedInput{
inputKit: input.inputKit,
preimage: preimage,
}
}
// MakeTaprootHtlcSucceedInputFinal creates a new HtlcSucceedInput that can be used
// to spend an HTLC output for a production taproot channel on the remote party's
// commitment transaction.
func MakeTaprootHtlcSucceedInputFinal(op *wire.OutPoint, signDesc *SignDescriptor,
preimage []byte, heightHint, blocksToMaturity uint32,
opts ...InputOpt) HtlcSucceedInput {
input := MakeBaseInput(
op, TaprootHtlcAcceptedRemoteSuccessFinal, signDesc,
heightHint, nil, opts...,
)
input.blockToMaturity = blocksToMaturity
return HtlcSucceedInput{
inputKit: input.inputKit,
preimage: preimage,
}
}
// CraftInputScript returns a valid set of input scripts allowing this output
// to be spent. The returns input scripts should target the input at location
// txIndex within the passed transaction. The input scripts generated by this
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (h *HtlcSucceedInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
error) {
desc := h.signDesc
desc.SigHashes = hashCache
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher
isTaproot := txscript.IsPayToTaproot(desc.Output.PkScript)
var (
witness wire.TxWitness
err error
)
if isTaproot {
if desc.ControlBlock == nil {
return nil, fmt.Errorf("ctrl block must be set")
}
desc.SignMethod = TaprootScriptSpendSignMethod
witness, err = SenderHTLCScriptTaprootRedeem(
signer, &desc, txn, h.preimage, nil, nil,
)
} else {
witness, err = SenderHtlcSpendRedeem(
signer, &desc, txn, h.preimage,
)
}
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
}
// Preimage returns the preimage for the input if it is an HTLC input.
func (h *HtlcSucceedInput) Preimage() fn.Option[lntypes.Preimage] {
if len(h.preimage) == 0 {
return fn.None[lntypes.Preimage]()
}
return fn.Some(lntypes.Preimage(h.preimage))
}
// HtlcSecondLevelAnchorInput is an input type used to spend HTLC outputs
// using a re-signed second level transaction, either via the timeout or success
// paths.
type HtlcSecondLevelAnchorInput struct {
inputKit
// SignedTx is the original second level transaction signed by the
// channel peer.
SignedTx *wire.MsgTx
// createWitness creates a witness allowing the passed transaction to
// spend the input.
createWitness func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error)
preimage []byte
}
// RequiredTxOut returns the tx out needed to be present on the sweep tx for
// the spend of the input to be valid.
func (i *HtlcSecondLevelAnchorInput) RequiredTxOut() *wire.TxOut {
return i.SignedTx.TxOut[0]
}
// RequiredLockTime returns the locktime needed for the sweep tx for the spend
// of the input to be valid. For a second level HTLC timeout this will be the
// CLTV expiry, for HTLC success it will be zero.
func (i *HtlcSecondLevelAnchorInput) RequiredLockTime() (uint32, bool) {
return i.SignedTx.LockTime, true
}
// CraftInputScript returns a valid set of input scripts allowing this output
// to be spent. The returns input scripts should target the input at location
// txIndex within the passed transaction. The input scripts generated by this
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (i *HtlcSecondLevelAnchorInput) CraftInputScript(signer Signer,
txn *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
error) {
witness, err := i.createWitness(
signer, txn, hashCache, prevOutputFetcher, txinIdx,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
}
// Preimage returns the preimage for the input if it is an HTLC input.
func (i *HtlcSecondLevelAnchorInput) Preimage() fn.Option[lntypes.Preimage] {
if len(i.preimage) == 0 {
return fn.None[lntypes.Preimage]()
}
return fn.Some(lntypes.Preimage(i.preimage))
}
// MakeHtlcSecondLevelTimeoutAnchorInput creates an input allowing the sweeper
// to spend the HTLC output on our commit using the second level timeout
// transaction.
func MakeHtlcSecondLevelTimeoutAnchorInput(signedTx *wire.MsgTx,
signDetails *SignDetails, heightHint uint32,
opts ...InputOpt) HtlcSecondLevelAnchorInput {
// Spend an HTLC output on our local commitment tx using the
// 2nd timeout transaction.
createWitness := func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error) {
desc := signDetails.SignDesc
desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher)
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher
return SenderHtlcSpendTimeout(
signDetails.PeerSig, signDetails.SigHashType, signer,
&desc, txn,
)
}
input := MakeBaseInput(
&signedTx.TxIn[0].PreviousOutPoint,
HtlcOfferedTimeoutSecondLevelInputConfirmed,
&signDetails.SignDesc, heightHint, nil, opts...,
)
input.blockToMaturity = 1
return HtlcSecondLevelAnchorInput{
inputKit: input.inputKit,
SignedTx: signedTx,
createWitness: createWitness,
}
}
// MakeHtlcSecondLevelTimeoutTaprootInput creates an input that allows the
// sweeper to spend an HTLC output to the second level on our commitment
// transaction. The sweeper is also able to generate witnesses on demand to
// sweep the second level HTLC aggregated with other transactions.
func MakeHtlcSecondLevelTimeoutTaprootInput(signedTx *wire.MsgTx,
signDetails *SignDetails,
heightHint uint32, opts ...InputOpt) HtlcSecondLevelAnchorInput {
createWitness := func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error) {
desc := signDetails.SignDesc
if desc.ControlBlock == nil {
return nil, fmt.Errorf("ctrl block must be set")
}
desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher)
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher
desc.SignMethod = TaprootScriptSpendSignMethod
return SenderHTLCScriptTaprootTimeout(
signDetails.PeerSig, signDetails.SigHashType, signer,
&desc, txn, nil, nil,
)
}
input := MakeBaseInput(
&signedTx.TxIn[0].PreviousOutPoint,
TaprootHtlcLocalOfferedTimeout,
&signDetails.SignDesc, heightHint, nil, opts...,
)
input.blockToMaturity = 1
return HtlcSecondLevelAnchorInput{
inputKit: input.inputKit,
SignedTx: signedTx,
createWitness: createWitness,
}
}
// MakeHtlcSecondLevelSuccessAnchorInput creates an input allowing the sweeper
// to spend the HTLC output on our commit using the second level success
// transaction.
func MakeHtlcSecondLevelSuccessAnchorInput(signedTx *wire.MsgTx,
signDetails *SignDetails, preimage lntypes.Preimage,
heightHint uint32, opts ...InputOpt) HtlcSecondLevelAnchorInput {
// Spend an HTLC output on our local commitment tx using the 2nd
// success transaction.
createWitness := func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error) {
desc := signDetails.SignDesc
desc.SigHashes = hashCache
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher
return ReceiverHtlcSpendRedeem(
signDetails.PeerSig, signDetails.SigHashType,
preimage[:], signer, &desc, txn,
)
}
input := MakeBaseInput(
&signedTx.TxIn[0].PreviousOutPoint,
HtlcAcceptedSuccessSecondLevelInputConfirmed,
&signDetails.SignDesc, heightHint, nil, opts...,
)
input.blockToMaturity = 1
return HtlcSecondLevelAnchorInput{
SignedTx: signedTx,
inputKit: input.inputKit,
createWitness: createWitness,
preimage: preimage[:],
}
}
// MakeHtlcSecondLevelSuccessTaprootInput creates an input that allows the
// sweeper to spend an HTLC output to the second level on our taproot
// commitment transaction.
func MakeHtlcSecondLevelSuccessTaprootInput(signedTx *wire.MsgTx,
signDetails *SignDetails, preimage lntypes.Preimage,
heightHint uint32, opts ...InputOpt) HtlcSecondLevelAnchorInput {
createWitness := func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error) {
desc := signDetails.SignDesc
if desc.ControlBlock == nil {
return nil, fmt.Errorf("ctrl block must be set")
}
desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher)
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher
desc.SignMethod = TaprootScriptSpendSignMethod
return ReceiverHTLCScriptTaprootRedeem(
signDetails.PeerSig, signDetails.SigHashType,
preimage[:], signer, &desc, txn, nil, nil,
)
}
input := MakeBaseInput(
&signedTx.TxIn[0].PreviousOutPoint,
TaprootHtlcAcceptedLocalSuccess,
&signDetails.SignDesc, heightHint, nil, opts...,
)
input.blockToMaturity = 1
return HtlcSecondLevelAnchorInput{
inputKit: input.inputKit,
SignedTx: signedTx,
createWitness: createWitness,
preimage: preimage[:],
}
}
// Compile-time constraints to ensure each input struct implement the Input
// interface.
var _ Input = (*BaseInput)(nil)
var _ Input = (*HtlcSucceedInput)(nil)
var _ Input = (*HtlcSecondLevelAnchorInput)(nil)