345 lines
12 KiB
Go
345 lines
12 KiB
Go
// Copyright (c) 2016 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package txauthor provides transaction creation code for wallets.
|
|
package txauthor
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/roasbeef/btcd/chaincfg"
|
|
"github.com/roasbeef/btcd/txscript"
|
|
"github.com/roasbeef/btcd/wire"
|
|
"github.com/roasbeef/btcutil"
|
|
"github.com/roasbeef/btcwallet/wallet/txrules"
|
|
|
|
h "github.com/roasbeef/btcwallet/internal/helpers"
|
|
"github.com/roasbeef/btcwallet/wallet/internal/txsizes"
|
|
)
|
|
|
|
// InputSource provides transaction inputs referencing spendable outputs to
|
|
// construct a transaction outputting some target amount. If the target amount
|
|
// can not be satisified, this can be signaled by returning a total amount less
|
|
// than the target or by returning a more detailed error implementing
|
|
// InputSourceError.
|
|
type InputSource func(target btcutil.Amount) (total btcutil.Amount, inputs []*wire.TxIn,
|
|
inputValues []btcutil.Amount, scripts [][]byte, err error)
|
|
|
|
// InputSourceError describes the failure to provide enough input value from
|
|
// unspent transaction outputs to meet a target amount. A typed error is used
|
|
// so input sources can provide their own implementations describing the reason
|
|
// for the error, for example, due to spendable policies or locked coins rather
|
|
// than the wallet not having enough available input value.
|
|
type InputSourceError interface {
|
|
error
|
|
InputSourceError()
|
|
}
|
|
|
|
// Default implementation of InputSourceError.
|
|
type insufficientFundsError struct{}
|
|
|
|
func (insufficientFundsError) InputSourceError() {}
|
|
func (insufficientFundsError) Error() string {
|
|
return "insufficient funds available to construct transaction"
|
|
}
|
|
|
|
// AuthoredTx holds the state of a newly-created transaction and the change
|
|
// output (if one was added).
|
|
type AuthoredTx struct {
|
|
Tx *wire.MsgTx
|
|
PrevScripts [][]byte
|
|
PrevInputValues []btcutil.Amount
|
|
TotalInput btcutil.Amount
|
|
ChangeIndex int // negative if no change
|
|
}
|
|
|
|
// ChangeSource provides P2PKH change output scripts for transaction creation.
|
|
type ChangeSource func() ([]byte, error)
|
|
|
|
// NewUnsignedTransaction creates an unsigned transaction paying to one or more
|
|
// non-change outputs. An appropriate transaction fee is included based on the
|
|
// transaction size.
|
|
//
|
|
// Transaction inputs are chosen from repeated calls to fetchInputs with
|
|
// increasing targets amounts.
|
|
//
|
|
// If any remaining output value can be returned to the wallet via a change
|
|
// output without violating mempool dust rules, a P2PKH change output is
|
|
// appended to the transaction outputs. Since the change output may not be
|
|
// necessary, fetchChange is called zero or one times to generate this script.
|
|
// This function must return a P2PKH script or smaller, otherwise fee estimation
|
|
// will be incorrect.
|
|
//
|
|
// If successful, the transaction, total input value spent, and all previous
|
|
// output scripts are returned. If the input source was unable to provide
|
|
// enough input value to pay for every output any any necessary fees, an
|
|
// InputSourceError is returned.
|
|
//
|
|
// BUGS: Fee estimation may be off when redeeming non-compressed P2PKH outputs.
|
|
// TODO(roasbeef): fix fee estimation for witness outputs
|
|
func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb btcutil.Amount,
|
|
fetchInputs InputSource, fetchChange ChangeSource) (*AuthoredTx, error) {
|
|
|
|
targetAmount := h.SumOutputValues(outputs)
|
|
estimatedSize := txsizes.EstimateSerializeSize(1, outputs, true)
|
|
targetFee := txrules.FeeForSerializeSize(relayFeePerKb, estimatedSize)
|
|
|
|
for {
|
|
inputAmount, inputs, inputValues, scripts, err := fetchInputs(targetAmount + targetFee)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if inputAmount < targetAmount+targetFee {
|
|
return nil, insufficientFundsError{}
|
|
}
|
|
|
|
maxSignedSize := txsizes.EstimateSerializeSize(len(inputs), outputs, true)
|
|
maxRequiredFee := txrules.FeeForSerializeSize(relayFeePerKb, maxSignedSize)
|
|
remainingAmount := inputAmount - targetAmount
|
|
if remainingAmount < maxRequiredFee {
|
|
targetFee = maxRequiredFee
|
|
continue
|
|
}
|
|
|
|
unsignedTransaction := &wire.MsgTx{
|
|
Version: wire.TxVersion,
|
|
TxIn: inputs,
|
|
TxOut: outputs,
|
|
LockTime: 0,
|
|
}
|
|
changeIndex := -1
|
|
changeAmount := inputAmount - targetAmount - maxRequiredFee
|
|
if changeAmount != 0 && !txrules.IsDustAmount(changeAmount,
|
|
txsizes.P2PKHPkScriptSize, relayFeePerKb) {
|
|
changeScript, err := fetchChange()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(changeScript) > txsizes.P2PKHPkScriptSize {
|
|
return nil, errors.New("fee estimation requires change " +
|
|
"scripts no larger than P2PKH output scripts")
|
|
}
|
|
change := wire.NewTxOut(int64(changeAmount), changeScript)
|
|
l := len(outputs)
|
|
unsignedTransaction.TxOut = append(outputs[:l:l], change)
|
|
changeIndex = l
|
|
}
|
|
|
|
return &AuthoredTx{
|
|
Tx: unsignedTransaction,
|
|
PrevScripts: scripts,
|
|
PrevInputValues: inputValues,
|
|
TotalInput: inputAmount,
|
|
ChangeIndex: changeIndex,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
// RandomizeOutputPosition randomizes the position of a transaction's output by
|
|
// swapping it with a random output. The new index is returned. This should be
|
|
// done before signing.
|
|
func RandomizeOutputPosition(outputs []*wire.TxOut, index int) int {
|
|
r := cprng.Int31n(int32(len(outputs)))
|
|
outputs[r], outputs[index] = outputs[index], outputs[r]
|
|
return int(r)
|
|
}
|
|
|
|
// RandomizeChangePosition randomizes the position of an authored transaction's
|
|
// change output. This should be done before signing.
|
|
func (tx *AuthoredTx) RandomizeChangePosition() {
|
|
tx.ChangeIndex = RandomizeOutputPosition(tx.Tx.TxOut, tx.ChangeIndex)
|
|
}
|
|
|
|
// SecretsSource provides private keys and redeem scripts necessary for
|
|
// constructing transaction input signatures. Secrets are looked up by the
|
|
// corresponding Address for the previous output script. Addresses for lookup
|
|
// are created using the source's blockchain parameters and means a single
|
|
// SecretsSource can only manage secrets for a single chain.
|
|
//
|
|
// TODO: Rewrite this interface to look up private keys and redeem scripts for
|
|
// pubkeys, pubkey hashes, script hashes, etc. as separate interface methods.
|
|
// This would remove the ChainParams requirement of the interface and could
|
|
// avoid unnecessary conversions from previous output scripts to Addresses.
|
|
// This can not be done without modifications to the txscript package.
|
|
type SecretsSource interface {
|
|
txscript.KeyDB
|
|
txscript.ScriptDB
|
|
ChainParams() *chaincfg.Params
|
|
}
|
|
|
|
// AddAllInputScripts modifies transaction a transaction by adding inputs
|
|
// scripts for each input. Previous output scripts being redeemed by each input
|
|
// are passed in prevPkScripts and the slice length must match the number of
|
|
// inputs. Private keys and redeem scripts are looked up using a SecretsSource
|
|
// based on the previous output script.
|
|
func AddAllInputScripts(tx *wire.MsgTx, prevPkScripts [][]byte, inputValues []btcutil.Amount,
|
|
secrets SecretsSource) error {
|
|
|
|
inputs := tx.TxIn
|
|
hashCache := txscript.NewTxSigHashes(tx)
|
|
chainParams := secrets.ChainParams()
|
|
|
|
if len(inputs) != len(prevPkScripts) {
|
|
return errors.New("tx.TxIn and prevPkScripts slices must " +
|
|
"have equal length")
|
|
}
|
|
|
|
for i := range inputs {
|
|
pkScript := prevPkScripts[i]
|
|
|
|
switch {
|
|
// If this is a p2sh output, who's script hash pre-image is a
|
|
// witness program, then we'll need to use a modified signing
|
|
// function which generates both the sigScript, and the witness
|
|
// script.
|
|
case txscript.IsPayToScriptHash(pkScript):
|
|
err := spendNestedWitnessPubKeyHash(inputs[i], pkScript,
|
|
int64(inputValues[i]), chainParams, secrets,
|
|
tx, hashCache, i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case txscript.IsPayToWitnessPubKeyHash(pkScript):
|
|
err := spendWitnessKeyHash(inputs[i], pkScript,
|
|
int64(inputValues[i]), chainParams, secrets,
|
|
tx, hashCache, i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
sigScript := inputs[i].SignatureScript
|
|
script, err := txscript.SignTxOutput(chainParams, tx, i,
|
|
pkScript, txscript.SigHashAll, secrets, secrets,
|
|
sigScript)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
inputs[i].SignatureScript = script
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// spendWitnessKeyHash generates, and sets a valid witness for spending the
|
|
// passed pkScript with the specified input amount. The input amount *must*
|
|
// correspond to the output value of the previous pkScript, or else verification
|
|
// will fail since the new sighash digest algorithm defined in BIP0143 includes
|
|
// the input value in the sighash.
|
|
func spendWitnessKeyHash(txIn *wire.TxIn, pkScript []byte,
|
|
inputValue int64, chainParams *chaincfg.Params, secrets SecretsSource,
|
|
tx *wire.MsgTx, hashCache *txscript.TxSigHashes, idx int) error {
|
|
|
|
// First obtain the key pair associated with this p2wkh address.
|
|
_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript,
|
|
chainParams)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
privKey, compressed, err := secrets.GetKey(addrs[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pubKey := privKey.PubKey()
|
|
|
|
// Once we have the key pair, generate a p2wkh address type, respecting
|
|
// the compression type of the generated key.
|
|
var pubKeyHash []byte
|
|
if compressed {
|
|
pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed())
|
|
} else {
|
|
pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed())
|
|
}
|
|
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, chainParams)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// With the concrete address type, we can now generate the
|
|
// corresponding witness program to be used to generate a valid witness
|
|
// which will allow us to spend this output.
|
|
witnessProgram, err := txscript.PayToAddrScript(p2wkhAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
witnessScript, err := txscript.WitnessSignature(tx, hashCache, idx,
|
|
inputValue, witnessProgram, txscript.SigHashAll, privKey, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
txIn.Witness = witnessScript
|
|
|
|
return nil
|
|
}
|
|
|
|
// spendNestedWitnessPubKey generates both a sigScript, and valid witness for
|
|
// spending the passed pkScript with the specified input amount. The generated
|
|
// sigScript is the version 0 p2wkh witness program corresponding to the queried
|
|
// key. The witness stack is identical to that of one which spends a regular
|
|
// p2wkh output. The input amount *must* correspond to the output value of the
|
|
// previous pkScript, or else verification will fail since the new sighash
|
|
// digest algorithm defined in BIP0143 includes the input value in the sighash.
|
|
func spendNestedWitnessPubKeyHash(txIn *wire.TxIn, pkScript []byte,
|
|
inputValue int64, chainParams *chaincfg.Params, secrets SecretsSource,
|
|
tx *wire.MsgTx, hashCache *txscript.TxSigHashes, idx int) error {
|
|
|
|
// First we need to obtain the key pair related to this p2sh output.
|
|
_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript,
|
|
chainParams)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
privKey, compressed, err := secrets.GetKey(addrs[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pubKey := privKey.PubKey()
|
|
|
|
var pubKeyHash []byte
|
|
if compressed {
|
|
pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed())
|
|
} else {
|
|
pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed())
|
|
}
|
|
|
|
// Next, we'll generate a valid sigScript that'll 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, chainParams)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
witnessProgram, err := txscript.PayToAddrScript(p2wkhAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bldr := txscript.NewScriptBuilder()
|
|
bldr.AddData(witnessProgram)
|
|
sigScript, err := bldr.Script()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txIn.SignatureScript = sigScript
|
|
|
|
// With the sigScript in place, we'll next generate the proper witness
|
|
// that'll allow us to spend the p2wkh output.
|
|
witnessScript, err := txscript.WitnessSignature(tx, hashCache, idx,
|
|
inputValue, witnessProgram, txscript.SigHashAll, privKey, compressed)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
txIn.Witness = witnessScript
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddAllInputScripts modifies an authored transaction by adding inputs scripts
|
|
// for each input of an authored transaction. Private keys and redeem scripts
|
|
// are looked up using a SecretsSource based on the previous output script.
|
|
func (tx *AuthoredTx) AddAllInputScripts(secrets SecretsSource) error {
|
|
return AddAllInputScripts(tx.Tx, tx.PrevScripts, tx.PrevInputValues, secrets)
|
|
}
|