mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-24 15:52:22 +02:00
btcwallet: add SignPsbt
This commit is contained in:
parent
c24763b3da
commit
d135b638f6
@ -189,6 +189,11 @@ func (w *WalletController) FundPsbt(*psbt.Packet, int32, chainfee.SatPerKWeight,
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignPsbt currently does nothing.
|
||||||
|
func (w *WalletController) SignPsbt(*psbt.Packet) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// FinalizePsbt currently does nothing.
|
// FinalizePsbt currently does nothing.
|
||||||
func (w *WalletController) FinalizePsbt(_ *psbt.Packet, _ string) error {
|
func (w *WalletController) FinalizePsbt(_ *psbt.Packet, _ string) error {
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,13 +1,33 @@
|
|||||||
package btcwallet
|
package btcwallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/btcsuite/btcutil/psbt"
|
"github.com/btcsuite/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"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}
|
||||||
|
)
|
||||||
|
|
||||||
// FundPsbt creates a fully populated PSBT packet that contains enough inputs to
|
// 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
|
// 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
|
// rate. If there is change left, a change output from the internal wallet is
|
||||||
@ -64,6 +84,177 @@ func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) error {
|
||||||
|
// Let's check that this is actually something we can and want to sign.
|
||||||
|
// We need at least one input and one output.
|
||||||
|
err := psbt.VerifyInputOutputLen(packet, true, true)
|
||||||
|
if err != nil {
|
||||||
|
return 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
|
||||||
|
sigHashes := txscript.NewTxSigHashes(tx)
|
||||||
|
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)
|
||||||
|
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, scriptSig, err := prepareScripts(in)
|
||||||
|
if err != nil {
|
||||||
|
// We derived the correct key so we _are_ expected to
|
||||||
|
// sign this. Not being able to sign at this point means
|
||||||
|
// there's something wrong.
|
||||||
|
return fmt.Errorf("error deriving script for input "+
|
||||||
|
"%d: %v", idx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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: %v", idx,
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
packet.Inputs[idx].FinalScriptSig = scriptSig
|
||||||
|
packet.Inputs[idx].PartialSigs = append(
|
||||||
|
packet.Inputs[idx].PartialSigs, &psbt.PartialSig{
|
||||||
|
PubKey: pubKeyBytes,
|
||||||
|
Signature: sig,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareScripts returns the appropriate witness and/or legacy scripts,
|
||||||
|
// depending on the type of input that should be signed.
|
||||||
|
func prepareScripts(in psbt.PInput) ([]byte, []byte, error) {
|
||||||
|
switch {
|
||||||
|
// It's a NP2WKH input:
|
||||||
|
case len(in.RedeemScript) > 0:
|
||||||
|
builder := txscript.NewScriptBuilder()
|
||||||
|
builder.AddData(in.RedeemScript)
|
||||||
|
sigScript, err := builder.Script()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error building np2wkh "+
|
||||||
|
"script: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return in.RedeemScript, sigScript, nil
|
||||||
|
|
||||||
|
// It's a P2WSH input:
|
||||||
|
case len(in.WitnessScript) > 0:
|
||||||
|
return in.WitnessScript, nil, nil
|
||||||
|
|
||||||
|
// It's a P2WKH input:
|
||||||
|
default:
|
||||||
|
return in.WitnessUtxo.PkScript, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
btcec.S256(), u.Value,
|
||||||
|
)
|
||||||
|
return input.DeriveRevocationPrivKey(
|
||||||
|
privKey, doubleTweakKey,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return privKey
|
||||||
|
}
|
||||||
|
|
||||||
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
|
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
|
||||||
// declared and tries to sign all inputs that belong to the specified account.
|
// 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
|
// Lnd must be the last signer of the transaction. That means, if there are any
|
||||||
|
337
lnwallet/btcwallet/psbt_test.go
Normal file
337
lnwallet/btcwallet/psbt_test.go
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
package btcwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/btcsuite/btcutil/psbt"
|
||||||
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
netParams = &chaincfg.RegressionNetParams
|
||||||
|
testValue int64 = 345678
|
||||||
|
testCSVTimeout uint32 = 2016
|
||||||
|
|
||||||
|
testCommitSecretBytes, _ = hex.DecodeString(
|
||||||
|
"9f1f0db609718cf70c580aec6a0e570c3f086ec85a2a6119295b1d64240d" +
|
||||||
|
"aca5",
|
||||||
|
)
|
||||||
|
testCommitSecret, testCommitPoint = btcec.PrivKeyFromBytes(
|
||||||
|
btcec.S256(), testCommitSecretBytes,
|
||||||
|
)
|
||||||
|
|
||||||
|
remoteRevocationBasePubKeyBytes, _ = hex.DecodeString(
|
||||||
|
"02baf067bfd1a6cf7229c7c459b106d384ad33e948ea1d561f2034475ff1" +
|
||||||
|
"7359fb",
|
||||||
|
)
|
||||||
|
remoteRevocationBasePubKey, _ = btcec.ParsePubKey(
|
||||||
|
remoteRevocationBasePubKeyBytes, btcec.S256(),
|
||||||
|
)
|
||||||
|
|
||||||
|
testTweakSingle, _ = hex.DecodeString(
|
||||||
|
"020143a30cf6b71ca2af01efbd1758a04b4c7f5c2299f2ea63a8a6b58107" +
|
||||||
|
"63b1ed",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// testInputType is a type that represents different types of inputs that are
|
||||||
|
// signed within a PSBT.
|
||||||
|
type testInputType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
plainP2WKH testInputType = 0
|
||||||
|
tweakedP2WKH testInputType = 1
|
||||||
|
nestedP2WKH testInputType = 2
|
||||||
|
singleKeyP2WSH testInputType = 3
|
||||||
|
singleKeyDoubleTweakedP2WSH testInputType = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i testInputType) keyPath() []uint32 {
|
||||||
|
switch i {
|
||||||
|
case nestedP2WKH:
|
||||||
|
return []uint32{
|
||||||
|
hardenedKey(waddrmgr.KeyScopeBIP0049Plus.Purpose),
|
||||||
|
hardenedKey(0),
|
||||||
|
hardenedKey(0),
|
||||||
|
0, 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
case singleKeyP2WSH:
|
||||||
|
return []uint32{
|
||||||
|
hardenedKey(keychain.BIP0043Purpose),
|
||||||
|
hardenedKey(netParams.HDCoinType),
|
||||||
|
hardenedKey(uint32(keychain.KeyFamilyPaymentBase)),
|
||||||
|
0, 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
case singleKeyDoubleTweakedP2WSH:
|
||||||
|
return []uint32{
|
||||||
|
hardenedKey(keychain.BIP0043Purpose),
|
||||||
|
hardenedKey(netParams.HDCoinType),
|
||||||
|
hardenedKey(uint32(keychain.KeyFamilyDelayBase)),
|
||||||
|
0, 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return []uint32{
|
||||||
|
hardenedKey(waddrmgr.KeyScopeBIP0084.Purpose),
|
||||||
|
hardenedKey(0),
|
||||||
|
hardenedKey(0),
|
||||||
|
0, 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i testInputType) output(t *testing.T,
|
||||||
|
privKey *btcec.PrivateKey) (*wire.TxOut, []byte) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
addr btcutil.Address
|
||||||
|
witnessScript []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch i {
|
||||||
|
case plainP2WKH:
|
||||||
|
h := btcutil.Hash160(privKey.PubKey().SerializeCompressed())
|
||||||
|
addr, err = btcutil.NewAddressWitnessPubKeyHash(h, netParams)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
case tweakedP2WKH:
|
||||||
|
privKey = input.TweakPrivKey(privKey, testTweakSingle)
|
||||||
|
|
||||||
|
h := btcutil.Hash160(privKey.PubKey().SerializeCompressed())
|
||||||
|
addr, err = btcutil.NewAddressWitnessPubKeyHash(h, netParams)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
case nestedP2WKH:
|
||||||
|
h := btcutil.Hash160(privKey.PubKey().SerializeCompressed())
|
||||||
|
witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash(
|
||||||
|
h, netParams,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
witnessProgram, err := txscript.PayToAddrScript(witnessAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
addr, err = btcutil.NewAddressScriptHash(
|
||||||
|
witnessProgram, netParams,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
case singleKeyP2WSH:
|
||||||
|
// We're simulating a delay-to-self script which we're going to
|
||||||
|
// spend through the time lock path. We don't actually need to
|
||||||
|
// know the private key of the remote revocation base key.
|
||||||
|
revokeKey := input.DeriveRevocationPubkey(
|
||||||
|
remoteRevocationBasePubKey, testCommitPoint,
|
||||||
|
)
|
||||||
|
witnessScript, err = input.CommitScriptToSelf(
|
||||||
|
testCSVTimeout, privKey.PubKey(), revokeKey,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
h := sha256.Sum256(witnessScript)
|
||||||
|
addr, err = btcutil.NewAddressWitnessScriptHash(h[:], netParams)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
case singleKeyDoubleTweakedP2WSH:
|
||||||
|
// We're simulating breaching a remote party's delay-to-self
|
||||||
|
// output which we're going to spend through the revocation
|
||||||
|
// path. In that case the self key is the other party's self key
|
||||||
|
// and, we only know the revocation base private key and commit
|
||||||
|
// secret.
|
||||||
|
revokeKey := input.DeriveRevocationPubkey(
|
||||||
|
privKey.PubKey(), testCommitPoint,
|
||||||
|
)
|
||||||
|
witnessScript, err = input.CommitScriptToSelf(
|
||||||
|
testCSVTimeout, remoteRevocationBasePubKey, revokeKey,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
h := sha256.Sum256(witnessScript)
|
||||||
|
addr, err = btcutil.NewAddressWitnessScriptHash(h[:], netParams)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Fatalf("invalid input type")
|
||||||
|
}
|
||||||
|
|
||||||
|
pkScript, err := txscript.PayToAddrScript(addr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return &wire.TxOut{
|
||||||
|
Value: testValue,
|
||||||
|
PkScript: pkScript,
|
||||||
|
}, witnessScript
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i testInputType) decorateInput(t *testing.T, privKey *btcec.PrivateKey,
|
||||||
|
in *psbt.PInput) {
|
||||||
|
|
||||||
|
switch i {
|
||||||
|
case tweakedP2WKH:
|
||||||
|
in.Unknowns = []*psbt.Unknown{{
|
||||||
|
Key: PsbtKeyTypeInputSignatureTweakSingle,
|
||||||
|
Value: testTweakSingle,
|
||||||
|
}}
|
||||||
|
|
||||||
|
case nestedP2WKH:
|
||||||
|
h := btcutil.Hash160(privKey.PubKey().SerializeCompressed())
|
||||||
|
witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash(
|
||||||
|
h, netParams,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
witnessProgram, err := txscript.PayToAddrScript(witnessAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
in.RedeemScript = witnessProgram
|
||||||
|
|
||||||
|
case singleKeyDoubleTweakedP2WSH:
|
||||||
|
in.Unknowns = []*psbt.Unknown{{
|
||||||
|
Key: PsbtKeyTypeInputSignatureTweakDouble,
|
||||||
|
Value: testCommitSecret.Serialize(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i testInputType) beforeFinalize(t *testing.T, packet *psbt.Packet) {
|
||||||
|
in := &packet.Inputs[0]
|
||||||
|
sigBytes := in.PartialSigs[0].Signature
|
||||||
|
pubKeyBytes := in.PartialSigs[0].PubKey
|
||||||
|
|
||||||
|
var witnessStack wire.TxWitness
|
||||||
|
switch i {
|
||||||
|
case singleKeyP2WSH:
|
||||||
|
witnessStack = make([][]byte, 3)
|
||||||
|
witnessStack[0] = sigBytes
|
||||||
|
witnessStack[1] = nil
|
||||||
|
witnessStack[2] = in.WitnessScript
|
||||||
|
|
||||||
|
case singleKeyDoubleTweakedP2WSH:
|
||||||
|
// Place a 1 as the first item in the evaluated witness stack to
|
||||||
|
// force script execution to the revocation clause.
|
||||||
|
witnessStack = make([][]byte, 3)
|
||||||
|
witnessStack[0] = sigBytes
|
||||||
|
witnessStack[1] = []byte{1}
|
||||||
|
witnessStack[2] = in.WitnessScript
|
||||||
|
|
||||||
|
default:
|
||||||
|
witnessStack = make([][]byte, 2)
|
||||||
|
witnessStack[0] = sigBytes
|
||||||
|
witnessStack[1] = pubKeyBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
in.FinalScriptWitness, err = serializeTxWitness(witnessStack)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// serializeTxWitness return the wire witness stack into raw bytes.
|
||||||
|
func serializeTxWitness(txWitness wire.TxWitness) ([]byte, error) {
|
||||||
|
var witnessBytes bytes.Buffer
|
||||||
|
err := psbt.WriteTxWitness(&witnessBytes, txWitness)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error serializing witness: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return witnessBytes.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSignPsbt tests the PSBT signing functionality.
|
||||||
|
func TestSignPsbt(t *testing.T) {
|
||||||
|
w, cleanup := newTestWallet(t, netParams, seedBytes)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
inputType testInputType
|
||||||
|
}{{
|
||||||
|
name: "plain P2WKH",
|
||||||
|
inputType: plainP2WKH,
|
||||||
|
}, {
|
||||||
|
name: "tweaked P2WKH",
|
||||||
|
inputType: tweakedP2WKH,
|
||||||
|
}, {
|
||||||
|
name: "nested P2WKH",
|
||||||
|
inputType: nestedP2WKH,
|
||||||
|
}, {
|
||||||
|
name: "single key P2WSH",
|
||||||
|
inputType: singleKeyP2WSH,
|
||||||
|
}, {
|
||||||
|
name: "single key double tweaked P2WSH",
|
||||||
|
inputType: singleKeyDoubleTweakedP2WSH,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
|
||||||
|
// This is the private key we're going to sign with.
|
||||||
|
privKey, err := w.deriveKeyByBIP32Path(tc.inputType.keyPath())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
txOut, witnessScript := tc.inputType.output(t, privKey)
|
||||||
|
|
||||||
|
// Create the reference transaction that has the input that is
|
||||||
|
// going to be spent by our PSBT.
|
||||||
|
refTx := wire.NewMsgTx(2)
|
||||||
|
refTx.AddTxIn(&wire.TxIn{})
|
||||||
|
refTx.AddTxOut(txOut)
|
||||||
|
|
||||||
|
// Create the unsigned spend transaction that is going to be the
|
||||||
|
// main content of our PSBT.
|
||||||
|
spendTx := wire.NewMsgTx(2)
|
||||||
|
spendTx.LockTime = testCSVTimeout
|
||||||
|
spendTx.AddTxIn(&wire.TxIn{
|
||||||
|
PreviousOutPoint: wire.OutPoint{
|
||||||
|
Hash: refTx.TxHash(),
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
Sequence: testCSVTimeout,
|
||||||
|
})
|
||||||
|
spendTx.AddTxOut(txOut)
|
||||||
|
|
||||||
|
// Convert it to a PSBT now and add all required signing
|
||||||
|
// metadata to it.
|
||||||
|
packet, err := psbt.NewFromUnsignedTx(spendTx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
packet.Inputs[0].WitnessScript = witnessScript
|
||||||
|
packet.Inputs[0].SighashType = txscript.SigHashAll
|
||||||
|
packet.Inputs[0].WitnessUtxo = refTx.TxOut[0]
|
||||||
|
packet.Inputs[0].Bip32Derivation = []*psbt.Bip32Derivation{{
|
||||||
|
PubKey: privKey.PubKey().SerializeCompressed(),
|
||||||
|
Bip32Path: tc.inputType.keyPath(),
|
||||||
|
}}
|
||||||
|
tc.inputType.decorateInput(t, privKey, &packet.Inputs[0])
|
||||||
|
|
||||||
|
// Let the wallet do its job. We expect to be the only signer
|
||||||
|
// for this PSBT, so we'll be able to finalize it later.
|
||||||
|
err = w.SignPsbt(packet)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// If the witness stack needs to be assembled, give the caller
|
||||||
|
// the option to do that now.
|
||||||
|
tc.inputType.beforeFinalize(t, packet)
|
||||||
|
|
||||||
|
finalTx, err := psbt.Extract(packet)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
vm, err := txscript.NewEngine(
|
||||||
|
refTx.TxOut[0].PkScript, finalTx, 0,
|
||||||
|
txscript.StandardVerifyFlags, nil, nil,
|
||||||
|
refTx.TxOut[0].Value,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, vm.Execute())
|
||||||
|
}
|
||||||
|
}
|
@ -364,6 +364,19 @@ type WalletController interface {
|
|||||||
FundPsbt(packet *psbt.Packet, minConfs int32,
|
FundPsbt(packet *psbt.Packet, minConfs int32,
|
||||||
feeRate chainfee.SatPerKWeight, account string) (int32, error)
|
feeRate chainfee.SatPerKWeight, account string) (int32, error)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
SignPsbt(packet *psbt.Packet) error
|
||||||
|
|
||||||
// FinalizePsbt expects a partial transaction with all inputs and
|
// FinalizePsbt expects a partial transaction with all inputs and
|
||||||
// outputs fully declared and tries to sign all inputs that belong to
|
// outputs fully declared and tries to sign all inputs that belong to
|
||||||
// the specified account. Lnd must be the last signer of the
|
// the specified account. Lnd must be the last signer of the
|
||||||
|
Loading…
x
Reference in New Issue
Block a user