multi: use prev output fetcher where possible

This commit is contained in:
Oliver Gugger 2022-03-18 18:37:44 +01:00
parent 72c9582b85
commit f130eddb92
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
13 changed files with 255 additions and 44 deletions

View File

@ -1096,11 +1096,15 @@ func (bo *breachedOutput) SignDesc() *input.SignDescriptor {
// sign descriptor. The method then returns the witness computed by invoking
// this function on the first and subsequent calls.
func (bo *breachedOutput) CraftInputScript(signer input.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) (*input.Script, error) {
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (*input.Script, error) {
// First, we ensure that the witness generation function has been
// initialized for this breached output.
bo.witnessFunc = bo.witnessType.WitnessGenerator(signer, bo.SignDesc())
signDesc := bo.SignDesc()
signDesc.PrevOutputFetcher = prevOutputFetcher
bo.witnessFunc = bo.witnessType.WitnessGenerator(signer, signDesc)
// Now that we have ensured that the witness generation function has
// been initialized, we can proceed to execute it and generate the
@ -1397,8 +1401,8 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64,
// Compute the total amount contained in the inputs.
var totalAmt btcutil.Amount
for _, input := range inputs {
totalAmt += btcutil.Amount(input.SignDesc().Output.Value)
for _, inp := range inputs {
totalAmt += btcutil.Amount(inp.SignDesc().Output.Value)
}
// We'll actually attempt to target inclusion within the next two
@ -1424,10 +1428,10 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64,
// Next, we add all of the spendable outputs as inputs to the
// transaction.
for _, input := range inputs {
for _, inp := range inputs {
txn.AddTxIn(&wire.TxIn{
PreviousOutPoint: *input.OutPoint(),
Sequence: input.BlocksToMaturity(),
PreviousOutPoint: *inp.OutPoint(),
Sequence: inp.BlocksToMaturity(),
})
}
@ -1440,7 +1444,11 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64,
// Create a sighash cache to improve the performance of hashing and
// signing SigHashAll inputs.
hashCache := input.NewTxSigHashesV0Only(txn)
prevOutputFetcher, err := input.MultiPrevOutFetcher(inputs)
if err != nil {
return nil, err
}
hashCache := txscript.NewTxSigHashes(txn, prevOutputFetcher)
// Create a closure that encapsulates the process of initializing a
// particular output's witness generation function, computing the
@ -1452,7 +1460,7 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64,
// transaction using the SpendableOutput's witness generation
// function.
inputScript, err := so.CraftInputScript(
b.cfg.Signer, txn, hashCache, idx,
b.cfg.Signer, txn, hashCache, prevOutputFetcher, idx,
)
if err != nil {
return err
@ -1467,8 +1475,8 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64,
// Finally, generate a witness for each output and attach it to the
// transaction.
for i, input := range inputs {
if err := addWitness(i, input); err != nil {
for i, inp := range inputs {
if err := addWitness(i, inp); err != nil {
return nil, err
}
}
@ -1648,7 +1656,7 @@ func (ret *retributionInfo) Encode(w io.Writer) error {
return nil
}
// Dencode deserializes a retribution from the passed byte stream.
// Decode deserializes a retribution from the passed byte stream.
func (ret *retributionInfo) Decode(r io.Reader) error {
var scratch [32]byte

View File

@ -1386,8 +1386,11 @@ func getSpendTransactions(signer input.Signer, chanPoint *wire.OutPoint,
case input.HtlcAcceptedRevoke:
fallthrough
case input.HtlcOfferedRevoke:
cannedFetcher := txscript.NewCannedPrevOutputFetcher(
nil, 0,
)
inputScript, err := inp.CraftInputScript(
signer, htlcSweep, hashCache, 0,
signer, htlcSweep, hashCache, cannedFetcher, 0,
)
if err != nil {
return nil, err

View File

@ -42,6 +42,7 @@ type Input interface {
// 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
@ -221,9 +222,13 @@ func NewCsvInputWithCltv(outpoint *wire.OutPoint, witnessType WitnessType,
// 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, txinIdx int) (*Script, error) {
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
error) {
witnessFunc := bi.witnessType.WitnessGenerator(signer, bi.SignDesc())
signDesc := bi.SignDesc()
signDesc.PrevOutputFetcher = prevOutputFetcher
witnessFunc := bi.witnessType.WitnessGenerator(signer, signDesc)
return witnessFunc(txn, hashCache, txinIdx)
}
@ -260,11 +265,14 @@ func MakeHtlcSucceedInput(outpoint *wire.OutPoint,
// 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, txinIdx int) (*Script, error) {
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
error) {
desc := h.signDesc
desc.SigHashes = hashCache
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher
witness, err := SenderHtlcSpendRedeem(
signer, &desc, txn, h.preimage,
@ -291,7 +299,9 @@ type HtlcSecondLevelAnchorInput struct {
// createWitness creates a witness allowing the passed transaction to
// spend the input.
createWitness func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) (wire.TxWitness, error)
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error)
}
// RequiredTxOut returns the tx out needed to be present on the sweep tx for
@ -313,9 +323,12 @@ func (i *HtlcSecondLevelAnchorInput) RequiredLockTime() (uint32, bool) {
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (i *HtlcSecondLevelAnchorInput) CraftInputScript(signer Signer,
txn *wire.MsgTx, hashCache *txscript.TxSigHashes,
txinIdx int) (*Script, error) {
prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
error) {
witness, err := i.createWitness(signer, txn, hashCache, txinIdx)
witness, err := i.createWitness(
signer, txn, hashCache, prevOutputFetcher, txinIdx,
)
if err != nil {
return nil, err
}
@ -335,11 +348,13 @@ func MakeHtlcSecondLevelTimeoutAnchorInput(signedTx *wire.MsgTx,
// 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 = NewTxSigHashesV0Only(txn)
desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher)
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher
return SenderHtlcSpendTimeout(
signDetails.PeerSig, signDetails.SigHashType, signer,
@ -373,11 +388,13 @@ func MakeHtlcSecondLevelSuccessAnchorInput(signedTx *wire.MsgTx,
// 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,

View File

@ -74,6 +74,11 @@ type SignDescriptor struct {
// generating the final sighash for signing.
SigHashes *txscript.TxSigHashes
// PrevOutputFetcher is an interface that can return the output
// information on all UTXOs that are being spent in this transaction.
// This MUST be set when spending Taproot outputs.
PrevOutputFetcher txscript.PrevOutputFetcher
// InputIndex is the target input within the transaction that should be
// signed.
InputIndex int

View File

@ -1,6 +1,8 @@
package input
import (
"fmt"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
)
@ -18,3 +20,25 @@ func NewTxSigHashesV0Only(tx *wire.MsgTx) *txscript.TxSigHashes {
nilFetcher := txscript.NewCannedPrevOutputFetcher(nil, 0)
return txscript.NewTxSigHashes(tx, nilFetcher)
}
// MultiPrevOutFetcher returns a txscript.MultiPrevOutFetcher for the given set
// of inputs.
func MultiPrevOutFetcher(inputs []Input) (*txscript.MultiPrevOutFetcher, error) {
fetcher := txscript.NewMultiPrevOutFetcher(nil)
for _, inp := range inputs {
op := inp.OutPoint()
desc := inp.SignDesc()
if op == nil {
return nil, fmt.Errorf("missing input outpoint")
}
if desc == nil || desc.Output == nil {
return nil, fmt.Errorf("missing input utxo information")
}
fetcher.AddPrevOut(*op, desc.Output)
}
return fetcher, nil
}

View File

@ -9,6 +9,7 @@ import (
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
@ -107,7 +108,8 @@ func (b *BtcWallet) SignPsbt(packet *psbt.Packet) error {
// there are inputs that we don't know how to sign, we won't return any
// error. So it's possible we're not the final signer.
tx := packet.UnsignedTx
sigHashes := input.NewTxSigHashesV0Only(tx)
prevOutputFetcher := wallet.PsbtPrevOutputFetcher(packet)
sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)
for idx := range tx.TxIn {
in := packet.Inputs[idx]

View File

@ -107,9 +107,15 @@ func (f *FullIntent) CompileFundingTx(extraInputs []*wire.TxIn,
// Next, sign all inputs that are ours, collecting the signatures in
// order of the inputs.
prevOutFetcher := NewSegWitV0DualFundingPrevOutputFetcher(
f.coinSource, extraInputs,
)
signDesc := input.SignDescriptor{
HashType: txscript.SigHashAll,
SigHashes: input.NewTxSigHashesV0Only(fundingTx),
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(
fundingTx, prevOutFetcher,
),
PrevOutputFetcher: prevOutFetcher,
}
for i, txIn := range fundingTx.TxIn {
// We can only sign this input if it's ours, so we'll ask the
@ -374,3 +380,55 @@ func (w *WalletAssembler) FundingTxAvailable() {}
// A compile-time assertion to ensure the WalletAssembler meets the
// FundingTxAssembler interface.
var _ FundingTxAssembler = (*WalletAssembler)(nil)
// SegWitV0DualFundingPrevOutputFetcher is a txscript.PrevOutputFetcher that
// knows about local and remote funding inputs.
//
// TODO(guggero): Support dual funding with p2tr inputs, currently only segwit
// v0 inputs are supported.
type SegWitV0DualFundingPrevOutputFetcher struct {
local CoinSource
remote *txscript.MultiPrevOutFetcher
}
var _ txscript.PrevOutputFetcher = (*SegWitV0DualFundingPrevOutputFetcher)(nil)
// NewSegWitV0DualFundingPrevOutputFetcher creates a new
// txscript.PrevOutputFetcher from the given local and remote inputs.
//
// NOTE: Since the actual pkScript and amounts aren't passed in, this will just
// make sure that nothing will panic when creating a SegWit v0 sighash. But this
// code will NOT WORK for transactions that spend any Taproot inputs!
func NewSegWitV0DualFundingPrevOutputFetcher(localSource CoinSource,
remoteInputs []*wire.TxIn) txscript.PrevOutputFetcher {
remote := txscript.NewMultiPrevOutFetcher(nil)
for _, inp := range remoteInputs {
// We add an empty output to prevent the sighash calculation
// from panicking. But this will always detect the inputs as
// SegWig v0!
remote.AddPrevOut(inp.PreviousOutPoint, &wire.TxOut{})
}
return &SegWitV0DualFundingPrevOutputFetcher{
local: localSource,
remote: remote,
}
}
// FetchPrevOutput attempts to fetch the previous output referenced by the
// passed outpoint.
//
// NOTE: This is a part of the txscript.PrevOutputFetcher interface.
func (d *SegWitV0DualFundingPrevOutputFetcher) FetchPrevOutput(
op wire.OutPoint) *wire.TxOut {
// Try the local source first. This will return nil if our internal
// wallet doesn't know the outpoint.
coin, err := d.local.CoinFromOutPoint(op)
if err == nil && coin != nil {
return &coin.TxOut
}
// Fall back to the remote
return d.remote.FetchPrevOutput(op)
}

View File

@ -133,9 +133,11 @@ func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut,
// We know at this point that we only have inputs from our own wallet.
// So we can just compute the input script using the remote signer.
outputFetcher := lnwallet.NewWalletPrevOutputFetcher(r.WalletController)
signDesc := input.SignDescriptor{
HashType: txscript.SigHashAll,
SigHashes: input.NewTxSigHashesV0Only(tx),
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(tx, outputFetcher),
PrevOutputFetcher: outputFetcher,
}
for i, txIn := range tx.TxIn {
// We can only sign this input if it's ours, so we'll ask the
@ -579,6 +581,32 @@ func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor,
return nil, fmt.Errorf("error converting TX into PSBT: %v", err)
}
// We need to add witness information for all inputs! Otherwise, we'll
// have a problem when attempting to sign a taproot input!
for idx := range packet.Inputs {
// Skip the input we're signing for, that will get a special
// treatment later on.
if idx == signDesc.InputIndex {
continue
}
txIn := tx.TxIn[idx]
info, err := r.WalletController.FetchInputInfo(
&txIn.PreviousOutPoint,
)
if err != nil {
log.Warnf("No UTXO info found for index %d "+
"(prev_outpoint=%v), won't be able to sign "+
"for taproot output!", idx,
txIn.PreviousOutPoint)
continue
}
packet.Inputs[idx].WitnessUtxo = &wire.TxOut{
Value: int64(info.Value),
PkScript: info.PkScript,
}
}
// Catch incorrect signing input index, just in case.
if signDesc.InputIndex < 0 || signDesc.InputIndex >= len(packet.Inputs) {
return nil, fmt.Errorf("invalid input index in sign descriptor")

View File

@ -2266,3 +2266,36 @@ func validateUpfrontShutdown(shutdown lnwire.DeliveryAddress,
return false
}
}
// WalletPrevOutputFetcher is a txscript.PrevOutputFetcher that can fetch
// outputs from a given wallet controller.
type WalletPrevOutputFetcher struct {
wc WalletController
}
// A compile time assertion that WalletPrevOutputFetcher implements the
// txscript.PrevOutputFetcher interface.
var _ txscript.PrevOutputFetcher = (*WalletPrevOutputFetcher)(nil)
// NewWalletPrevOutputFetcher creates a new WalletPrevOutputFetcher that fetches
// previous outputs from the given wallet controller.
func NewWalletPrevOutputFetcher(wc WalletController) *WalletPrevOutputFetcher {
return &WalletPrevOutputFetcher{
wc: wc,
}
}
// FetchPrevOutput attempts to fetch the previous output referenced by the
// passed outpoint. A nil value will be returned if the passed outpoint doesn't
// exist.
func (w *WalletPrevOutputFetcher) FetchPrevOutput(op wire.OutPoint) *wire.TxOut {
utxo, err := w.wc.FetchInputInfo(&op)
if err != nil {
return nil
}
return &wire.TxOut{
Value: int64(utxo.Value),
PkScript: utxo.PkScript,
}
}

View File

@ -1623,7 +1623,9 @@ func (i *testInput) RequiredTxOut() *wire.TxOut {
// encode the spending outpoint and the tx input index as part of the returned
// witness.
func (i *testInput) CraftInputScript(_ input.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) (*input.Script, error) {
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (*input.Script, error) {
// We'll encode the outpoint in the witness, so we can assert that the
// expected input was signed at the correct index.

View File

@ -7,6 +7,7 @@ import (
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
@ -263,13 +264,18 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
return nil, err
}
hashCache := input.NewTxSigHashesV0Only(sweepTx)
prevInputFetcher, err := input.MultiPrevOutFetcher(inputs)
if err != nil {
return nil, fmt.Errorf("error creating prev input fetcher "+
"for hash cache: %v", err)
}
hashCache := txscript.NewTxSigHashes(sweepTx, prevInputFetcher)
// With all the inputs in place, use each output's unique input script
// function to generate the final witness required for spending.
addInputScript := func(idx int, tso input.Input) error {
inputScript, err := tso.CraftInputScript(
signer, sweepTx, hashCache, idx,
signer, sweepTx, hashCache, prevInputFetcher, idx,
)
if err != nil {
return err

View File

@ -2,6 +2,7 @@ package lookout
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec/v2"
@ -177,11 +178,11 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64,
// First, construct add the breached inputs to our justice transaction
// and compute the total amount that will be swept.
var totalAmt btcutil.Amount
for _, input := range inputs {
totalAmt += btcutil.Amount(input.txOut.Value)
for _, inp := range inputs {
totalAmt += btcutil.Amount(inp.txOut.Value)
justiceTxn.AddTxIn(&wire.TxIn{
PreviousOutPoint: input.outPoint,
Sequence: input.sequence,
PreviousOutPoint: inp.outPoint,
Sequence: inp.sequence,
})
}
@ -217,20 +218,22 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64,
}
// Attach each of the provided witnesses to the transaction.
for _, input := range inputs {
prevOutFetcher, err := prevOutFetcher(inputs)
if err != nil {
return nil, fmt.Errorf("error creating previous output "+
"fetcher: %v", err)
}
for _, inp := range inputs {
// Lookup the input's new post-sort position.
i := inputIndex[input.outPoint]
justiceTxn.TxIn[i].Witness = input.witness
i := inputIndex[inp.outPoint]
justiceTxn.TxIn[i].Witness = inp.witness
// Validate the reconstructed witnesses to ensure they are valid
// for the breached inputs.
vm, err := txscript.NewEngine(
input.txOut.PkScript, justiceTxn, i,
inp.txOut.PkScript, justiceTxn, i,
txscript.StandardVerifyFlags,
nil, nil, input.txOut.Value,
txscript.NewCannedPrevOutputFetcher(
input.txOut.PkScript, input.txOut.Value,
),
nil, nil, inp.txOut.Value, prevOutFetcher,
)
if err != nil {
return nil, err
@ -345,3 +348,20 @@ func buildWitness(witnessStack [][]byte, witnessScript []byte) [][]byte {
return witness
}
// prevOutFetcher returns a txscript.MultiPrevOutFetcher for the given set
// of inputs.
func prevOutFetcher(inputs []*breachedInput) (*txscript.MultiPrevOutFetcher,
error) {
fetcher := txscript.NewMultiPrevOutFetcher(nil)
for _, inp := range inputs {
if inp.txOut == nil {
return nil, fmt.Errorf("missing input utxo information")
}
fetcher.AddPrevOut(inp.outPoint, inp.txOut)
}
return fetcher, nil
}

View File

@ -7,6 +7,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/txsort"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
@ -262,10 +263,14 @@ func (t *backupTask) craftSessionPayload(
// information. This will either be contain both the to-local and
// to-remote outputs, or only be the to-local output.
inputs := t.inputs()
for prevOutPoint, input := range inputs {
prevOutputFetcher := txscript.NewMultiPrevOutFetcher(nil)
for prevOutPoint, inp := range inputs {
prevOutputFetcher.AddPrevOut(
prevOutPoint, inp.SignDesc().Output,
)
justiceTxn.AddTxIn(&wire.TxIn{
PreviousOutPoint: prevOutPoint,
Sequence: input.BlocksToMaturity(),
Sequence: inp.BlocksToMaturity(),
})
}
@ -284,7 +289,7 @@ func (t *backupTask) craftSessionPayload(
}
// Construct a sighash cache to improve signing performance.
hashCache := input.NewTxSigHashesV0Only(justiceTxn)
hashCache := txscript.NewTxSigHashes(justiceTxn, prevOutputFetcher)
// Since the transaction inputs could have been reordered as a result of
// the BIP69 sort, create an index mapping each prevout to it's new
@ -303,7 +308,7 @@ func (t *backupTask) craftSessionPayload(
// Construct the full witness required to spend this input.
inputScript, err := inp.CraftInputScript(
signer, justiceTxn, hashCache, i,
signer, justiceTxn, hashCache, prevOutputFetcher, i,
)
if err != nil {
return hint, nil, err