2022-01-05 11:04:18 +01:00
|
|
|
package btcwallet
|
|
|
|
|
|
|
|
import (
|
2022-01-05 11:04:23 +01:00
|
|
|
"bytes"
|
2022-04-27 21:05:46 +02:00
|
|
|
"crypto/sha256"
|
2024-02-06 12:25:52 +01:00
|
|
|
"errors"
|
2022-01-05 11:04:23 +01:00
|
|
|
"fmt"
|
|
|
|
|
2022-02-23 14:48:00 +01:00
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
2022-04-27 21:05:47 +02:00
|
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
2022-02-23 14:48:00 +01:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2024-02-09 13:45:36 +01:00
|
|
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
2022-02-23 14:48:00 +01:00
|
|
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
2022-01-05 11:04:23 +01:00
|
|
|
"github.com/btcsuite/btcd/txscript"
|
2022-04-27 21:05:44 +02:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
2022-01-05 11:04:18 +01:00
|
|
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
2022-03-18 18:37:44 +01:00
|
|
|
"github.com/btcsuite/btcwallet/wallet"
|
2022-01-05 11:04:23 +01:00
|
|
|
"github.com/lightningnetwork/lnd/input"
|
2024-02-09 13:45:36 +01:00
|
|
|
"github.com/lightningnetwork/lnd/keychain"
|
2022-01-05 11:04:18 +01:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
|
|
)
|
|
|
|
|
2022-01-05 11:04:23 +01:00
|
|
|
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}
|
2024-02-06 12:25:52 +01:00
|
|
|
|
|
|
|
// 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")
|
2022-01-05 11:04:23 +01:00
|
|
|
)
|
|
|
|
|
2022-01-05 11:04:18 +01:00
|
|
|
// 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
|
2023-02-02 13:40:02 +01:00
|
|
|
// 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.
|
2022-01-05 11:04:18 +01:00
|
|
|
//
|
|
|
|
// 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,
|
2023-02-02 13:40:02 +01:00
|
|
|
feeRate chainfee.SatPerKWeight, accountName string,
|
2024-03-26 18:08:27 +01:00
|
|
|
changeScope *waddrmgr.KeyScope,
|
|
|
|
strategy wallet.CoinSelectionStrategy) (int32, error) {
|
2022-01-05 11:04:18 +01:00
|
|
|
|
|
|
|
// 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
|
|
|
|
)
|
2023-02-02 13:40:02 +01:00
|
|
|
|
2022-01-05 11:04:18 +01:00
|
|
|
switch accountName {
|
2023-02-02 13:40:02 +01:00
|
|
|
// For default accounts and single imported public keys, we'll provide a
|
2023-09-20 17:37:32 +02:00
|
|
|
// nil key scope to FundPsbt, allowing it to select inputs from all
|
2023-02-02 13:40:02 +01:00
|
|
|
// scopes (NP2WKH, P2WKH, P2TR). By default, the change key scope for
|
|
|
|
// these accounts will be P2WKH.
|
2022-01-05 11:04:18 +01:00
|
|
|
case lnwallet.DefaultAccountName:
|
2023-02-02 13:40:02 +01:00
|
|
|
if changeScope == nil {
|
|
|
|
changeScope = &waddrmgr.KeyScopeBIP0084
|
|
|
|
}
|
|
|
|
|
2022-01-05 11:04:18 +01:00
|
|
|
accountNum = defaultAccount
|
|
|
|
|
|
|
|
case waddrmgr.ImportedAddrAccountName:
|
2023-02-02 13:40:02 +01:00
|
|
|
if changeScope == nil {
|
|
|
|
changeScope = &waddrmgr.KeyScopeBIP0084
|
|
|
|
}
|
|
|
|
|
2022-01-05 11:04:18 +01:00
|
|
|
accountNum = importedAccount
|
|
|
|
|
|
|
|
// Otherwise, map the account name to its key scope and internal account
|
2023-02-02 13:40:02 +01:00
|
|
|
// 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.
|
2022-01-05 11:04:18 +01:00
|
|
|
default:
|
2023-02-02 13:40:02 +01:00
|
|
|
if changeScope != nil {
|
|
|
|
return 0, fmt.Errorf("couldn't select a " +
|
|
|
|
"custom change type for custom accounts")
|
|
|
|
}
|
|
|
|
|
2023-03-06 18:19:18 +01:00
|
|
|
scope, account, err := b.lookupFirstCustomAccount(accountName)
|
2022-01-05 11:04:18 +01:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
keyScope = &scope
|
2023-02-02 13:40:02 +01:00
|
|
|
changeScope = keyScope
|
2022-01-05 11:04:18 +01:00
|
|
|
accountNum = account
|
|
|
|
}
|
|
|
|
|
2023-02-02 13:40:02 +01:00
|
|
|
var opts []wallet.TxCreateOption
|
|
|
|
if changeScope != nil {
|
|
|
|
opts = append(opts, wallet.WithCustomChangeScope(changeScope))
|
|
|
|
}
|
|
|
|
|
2022-01-05 11:04:18 +01:00
|
|
|
// 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,
|
2024-03-26 18:08:27 +01:00
|
|
|
strategy, opts...,
|
2022-01-05 11:04:18 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-01-05 11:04:23 +01:00
|
|
|
// 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.
|
2022-08-17 01:31:06 +02:00
|
|
|
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
|
|
|
|
|
2022-01-05 11:04:23 +01:00
|
|
|
// Let's check that this is actually something we can and want to sign.
|
2023-03-20 22:43:15 +01:00
|
|
|
// 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)
|
2022-01-05 11:04:23 +01:00
|
|
|
if err != nil {
|
2022-08-17 01:31:06 +02:00
|
|
|
return nil, err
|
2022-01-05 11:04:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2022-03-18 18:37:44 +01:00
|
|
|
prevOutputFetcher := wallet.PsbtPrevOutputFetcher(packet)
|
|
|
|
sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)
|
2022-01-05 11:04:23 +01:00
|
|
|
for idx := range tx.TxIn {
|
2022-04-27 21:05:44 +02:00
|
|
|
in := &packet.Inputs[idx]
|
2022-01-05 11:04:23 +01:00
|
|
|
|
|
|
|
// 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)
|
2022-04-27 21:05:44 +02:00
|
|
|
|
|
|
|
// What kind of signature is expected from us and do we have all
|
|
|
|
// information we need?
|
|
|
|
signMethod, err := validateSigningMethod(in)
|
2022-01-05 11:04:23 +01:00
|
|
|
if err != nil {
|
2022-08-17 01:31:06 +02:00
|
|
|
return nil, err
|
2022-01-05 11:04:23 +01:00
|
|
|
}
|
|
|
|
|
2022-04-27 21:05:44 +02:00
|
|
|
switch signMethod {
|
|
|
|
// For p2wkh, np2wkh and p2wsh.
|
|
|
|
case input.WitnessV0SignMethod:
|
|
|
|
err = signSegWitV0(in, tx, sigHashes, idx, privKey)
|
|
|
|
|
2022-04-27 21:05:46 +02:00
|
|
|
// 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,
|
|
|
|
)
|
|
|
|
|
2022-04-27 21:05:47 +02:00
|
|
|
// 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,
|
|
|
|
)
|
|
|
|
|
2022-04-27 21:05:44 +02:00
|
|
|
default:
|
|
|
|
err = fmt.Errorf("unsupported signing method for "+
|
|
|
|
"PSBT signing: %v", signMethod)
|
|
|
|
}
|
2022-01-05 11:04:23 +01:00
|
|
|
if err != nil {
|
2022-08-17 01:31:06 +02:00
|
|
|
return nil, err
|
2022-01-05 11:04:23 +01:00
|
|
|
}
|
2022-08-17 01:31:06 +02:00
|
|
|
signedInputs = append(signedInputs, uint32(idx))
|
2022-01-05 11:04:23 +01:00
|
|
|
}
|
2022-08-17 01:31:06 +02:00
|
|
|
return signedInputs, nil
|
2022-01-05 11:04:23 +01:00
|
|
|
}
|
|
|
|
|
2022-04-27 21:05:44 +02:00
|
|
|
// 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
|
|
|
|
|
2022-04-27 21:05:46 +02:00
|
|
|
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:
|
2022-04-27 21:05:47 +02:00
|
|
|
// 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")
|
|
|
|
}
|
|
|
|
|
2022-04-27 21:05:46 +02:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2022-04-27 21:05:44 +02:00
|
|
|
default:
|
|
|
|
return 0, fmt.Errorf("unsupported script class for signing "+
|
|
|
|
"PSBT: %v", script.Class())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-06 12:25:52 +01:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2022-04-27 21:05:44 +02:00
|
|
|
// 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.
|
2022-06-29 18:19:08 +02:00
|
|
|
subScript := prepareScriptsV0(in)
|
2022-04-27 21:05:44 +02:00
|
|
|
|
|
|
|
// 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 {
|
2024-03-07 13:19:28 +01:00
|
|
|
return fmt.Errorf("error signing input %d: %w", idx, err)
|
2022-04-27 21:05:44 +02:00
|
|
|
}
|
|
|
|
in.PartialSigs = append(in.PartialSigs, &psbt.PartialSig{
|
|
|
|
PubKey: pubKeyBytes,
|
|
|
|
Signature: sig,
|
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-27 21:05:46 +02:00
|
|
|
// 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 {
|
2024-03-07 13:19:28 +01:00
|
|
|
return fmt.Errorf("error signing taproot input %d: %w", idx,
|
2022-04-27 21:05:46 +02:00
|
|
|
err)
|
|
|
|
}
|
|
|
|
|
|
|
|
in.TaprootKeySpendSig = rawSig
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-27 21:05:47 +02:00
|
|
|
// 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 {
|
2024-03-07 13:19:28 +01:00
|
|
|
return fmt.Errorf("error signing taproot script input %d: %w",
|
2022-04-27 21:05:47 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-04-27 21:05:44 +02:00
|
|
|
// prepareScriptsV0 returns the appropriate witness v0 and/or legacy scripts,
|
2022-01-05 11:04:23 +01:00
|
|
|
// depending on the type of input that should be signed.
|
2022-06-29 18:19:08 +02:00
|
|
|
func prepareScriptsV0(in *psbt.PInput) []byte {
|
2022-01-05 11:04:23 +01:00
|
|
|
switch {
|
|
|
|
// It's a NP2WKH input:
|
|
|
|
case len(in.RedeemScript) > 0:
|
2022-06-29 18:19:08 +02:00
|
|
|
return in.RedeemScript
|
2022-01-05 11:04:23 +01:00
|
|
|
|
|
|
|
// It's a P2WSH input:
|
|
|
|
case len(in.WitnessScript) > 0:
|
2022-06-29 18:19:08 +02:00
|
|
|
return in.WitnessScript
|
2022-01-05 11:04:23 +01:00
|
|
|
|
|
|
|
// It's a P2WKH input:
|
|
|
|
default:
|
2022-06-29 18:19:08 +02:00
|
|
|
return in.WitnessUtxo.PkScript
|
2022-01-05 11:04:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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(
|
2022-02-23 14:48:00 +01:00
|
|
|
u.Value,
|
2022-01-05 11:04:23 +01:00
|
|
|
)
|
|
|
|
return input.DeriveRevocationPrivKey(
|
|
|
|
privKey, doubleTweakKey,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return privKey
|
|
|
|
}
|
|
|
|
|
2022-01-05 11:04:18 +01:00
|
|
|
// 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:
|
2023-03-06 18:19:18 +01:00
|
|
|
scope, account, err := b.lookupFirstCustomAccount(accountName)
|
2022-01-05 11:04:18 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
keyScope = &scope
|
|
|
|
accountNum = account
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.wallet.FinalizePsbt(keyScope, accountNum, packet)
|
|
|
|
}
|
2023-03-06 18:19:18 +01:00
|
|
|
|
2024-02-06 12:25:54 +01:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2023-03-06 18:19:18 +01:00
|
|
|
// 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
|
|
|
|
}
|
2024-02-09 13:45:36 +01:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|