From b9b4d4efe1b6830a5af83e05f1ebeffbe552dc99 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Mon, 29 Mar 2021 14:22:51 -0700 Subject: [PATCH] wallet: include redeem script for NP2WKH inputs in PSBT generation This allows external signers to properly sign NP2WKH inputs. Co-authored-by: Oliver Gugger --- wallet/psbt.go | 14 +++++- wallet/signer.go | 117 ++++++++++++++++++++++++++++------------------- 2 files changed, 82 insertions(+), 49 deletions(-) diff --git a/wallet/psbt.go b/wallet/psbt.go index fc9085b..a0617d8 100644 --- a/wallet/psbt.go +++ b/wallet/psbt.go @@ -104,9 +104,21 @@ func (w *Wallet) FundPsbt(packet *psbt.Packet, keyScope *waddrmgr.KeyScope, } // We don't want to include the witness or any script - // just yet. + // on the unsigned TX just yet. packet.UnsignedTx.TxIn[idx].Witness = wire.TxWitness{} packet.UnsignedTx.TxIn[idx].SignatureScript = nil + + // For nested P2WKH we need to add the redeem script to + // the input, otherwise an offline wallet won't be able + // to sign for it. For normal P2WKH this will be nil. + addr, witnessProgram, _, err := w.scriptForOutput(utxo) + if err != nil { + return fmt.Errorf("error fetching UTXO "+ + "script: %v", err) + } + if addr.AddrType() == waddrmgr.NestedWitnessPubKey { + packet.Inputs[idx].RedeemScript = witnessProgram + } } return nil diff --git a/wallet/signer.go b/wallet/signer.go index 390022b..db5a10e 100644 --- a/wallet/signer.go +++ b/wallet/signer.go @@ -5,6 +5,8 @@ package wallet import ( + "fmt" + "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -12,6 +14,71 @@ import ( "github.com/btcsuite/btcwallet/waddrmgr" ) +// scriptForOutput returns the address, witness program and redeem script for a +// given UTXO. An error is returned if the UTXO does not belong to our wallet or +// it is not a managed pubKey address. +func (w *Wallet) scriptForOutput(output *wire.TxOut) ( + waddrmgr.ManagedPubKeyAddress, []byte, []byte, error) { + + // First make sure we can sign for the input by making sure the script + // in the UTXO belongs to our wallet and we have the private key for it. + walletAddr, err := w.fetchOutputAddr(output.PkScript) + if err != nil { + return nil, nil, nil, err + } + + pubKeyAddr, ok := walletAddr.(waddrmgr.ManagedPubKeyAddress) + if !ok { + return nil, nil, nil, fmt.Errorf("address %s is not a "+ + "p2wkh or np2wkh address", walletAddr.Address()) + } + + var ( + witnessProgram []byte + sigScript []byte + ) + + switch { + // If we're spending p2wkh output nested within a p2sh output, then + // we'll need to attach a sigScript in addition to witness data. + case walletAddr.AddrType() == waddrmgr.NestedWitnessPubKey: + pubKey := pubKeyAddr.PubKey() + pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed()) + + // Next, we'll generate a valid sigScript that will allow us to + // spend the p2sh output. The sigScript will contain only a + // single push of the p2wkh witness program corresponding to + // the matching public key of this address. + p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash( + pubKeyHash, w.chainParams, + ) + if err != nil { + return nil, nil, nil, err + } + witnessProgram, err = txscript.PayToAddrScript(p2wkhAddr) + if err != nil { + return nil, nil, nil, err + } + + bldr := txscript.NewScriptBuilder() + bldr.AddData(witnessProgram) + sigScript, err = bldr.Script() + if err != nil { + return nil, nil, nil, err + } + + // Otherwise, this is a regular p2wkh output, so we include the + // witness program itself as the subscript to generate the proper + // sighash digest. As part of the new sighash digest algorithm, the + // p2wkh witness program will be expanded into a regular p2kh + // script. + default: + witnessProgram = output.PkScript + } + + return pubKeyAddr, witnessProgram, sigScript, nil +} + // PrivKeyTweaker is a function type that can be used to pass in a callback for // tweaking a private key before it's used to sign an input. type PrivKeyTweaker func(*btcec.PrivateKey) (*btcec.PrivateKey, error) @@ -25,62 +92,16 @@ func (w *Wallet) ComputeInputScript(tx *wire.MsgTx, output *wire.TxOut, hashType txscript.SigHashType, tweaker PrivKeyTweaker) (wire.TxWitness, []byte, error) { - // First make sure we can sign for the input by making sure the script - // in the UTXO belongs to our wallet and we have the private key for it. - walletAddr, err := w.fetchOutputAddr(output.PkScript) + walletAddr, witnessProgram, sigScript, err := w.scriptForOutput(output) if err != nil { return nil, nil, err } - pka := walletAddr.(waddrmgr.ManagedPubKeyAddress) - privKey, err := pka.PrivKey() + privKey, err := walletAddr.PrivKey() if err != nil { return nil, nil, err } - var ( - witnessProgram []byte - sigScript []byte - ) - - switch { - // If we're spending p2wkh output nested within a p2sh output, then - // we'll need to attach a sigScript in addition to witness data. - case pka.AddrType() == waddrmgr.NestedWitnessPubKey: - pubKey := privKey.PubKey() - pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed()) - - // Next, we'll generate a valid sigScript that will allow us to - // spend the p2sh output. The sigScript will contain only a - // single push of the p2wkh witness program corresponding to - // the matching public key of this address. - p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash( - pubKeyHash, w.chainParams, - ) - if err != nil { - return nil, nil, err - } - witnessProgram, err = txscript.PayToAddrScript(p2wkhAddr) - if err != nil { - return nil, nil, err - } - - bldr := txscript.NewScriptBuilder() - bldr.AddData(witnessProgram) - sigScript, err = bldr.Script() - if err != nil { - return nil, nil, err - } - - // Otherwise, this is a regular p2wkh output, so we include the - // witness program itself as the subscript to generate the proper - // sighash digest. As part of the new sighash digest algorithm, the - // p2wkh witness program will be expanded into a regular p2kh - // script. - default: - witnessProgram = output.PkScript - } - // If we need to maybe tweak our private key, do it now. if tweaker != nil { privKey, err = tweaker(privKey)