mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-20 10:39:01 +01:00
ab7634b276
Add utxo restrictions for psbt internal wallet funded lightning channels. This also includes batchopening channels backed by the internal wallet.
729 lines
24 KiB
Go
729 lines
24 KiB
Go
package btcwallet
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
|
"github.com/btcsuite/btcwallet/wallet"
|
|
"github.com/btcsuite/btcwallet/wtxmgr"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
)
|
|
|
|
var (
|
|
// PsbtKeyTypeInputSignatureTweakSingle is a custom/proprietary PSBT key
|
|
// for an input that specifies what single tweak should be applied to
|
|
// the key before signing the input. The value 51 is leet speak for
|
|
// "si", short for "single".
|
|
PsbtKeyTypeInputSignatureTweakSingle = []byte{0x51}
|
|
|
|
// PsbtKeyTypeInputSignatureTweakDouble is a custom/proprietary PSBT key
|
|
// for an input that specifies what double tweak should be applied to
|
|
// the key before signing the input. The value d0 is leet speak for
|
|
// "do", short for "double".
|
|
PsbtKeyTypeInputSignatureTweakDouble = []byte{0xd0}
|
|
|
|
// ErrInputMissingUTXOInfo is returned if a PSBT input is supplied that
|
|
// does not specify the witness UTXO info.
|
|
ErrInputMissingUTXOInfo = errors.New(
|
|
"input doesn't specify any UTXO info",
|
|
)
|
|
|
|
// ErrScriptSpendFeeEstimationUnsupported is returned if a PSBT input is
|
|
// of a script spend type.
|
|
ErrScriptSpendFeeEstimationUnsupported = errors.New(
|
|
"cannot estimate fee for script spend inputs",
|
|
)
|
|
|
|
// ErrUnsupportedScript is returned if a supplied pk script is not
|
|
// known or supported.
|
|
ErrUnsupportedScript = errors.New("unsupported or unknown pk script")
|
|
)
|
|
|
|
// FundPsbt creates a fully populated PSBT packet that contains enough inputs to
|
|
// fund the outputs specified in the passed in packet with the specified fee
|
|
// rate. If there is change left, a change output from the internal wallet is
|
|
// added and the index of the change output is returned. Otherwise no additional
|
|
// output is created and the index -1 is returned. If no custom change
|
|
// scope is specified, the BIP0084 will be used for default accounts and single
|
|
// imported public keys. For custom account, no key scope should be provided
|
|
// as the coin selection key scope will always be used to generate the change
|
|
// address.
|
|
// The function argument `allowUtxo` specifies a filter function for utxos
|
|
// during coin selection. It should return true for utxos that can be used and
|
|
// false for those that should be excluded.
|
|
//
|
|
// NOTE: If the packet doesn't contain any inputs, coin selection is performed
|
|
// automatically. The account parameter must be non-empty as it determines which
|
|
// set of coins are eligible for coin selection. If the packet does contain any
|
|
// inputs, it is assumed that full coin selection happened externally and no
|
|
// additional inputs are added. If the specified inputs aren't enough to fund
|
|
// the outputs with the given fee rate, an error is returned. No lock lease is
|
|
// acquired for any of the selected/validated inputs. It is in the caller's
|
|
// responsibility to lock the inputs before handing them out.
|
|
//
|
|
// This is a part of the WalletController interface.
|
|
func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32,
|
|
feeRate chainfee.SatPerKWeight, accountName string,
|
|
changeScope *waddrmgr.KeyScope,
|
|
strategy wallet.CoinSelectionStrategy,
|
|
allowUtxo func(wtxmgr.Credit) bool) (int32, error) {
|
|
|
|
// The fee rate is passed in using units of sat/kw, so we'll convert
|
|
// this to sat/KB as the CreateSimpleTx method requires this unit.
|
|
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
|
|
|
|
var (
|
|
keyScope *waddrmgr.KeyScope
|
|
accountNum uint32
|
|
)
|
|
|
|
switch accountName {
|
|
// For default accounts and single imported public keys, we'll provide a
|
|
// nil key scope to FundPsbt, allowing it to select inputs from all
|
|
// scopes (NP2WKH, P2WKH, P2TR). By default, the change key scope for
|
|
// these accounts will be P2WKH.
|
|
case lnwallet.DefaultAccountName:
|
|
if changeScope == nil {
|
|
changeScope = &waddrmgr.KeyScopeBIP0084
|
|
}
|
|
|
|
accountNum = defaultAccount
|
|
|
|
case waddrmgr.ImportedAddrAccountName:
|
|
if changeScope == nil {
|
|
changeScope = &waddrmgr.KeyScopeBIP0084
|
|
}
|
|
|
|
accountNum = importedAccount
|
|
|
|
// Otherwise, map the account name to its key scope and internal account
|
|
// number to only select inputs from said account. No change key scope
|
|
// should have been specified as a custom account should only have one
|
|
// key scope. Providing a change key scope would break this assumption
|
|
// and lead to non-deterministic behavior by using a different change
|
|
// key scope than the custom account key scope. The change key scope
|
|
// will always be the same as the coin selection.
|
|
default:
|
|
if changeScope != nil {
|
|
return 0, fmt.Errorf("couldn't select a " +
|
|
"custom change type for custom accounts")
|
|
}
|
|
|
|
scope, account, err := b.lookupFirstCustomAccount(accountName)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
keyScope = &scope
|
|
changeScope = keyScope
|
|
accountNum = account
|
|
}
|
|
|
|
var opts []wallet.TxCreateOption
|
|
if changeScope != nil {
|
|
opts = append(opts, wallet.WithCustomChangeScope(changeScope))
|
|
}
|
|
if allowUtxo != nil {
|
|
opts = append(opts, wallet.WithUtxoFilter(allowUtxo))
|
|
}
|
|
|
|
// Let the wallet handle coin selection and/or fee estimation based on
|
|
// the partial TX information in the packet.
|
|
return b.wallet.FundPsbt(
|
|
packet, keyScope, minConfs, accountNum, feeSatPerKB,
|
|
strategy, opts...,
|
|
)
|
|
}
|
|
|
|
// SignPsbt expects a partial transaction with all inputs and outputs fully
|
|
// declared and tries to sign all unsigned inputs that have all required fields
|
|
// (UTXO information, BIP32 derivation information, witness or sig scripts) set.
|
|
// If no error is returned, the PSBT is ready to be given to the next signer or
|
|
// to be finalized if lnd was the last signer.
|
|
//
|
|
// NOTE: This method only signs inputs (and only those it can sign), it does not
|
|
// perform any other tasks (such as coin selection, UTXO locking or
|
|
// input/output/fee value validation, PSBT finalization). Any input that is
|
|
// incomplete will be skipped.
|
|
func (b *BtcWallet) SignPsbt(packet *psbt.Packet) ([]uint32, error) {
|
|
// In signedInputs we return the indices of psbt inputs that were signed
|
|
// by our wallet. This way the caller can check if any inputs were signed.
|
|
var signedInputs []uint32
|
|
|
|
// Let's check that this is actually something we can and want to sign.
|
|
// We need at least one input and one output. In addition each
|
|
// input needs nonWitness Utxo or witness Utxo data specified.
|
|
err := psbt.InputsReadyToSign(packet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Go through each input that doesn't have final witness data attached
|
|
// to it already and try to sign it. If there is nothing more to sign or
|
|
// 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
|
|
prevOutputFetcher := wallet.PsbtPrevOutputFetcher(packet)
|
|
sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)
|
|
for idx := range tx.TxIn {
|
|
in := &packet.Inputs[idx]
|
|
|
|
// We can only sign if we have UTXO information available. Since
|
|
// we don't finalize, we just skip over any input that we know
|
|
// we can't do anything with. Since we only support signing
|
|
// witness inputs, we only look at the witness UTXO being set.
|
|
if in.WitnessUtxo == nil {
|
|
continue
|
|
}
|
|
|
|
// Skip this input if it's got final witness data attached.
|
|
if len(in.FinalScriptWitness) > 0 {
|
|
continue
|
|
}
|
|
|
|
// Skip this input if there is no BIP32 derivation info
|
|
// available.
|
|
if len(in.Bip32Derivation) == 0 {
|
|
continue
|
|
}
|
|
|
|
// TODO(guggero): For multisig, we'll need to find out what key
|
|
// to use and there should be multiple derivation paths in the
|
|
// BIP32 derivation field.
|
|
|
|
// Let's try and derive the key now. This method will decide if
|
|
// it's a BIP49/84 key for normal on-chain funds or a key of the
|
|
// custom purpose 1017 key scope.
|
|
derivationInfo := in.Bip32Derivation[0]
|
|
privKey, err := b.deriveKeyByBIP32Path(derivationInfo.Bip32Path)
|
|
if err != nil {
|
|
log.Warnf("SignPsbt: Skipping input %d, error "+
|
|
"deriving signing key: %v", idx, err)
|
|
continue
|
|
}
|
|
|
|
// We need to make sure we actually derived the key that was
|
|
// expected to be derived.
|
|
pubKeysEqual := bytes.Equal(
|
|
derivationInfo.PubKey,
|
|
privKey.PubKey().SerializeCompressed(),
|
|
)
|
|
if !pubKeysEqual {
|
|
log.Warnf("SignPsbt: Skipping input %d, derived "+
|
|
"public key %x does not match bip32 "+
|
|
"derivation info public key %x", idx,
|
|
privKey.PubKey().SerializeCompressed(),
|
|
derivationInfo.PubKey)
|
|
continue
|
|
}
|
|
|
|
// Do we need to tweak anything? Single or double tweaks are
|
|
// sent as custom/proprietary fields in the PSBT input section.
|
|
privKey = maybeTweakPrivKeyPsbt(in.Unknowns, privKey)
|
|
|
|
// What kind of signature is expected from us and do we have all
|
|
// information we need?
|
|
signMethod, err := validateSigningMethod(in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch signMethod {
|
|
// For p2wkh, np2wkh and p2wsh.
|
|
case input.WitnessV0SignMethod:
|
|
err = signSegWitV0(in, tx, sigHashes, idx, privKey)
|
|
|
|
// For p2tr BIP0086 key spend only.
|
|
case input.TaprootKeySpendBIP0086SignMethod:
|
|
rootHash := make([]byte, 0)
|
|
err = signSegWitV1KeySpend(
|
|
in, tx, sigHashes, idx, privKey, rootHash,
|
|
)
|
|
|
|
// For p2tr with script commitment key spend path.
|
|
case input.TaprootKeySpendSignMethod:
|
|
rootHash := in.TaprootMerkleRoot
|
|
err = signSegWitV1KeySpend(
|
|
in, tx, sigHashes, idx, privKey, rootHash,
|
|
)
|
|
|
|
// For p2tr script spend path.
|
|
case input.TaprootScriptSpendSignMethod:
|
|
leafScript := in.TaprootLeafScript[0]
|
|
leaf := txscript.TapLeaf{
|
|
LeafVersion: leafScript.LeafVersion,
|
|
Script: leafScript.Script,
|
|
}
|
|
err = signSegWitV1ScriptSpend(
|
|
in, tx, sigHashes, idx, privKey, leaf,
|
|
)
|
|
|
|
default:
|
|
err = fmt.Errorf("unsupported signing method for "+
|
|
"PSBT signing: %v", signMethod)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
signedInputs = append(signedInputs, uint32(idx))
|
|
}
|
|
return signedInputs, nil
|
|
}
|
|
|
|
// validateSigningMethod attempts to detect the signing method that is required
|
|
// to sign for the given PSBT input and makes sure all information is available
|
|
// to do so.
|
|
func validateSigningMethod(in *psbt.PInput) (input.SignMethod, error) {
|
|
script, err := txscript.ParsePkScript(in.WitnessUtxo.PkScript)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("error detecting signing method, "+
|
|
"couldn't parse pkScript: %v", err)
|
|
}
|
|
|
|
switch script.Class() {
|
|
case txscript.WitnessV0PubKeyHashTy, txscript.ScriptHashTy,
|
|
txscript.WitnessV0ScriptHashTy:
|
|
|
|
return input.WitnessV0SignMethod, nil
|
|
|
|
case txscript.WitnessV1TaprootTy:
|
|
if len(in.TaprootBip32Derivation) == 0 {
|
|
return 0, fmt.Errorf("cannot sign for taproot input " +
|
|
"without taproot BIP0032 derivation info")
|
|
}
|
|
|
|
// Currently, we only support creating one signature per input.
|
|
//
|
|
// TODO(guggero): Should we support signing multiple paths at
|
|
// the same time? What are the performance and security
|
|
// implications?
|
|
if len(in.TaprootBip32Derivation) > 1 {
|
|
return 0, fmt.Errorf("unsupported multiple taproot " +
|
|
"BIP0032 derivation info found, can only " +
|
|
"sign for one at a time")
|
|
}
|
|
|
|
derivation := in.TaprootBip32Derivation[0]
|
|
switch {
|
|
// No leaf hashes means this is the internal key we're signing
|
|
// with, so it's a key spend. And no merkle root means this is
|
|
// a BIP0086 output we're signing for.
|
|
case len(derivation.LeafHashes) == 0 &&
|
|
len(in.TaprootMerkleRoot) == 0:
|
|
|
|
return input.TaprootKeySpendBIP0086SignMethod, nil
|
|
|
|
// A non-empty merkle root means we committed to a taproot hash
|
|
// that we need to use in the tap tweak.
|
|
case len(derivation.LeafHashes) == 0:
|
|
// Getting here means the merkle root isn't empty, but
|
|
// is it exactly the length we need?
|
|
if len(in.TaprootMerkleRoot) != sha256.Size {
|
|
return 0, fmt.Errorf("invalid taproot merkle "+
|
|
"root length, got %d expected %d",
|
|
len(in.TaprootMerkleRoot), sha256.Size)
|
|
}
|
|
|
|
return input.TaprootKeySpendSignMethod, nil
|
|
|
|
// Currently, we only support signing for one leaf at a time.
|
|
//
|
|
// TODO(guggero): Should we support signing multiple paths at
|
|
// the same time? What are the performance and security
|
|
// implications?
|
|
case len(derivation.LeafHashes) == 1:
|
|
// If we're supposed to be signing for a leaf hash, we
|
|
// also expect the leaf script that hashes to that hash
|
|
// in the appropriate field.
|
|
if len(in.TaprootLeafScript) != 1 {
|
|
return 0, fmt.Errorf("specified leaf hash in " +
|
|
"taproot BIP0032 derivation but " +
|
|
"missing taproot leaf script")
|
|
}
|
|
|
|
leafScript := in.TaprootLeafScript[0]
|
|
leaf := txscript.TapLeaf{
|
|
LeafVersion: leafScript.LeafVersion,
|
|
Script: leafScript.Script,
|
|
}
|
|
leafHash := leaf.TapHash()
|
|
if !bytes.Equal(leafHash[:], derivation.LeafHashes[0]) {
|
|
return 0, fmt.Errorf("specified leaf hash in" +
|
|
"taproot BIP0032 derivation but " +
|
|
"corresponding taproot leaf script " +
|
|
"was not found")
|
|
}
|
|
|
|
return input.TaprootScriptSpendSignMethod, nil
|
|
|
|
default:
|
|
return 0, fmt.Errorf("unsupported number of leaf " +
|
|
"hashes in taproot BIP0032 derivation info, " +
|
|
"can only sign for one at a time")
|
|
}
|
|
|
|
default:
|
|
return 0, fmt.Errorf("unsupported script class for signing "+
|
|
"PSBT: %v", script.Class())
|
|
}
|
|
}
|
|
|
|
// EstimateInputWeight estimates the weight of a PSBT input and adds it to the
|
|
// passed in TxWeightEstimator. It returns an error if the input type is
|
|
// unknown or unsupported. Only inputs that have a known witness size are
|
|
// supported, which is P2WKH, NP2WKH and P2TR (key spend path).
|
|
func EstimateInputWeight(in *psbt.PInput, w *input.TxWeightEstimator) error {
|
|
if in.WitnessUtxo == nil {
|
|
return ErrInputMissingUTXOInfo
|
|
}
|
|
|
|
pkScript := in.WitnessUtxo.PkScript
|
|
switch {
|
|
case txscript.IsPayToScriptHash(pkScript):
|
|
w.AddNestedP2WKHInput()
|
|
|
|
case txscript.IsPayToWitnessPubKeyHash(pkScript):
|
|
w.AddP2WKHInput()
|
|
|
|
case txscript.IsPayToWitnessScriptHash(pkScript):
|
|
return fmt.Errorf("P2WSH inputs are not supported, cannot "+
|
|
"estimate witness size for script spend: %w",
|
|
ErrScriptSpendFeeEstimationUnsupported)
|
|
|
|
case txscript.IsPayToTaproot(pkScript):
|
|
signMethod, err := validateSigningMethod(in)
|
|
if err != nil {
|
|
return fmt.Errorf("error determining p2tr signing "+
|
|
"method: %w", err)
|
|
}
|
|
|
|
switch signMethod {
|
|
// For p2tr key spend paths.
|
|
case input.TaprootKeySpendBIP0086SignMethod,
|
|
input.TaprootKeySpendSignMethod:
|
|
|
|
w.AddTaprootKeySpendInput(in.SighashType)
|
|
|
|
// For p2tr script spend path.
|
|
case input.TaprootScriptSpendSignMethod:
|
|
return fmt.Errorf("P2TR inputs are not supported, "+
|
|
"cannot estimate witness size for script "+
|
|
"spend: %w",
|
|
ErrScriptSpendFeeEstimationUnsupported)
|
|
|
|
default:
|
|
return fmt.Errorf("unsupported signing method for "+
|
|
"PSBT signing: %v", signMethod)
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("unknown input type for script %x: %w",
|
|
pkScript, ErrUnsupportedScript)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SignSegWitV0 attempts to generate a signature for a SegWit version 0 input
|
|
// and stores it in the PartialSigs (and FinalScriptSig for np2wkh addresses)
|
|
// field.
|
|
func signSegWitV0(in *psbt.PInput, tx *wire.MsgTx,
|
|
sigHashes *txscript.TxSigHashes, idx int,
|
|
privKey *btcec.PrivateKey) error {
|
|
|
|
pubKeyBytes := privKey.PubKey().SerializeCompressed()
|
|
|
|
// Extract the correct witness and/or legacy scripts now, depending on
|
|
// the type of input we sign. The txscript package has the peculiar
|
|
// requirement that the PkScript of a P2PKH must be given as the witness
|
|
// script in order for it to arrive at the correct sighash. That's why
|
|
// we call it subScript here instead of witness script.
|
|
subScript := prepareScriptsV0(in)
|
|
|
|
// We have everything we need for signing the input now.
|
|
sig, err := txscript.RawTxInWitnessSignature(
|
|
tx, sigHashes, idx, in.WitnessUtxo.Value, subScript,
|
|
in.SighashType, privKey,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("error signing input %d: %w", idx, err)
|
|
}
|
|
in.PartialSigs = append(in.PartialSigs, &psbt.PartialSig{
|
|
PubKey: pubKeyBytes,
|
|
Signature: sig,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// signSegWitV1KeySpend attempts to generate a signature for a SegWit version 1
|
|
// (p2tr) input and stores it in the TaprootKeySpendSig field.
|
|
func signSegWitV1KeySpend(in *psbt.PInput, tx *wire.MsgTx,
|
|
sigHashes *txscript.TxSigHashes, idx int, privKey *btcec.PrivateKey,
|
|
tapscriptRootHash []byte) error {
|
|
|
|
rawSig, err := txscript.RawTxInTaprootSignature(
|
|
tx, sigHashes, idx, in.WitnessUtxo.Value,
|
|
in.WitnessUtxo.PkScript, tapscriptRootHash, in.SighashType,
|
|
privKey,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("error signing taproot input %d: %w", idx,
|
|
err)
|
|
}
|
|
|
|
in.TaprootKeySpendSig = rawSig
|
|
|
|
return nil
|
|
}
|
|
|
|
// signSegWitV1ScriptSpend attempts to generate a signature for a SegWit version
|
|
// 1 (p2tr) input and stores it in the TaprootScriptSpendSig field.
|
|
func signSegWitV1ScriptSpend(in *psbt.PInput, tx *wire.MsgTx,
|
|
sigHashes *txscript.TxSigHashes, idx int, privKey *btcec.PrivateKey,
|
|
leaf txscript.TapLeaf) error {
|
|
|
|
rawSig, err := txscript.RawTxInTapscriptSignature(
|
|
tx, sigHashes, idx, in.WitnessUtxo.Value,
|
|
in.WitnessUtxo.PkScript, leaf, in.SighashType, privKey,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("error signing taproot script input %d: %w",
|
|
idx, err)
|
|
}
|
|
|
|
leafHash := leaf.TapHash()
|
|
in.TaprootScriptSpendSig = append(
|
|
in.TaprootScriptSpendSig, &psbt.TaprootScriptSpendSig{
|
|
XOnlyPubKey: in.TaprootBip32Derivation[0].XOnlyPubKey,
|
|
LeafHash: leafHash[:],
|
|
// We snip off the sighash flag from the end (if it was
|
|
// specified in the first place.)
|
|
Signature: rawSig[:schnorr.SignatureSize],
|
|
SigHash: in.SighashType,
|
|
},
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// prepareScriptsV0 returns the appropriate witness v0 and/or legacy scripts,
|
|
// depending on the type of input that should be signed.
|
|
func prepareScriptsV0(in *psbt.PInput) []byte {
|
|
switch {
|
|
// It's a NP2WKH input:
|
|
case len(in.RedeemScript) > 0:
|
|
return in.RedeemScript
|
|
|
|
// It's a P2WSH input:
|
|
case len(in.WitnessScript) > 0:
|
|
return in.WitnessScript
|
|
|
|
// It's a P2WKH input:
|
|
default:
|
|
return in.WitnessUtxo.PkScript
|
|
}
|
|
}
|
|
|
|
// maybeTweakPrivKeyPsbt examines if there are any tweak parameters given in the
|
|
// custom/proprietary PSBT fields and may perform a mapping on the passed
|
|
// private key in order to utilize the tweaks, if populated.
|
|
func maybeTweakPrivKeyPsbt(unknowns []*psbt.Unknown,
|
|
privKey *btcec.PrivateKey) *btcec.PrivateKey {
|
|
|
|
// There can be other custom/unknown keys in a PSBT that we just ignore.
|
|
// Key tweaking is optional and only one tweak (single _or_ double) can
|
|
// ever be applied (at least for any use cases described in the BOLT
|
|
// spec).
|
|
for _, u := range unknowns {
|
|
if bytes.Equal(u.Key, PsbtKeyTypeInputSignatureTweakSingle) {
|
|
return input.TweakPrivKey(privKey, u.Value)
|
|
}
|
|
|
|
if bytes.Equal(u.Key, PsbtKeyTypeInputSignatureTweakDouble) {
|
|
doubleTweakKey, _ := btcec.PrivKeyFromBytes(
|
|
u.Value,
|
|
)
|
|
return input.DeriveRevocationPrivKey(
|
|
privKey, doubleTweakKey,
|
|
)
|
|
}
|
|
}
|
|
|
|
return privKey
|
|
}
|
|
|
|
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
|
|
// declared and tries to sign all inputs that belong to the specified account.
|
|
// Lnd must be the last signer of the transaction. That means, if there are any
|
|
// unsigned non-witness inputs or inputs without UTXO information attached or
|
|
// inputs without witness data that do not belong to lnd's wallet, this method
|
|
// will fail. If no error is returned, the PSBT is ready to be extracted and the
|
|
// final TX within to be broadcast.
|
|
//
|
|
// NOTE: This method does NOT publish the transaction after it's been
|
|
// finalized successfully.
|
|
//
|
|
// This is a part of the WalletController interface.
|
|
func (b *BtcWallet) FinalizePsbt(packet *psbt.Packet, accountName string) error {
|
|
var (
|
|
keyScope *waddrmgr.KeyScope
|
|
accountNum uint32
|
|
)
|
|
switch accountName {
|
|
// If the default/imported account name was specified, we'll provide a
|
|
// nil key scope to FundPsbt, allowing it to sign inputs from both key
|
|
// scopes (NP2WKH, P2WKH).
|
|
case lnwallet.DefaultAccountName:
|
|
accountNum = defaultAccount
|
|
|
|
case waddrmgr.ImportedAddrAccountName:
|
|
accountNum = importedAccount
|
|
|
|
// Otherwise, map the account name to its key scope and internal account
|
|
// number to determine if the inputs belonging to this account should be
|
|
// signed.
|
|
default:
|
|
scope, account, err := b.lookupFirstCustomAccount(accountName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyScope = &scope
|
|
accountNum = account
|
|
}
|
|
|
|
return b.wallet.FinalizePsbt(keyScope, accountNum, packet)
|
|
}
|
|
|
|
// DecorateInputs fetches the UTXO information of all inputs it can identify and
|
|
// adds the required information to the package's inputs. The failOnUnknown
|
|
// boolean controls whether the method should return an error if it cannot
|
|
// identify an input or if it should just skip it.
|
|
//
|
|
// This is a part of the WalletController interface.
|
|
func (b *BtcWallet) DecorateInputs(packet *psbt.Packet,
|
|
failOnUnknown bool) error {
|
|
|
|
return b.wallet.DecorateInputs(packet, failOnUnknown)
|
|
}
|
|
|
|
// lookupFirstCustomAccount returns the first custom account found. In theory,
|
|
// there should be only one custom account for the given name. However, due to a
|
|
// lack of check, users could have created custom accounts with various key
|
|
// scopes. This behaviour has been fixed but, we still need to handle this
|
|
// specific case to avoid non-deterministic behaviour implied by LookupAccount.
|
|
func (b *BtcWallet) lookupFirstCustomAccount(
|
|
name string) (waddrmgr.KeyScope, uint32, error) {
|
|
|
|
var (
|
|
account *waddrmgr.AccountProperties
|
|
keyScope waddrmgr.KeyScope
|
|
)
|
|
for _, scope := range waddrmgr.DefaultKeyScopes {
|
|
var err error
|
|
account, err = b.wallet.AccountPropertiesByName(scope, name)
|
|
if waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return keyScope, 0, err
|
|
}
|
|
|
|
keyScope = scope
|
|
|
|
break
|
|
}
|
|
if account == nil {
|
|
return waddrmgr.KeyScope{}, 0, newAccountNotFoundError(name)
|
|
}
|
|
|
|
return keyScope, account.AccountNumber, nil
|
|
}
|
|
|
|
// Bip32DerivationFromKeyDesc returns the default and Taproot BIP-0032 key
|
|
// derivation information from the given key descriptor information.
|
|
func Bip32DerivationFromKeyDesc(keyDesc keychain.KeyDescriptor,
|
|
coinType uint32) (*psbt.Bip32Derivation, *psbt.TaprootBip32Derivation,
|
|
string) {
|
|
|
|
bip32Derivation := &psbt.Bip32Derivation{
|
|
PubKey: keyDesc.PubKey.SerializeCompressed(),
|
|
Bip32Path: []uint32{
|
|
keychain.BIP0043Purpose + hdkeychain.HardenedKeyStart,
|
|
coinType + hdkeychain.HardenedKeyStart,
|
|
uint32(keyDesc.Family) +
|
|
uint32(hdkeychain.HardenedKeyStart),
|
|
0,
|
|
keyDesc.Index,
|
|
},
|
|
}
|
|
|
|
derivationPath := fmt.Sprintf(
|
|
"m/%d'/%d'/%d'/%d/%d", keychain.BIP0043Purpose, coinType,
|
|
keyDesc.Family, 0, keyDesc.Index,
|
|
)
|
|
|
|
return bip32Derivation, &psbt.TaprootBip32Derivation{
|
|
XOnlyPubKey: bip32Derivation.PubKey[1:],
|
|
MasterKeyFingerprint: bip32Derivation.MasterKeyFingerprint,
|
|
Bip32Path: bip32Derivation.Bip32Path,
|
|
LeafHashes: make([][]byte, 0),
|
|
}, derivationPath
|
|
}
|
|
|
|
// Bip32DerivationFromAddress returns the default and Taproot BIP-0032 key
|
|
// derivation information from the given managed address.
|
|
func Bip32DerivationFromAddress(
|
|
addr waddrmgr.ManagedAddress) (*psbt.Bip32Derivation,
|
|
*psbt.TaprootBip32Derivation, string, error) {
|
|
|
|
pubKeyAddr, ok := addr.(waddrmgr.ManagedPubKeyAddress)
|
|
if !ok {
|
|
return nil, nil, "", fmt.Errorf("address is not a pubkey " +
|
|
"address")
|
|
}
|
|
|
|
scope, derivationInfo, haveInfo := pubKeyAddr.DerivationInfo()
|
|
if !haveInfo {
|
|
return nil, nil, "", fmt.Errorf("address is an imported " +
|
|
"public key, can't derive BIP32 path")
|
|
}
|
|
|
|
bip32Derivation := &psbt.Bip32Derivation{
|
|
PubKey: pubKeyAddr.PubKey().SerializeCompressed(),
|
|
Bip32Path: []uint32{
|
|
scope.Purpose + hdkeychain.HardenedKeyStart,
|
|
scope.Coin + hdkeychain.HardenedKeyStart,
|
|
derivationInfo.InternalAccount +
|
|
hdkeychain.HardenedKeyStart,
|
|
derivationInfo.Branch,
|
|
derivationInfo.Index,
|
|
},
|
|
}
|
|
|
|
derivationPath := fmt.Sprintf(
|
|
"m/%d'/%d'/%d'/%d/%d", scope.Purpose, scope.Coin,
|
|
derivationInfo.InternalAccount, derivationInfo.Branch,
|
|
derivationInfo.Index,
|
|
)
|
|
|
|
return bip32Derivation, &psbt.TaprootBip32Derivation{
|
|
XOnlyPubKey: bip32Derivation.PubKey[1:],
|
|
MasterKeyFingerprint: bip32Derivation.MasterKeyFingerprint,
|
|
Bip32Path: bip32Derivation.Bip32Path,
|
|
LeafHashes: make([][]byte, 0),
|
|
}, derivationPath, nil
|
|
}
|