e17c9730c4
Implements: PSBT struct, roles: creator, updater, signer, extractor. Passing test vectors.
485 lines
14 KiB
Go
485 lines
14 KiB
Go
// Copyright (c) 2018 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package psbt
|
|
|
|
// The Finalizer requires provision of a single PSBT input
|
|
// in which all necessary signatures are encoded, and
|
|
// uses it to construct valid final scriptSig and scriptWitness
|
|
// fields.
|
|
// NOTE that p2sh (legacy) and p2wsh currently support only
|
|
// multisig and no other custom script.
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
|
|
"io"
|
|
"sort"
|
|
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
)
|
|
|
|
// A utility function due to non-exported witness serialization
|
|
// (writeTxWitness encodes the bitcoin protocol encoding for a transaction
|
|
// input's witness into w).
|
|
func writeTxWitness(w io.Writer, wit [][]byte) error {
|
|
err := wire.WriteVarInt(w, 0, uint64(len(wit)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, item := range wit {
|
|
err = wire.WriteVarBytes(w, 0, item)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// writePKHWitness writes a witness for a p2wkh spending input
|
|
func writePKHWitness(sig []byte, pub []byte) ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
var witnessItems = [][]byte{sig, pub}
|
|
err := writeTxWitness(&buf, witnessItems)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// checkIsMultisigScript is a utility function to check wheter
|
|
// a given redeemscript fits the standard multisig template used
|
|
// in all p2sh based multisig, given a set of pubkeys for redemption.
|
|
func checkIsMultiSigScript(pubKeys [][]byte, sigs [][]byte,
|
|
script []byte) bool {
|
|
|
|
// First insist that the script type is multisig
|
|
if txscript.GetScriptClass(script) != txscript.MultiSigTy {
|
|
return false
|
|
}
|
|
|
|
// Inspect the script to ensure that the number of sigs and
|
|
// pubkeys is correct
|
|
numSigs, numPubKeys, err := txscript.CalcMultiSigStats(script)
|
|
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if numPubKeys != len(pubKeys) || numSigs != len(sigs) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// extractKeyOrderFromScript is a utility function
|
|
// to extract an ordered list of signatures, given
|
|
// a serialized script (redeemscript or witness script),
|
|
// a list of pubkeys and the signatures corresponding to those
|
|
// pubkeys, so that the signatures will be embedded in the final
|
|
// scriptSig or scriptWitness in the correct order.
|
|
func extractKeyOrderFromScript(script []byte, expectedPubkeys [][]byte,
|
|
sigs [][]byte) ([][]byte, error) {
|
|
|
|
if !checkIsMultiSigScript(expectedPubkeys, sigs, script) {
|
|
return nil, ErrUnsupportedScriptType
|
|
}
|
|
// Arrange the pubkeys and sigs into a slice of format:
|
|
// [[pub,sig], [pub,sig],..]
|
|
pubsSigs := [][][]byte{}
|
|
for i, pub := range expectedPubkeys {
|
|
tmp := [][]byte{pub, sigs[i]}
|
|
pubsSigs = append(pubsSigs, tmp)
|
|
}
|
|
type kv struct {
|
|
Key int
|
|
Value [][]byte
|
|
}
|
|
var positionMap []kv
|
|
for _, p := range pubsSigs {
|
|
pos := bytes.Index(script, p[0])
|
|
if pos < 0 {
|
|
return nil, errors.New("Script does not contain pubkeys")
|
|
}
|
|
positionMap = append(positionMap, kv{Key: pos, Value: p})
|
|
}
|
|
|
|
sort.Slice(positionMap, func(i, j int) bool {
|
|
return positionMap[i].Key < positionMap[j].Key
|
|
})
|
|
// Build the return array of signatures
|
|
sigsNew := [][]byte{}
|
|
for _, x := range positionMap {
|
|
sigsNew = append(sigsNew, x.Value[1])
|
|
}
|
|
return sigsNew, nil
|
|
}
|
|
|
|
// getMultisigScriptWitness creates a full Witness field for the transaction,
|
|
// given the public keys and signatures to be appended, after checking
|
|
// that the witnessScript is of type M of N multisig. This
|
|
// is used for both p2wsh and nested p2wsh multisig cases.
|
|
func getMultisigScriptWitness(witnessScript []byte, pubKeys [][]byte,
|
|
sigs [][]byte) ([]byte, error) {
|
|
|
|
orderedSigs, err := extractKeyOrderFromScript(witnessScript, pubKeys, sigs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
var witnessItems = [][]byte{
|
|
nil}
|
|
for _, os := range orderedSigs {
|
|
witnessItems = append(witnessItems, os)
|
|
}
|
|
witnessItems = append(witnessItems, witnessScript)
|
|
err = writeTxWitness(&buf, witnessItems)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// checkSigHashFlags compares the sighash flag byte on a signature with the
|
|
// value expected according to any PsbtInSighashType field in this section
|
|
// of the PSBT, and returns true if they match, false otherwise.
|
|
// If no SighashType field exists, it is assumed to be SIGHASH_ALL.
|
|
// TODO sighash type not restricted to one byte in future?
|
|
func checkSigHashFlags(sig []byte, input *PInput) bool {
|
|
expectedSighashType := txscript.SigHashAll
|
|
if input.SighashType != 0 {
|
|
expectedSighashType = input.SighashType
|
|
}
|
|
|
|
return expectedSighashType == txscript.SigHashType(sig[len(sig)-1])
|
|
}
|
|
|
|
// isFinalized considers this input finalized if it contains
|
|
// at least one of the FinalScriptSig or FinalScriptWitness
|
|
// are filled (which only occurs in a successful call to Finalize*)
|
|
func isFinalized(p *Psbt, inIndex int) bool {
|
|
input := p.Inputs[inIndex]
|
|
return input.FinalScriptSig != nil || input.FinalScriptWitness != nil
|
|
}
|
|
|
|
// isFinalizable checks whether the structure of the entry
|
|
// for the input of the Psbt p at index inIndex contains sufficient
|
|
// information to finalize this input. Deduce the template
|
|
// from the contents.
|
|
func isFinalizable(p *Psbt, inIndex int) bool {
|
|
pInput := p.Inputs[inIndex]
|
|
|
|
// Cannot be finalizable without any signatures
|
|
if pInput.PartialSigs == nil {
|
|
return false
|
|
}
|
|
|
|
if pInput.WitnessUtxo != nil {
|
|
if txscript.IsWitnessProgram(pInput.WitnessUtxo.PkScript) {
|
|
if txscript.IsPayToWitnessScriptHash(pInput.WitnessUtxo.PkScript) {
|
|
if pInput.WitnessScript == nil || pInput.RedeemScript != nil {
|
|
return false
|
|
}
|
|
} else {
|
|
// if it's p2wkh there should be no redeemScript or witnessScript
|
|
if pInput.WitnessScript != nil || pInput.RedeemScript != nil {
|
|
return false
|
|
}
|
|
}
|
|
} else if txscript.IsPayToScriptHash(pInput.WitnessUtxo.PkScript) {
|
|
if pInput.RedeemScript == nil {
|
|
return false
|
|
}
|
|
// if it's nested, and it's p2wsh, it must have WitnessScript;
|
|
// if p2wkh, it must not.
|
|
if txscript.IsPayToWitnessScriptHash(pInput.RedeemScript) {
|
|
if pInput.WitnessScript == nil {
|
|
return false
|
|
}
|
|
} else if txscript.IsPayToWitnessPubKeyHash(pInput.RedeemScript) {
|
|
if pInput.WitnessScript != nil {
|
|
return false
|
|
}
|
|
} else {
|
|
// unrecognized type
|
|
return false
|
|
}
|
|
}
|
|
} else if pInput.NonWitnessUtxo != nil {
|
|
if pInput.WitnessScript != nil {
|
|
return false
|
|
}
|
|
outIndex := p.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Index
|
|
if txscript.IsPayToScriptHash(pInput.NonWitnessUtxo.TxOut[outIndex].PkScript) {
|
|
if pInput.RedeemScript == nil {
|
|
return false
|
|
}
|
|
} else {
|
|
if pInput.RedeemScript != nil {
|
|
return false
|
|
}
|
|
}
|
|
} else {
|
|
// one of witness and nonwitness utxo must be present
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// MaybeFinalize attempts to finalize the input at index inIndex
|
|
// in the PSBT p, returning true with no error if it succeeds, OR
|
|
// if the input has already been finalized.
|
|
func MaybeFinalize(p *Psbt, inIndex int) (bool, error) {
|
|
if isFinalized(p, inIndex) {
|
|
return true, nil
|
|
}
|
|
if !isFinalizable(p, inIndex) {
|
|
return false, ErrNotFinalizable
|
|
}
|
|
err := Finalize(p, inIndex)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// MaybeFinalizeAll attempts to finalize all inputs of the Psbt that
|
|
// are not already finalized, and returns an error if it fails to do so.
|
|
func MaybeFinalizeAll(p *Psbt) error {
|
|
for i := range p.UnsignedTx.TxIn {
|
|
success, err := MaybeFinalize(p, i)
|
|
if err != nil || !success {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Finalize assumes that the provided Psbt struct
|
|
// has all partial signatures and redeem scripts/witness scripts
|
|
// already prepared for the specified input, and so removes all temporary
|
|
// data and replaces them with completed scriptSig and witness
|
|
// fields, which are stored in key-types 07 and 08. The witness/
|
|
// non-witness utxo fields in the inputs (key-types 00 and 01) are
|
|
// left intact as they may be needed for validation (?).
|
|
// If there is any invalid or incomplete data, an error is
|
|
// returned.
|
|
func Finalize(p *Psbt, inIndex int) error {
|
|
var err error
|
|
pInput := p.Inputs[inIndex]
|
|
if pInput.WitnessUtxo != nil {
|
|
err = FinalizeWitness(p, inIndex)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if pInput.NonWitnessUtxo != nil {
|
|
err = FinalizeNonWitness(p, inIndex)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
return ErrInvalidPsbtFormat
|
|
}
|
|
|
|
if err = p.SanityCheck(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// checkFinalScriptSigWitness checks whether a given input in the
|
|
// Psbt struct already has the fields 07 (FinalInScriptSig) or 08
|
|
// (FinalInWitness). If so, it returns true. It does not modify the
|
|
// Psbt.
|
|
func checkFinalScriptSigWitness(p *Psbt, inIndex int) bool {
|
|
pInput := p.Inputs[inIndex]
|
|
if pInput.FinalScriptSig != nil {
|
|
return true
|
|
}
|
|
if pInput.FinalScriptWitness != nil {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// FinalizeNonWitness attempts to create PsbtInFinalScriptSig field
|
|
// for input at index inIndex, and removes all other fields except
|
|
// for the utxo field, for an input of type non-witness, or returns
|
|
// an error.
|
|
func FinalizeNonWitness(p *Psbt, inIndex int) error {
|
|
if checkFinalScriptSigWitness(p, inIndex) {
|
|
return ErrInputAlreadyFinalized
|
|
}
|
|
// Construct a scriptSig given the pubkey, signature (keytype 02),
|
|
// of which there might be multiple, and the redeem script
|
|
// field (keytype 04) if present (note, it is not present
|
|
// for p2pkh type inputs).
|
|
var scriptSig []byte
|
|
var err error
|
|
pInput := p.Inputs[inIndex]
|
|
containsRedeemScript := pInput.RedeemScript != nil
|
|
var pubKeys [][]byte
|
|
var sigs [][]byte
|
|
for _, ps := range pInput.PartialSigs {
|
|
pubKeys = append(pubKeys, ps.PubKey)
|
|
sigOK := checkSigHashFlags(ps.Signature, &pInput)
|
|
if !sigOK {
|
|
return ErrInvalidSigHashFlags
|
|
}
|
|
sigs = append(sigs, ps.Signature)
|
|
}
|
|
|
|
if len(sigs) < 1 || len(pubKeys) < 1 {
|
|
// We have failed to identify at least 1 (sig, pub) pair
|
|
// in the PSBT, which indicates it was not ready to be finalized.
|
|
return ErrNotFinalizable
|
|
}
|
|
|
|
if !containsRedeemScript {
|
|
// p2pkh - insist on one sig/pub and build scriptSig
|
|
if len(sigs) != 1 || len(pubKeys) != 1 {
|
|
return ErrNotFinalizable
|
|
}
|
|
builder := txscript.NewScriptBuilder()
|
|
builder.AddData(sigs[0]).AddData(pubKeys[0])
|
|
scriptSig, err = builder.Script()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// This is assumed p2sh multisig
|
|
// Given redeemScript and pubKeys we can decide in what order
|
|
// signatures must be appended.
|
|
orderedSigs, err := extractKeyOrderFromScript(pInput.RedeemScript,
|
|
pubKeys, sigs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// TODO the below is specific to the multisig case.
|
|
builder := txscript.NewScriptBuilder()
|
|
builder.AddOp(txscript.OP_FALSE)
|
|
for _, os := range orderedSigs {
|
|
builder.AddData(os)
|
|
}
|
|
builder.AddData(pInput.RedeemScript)
|
|
scriptSig, err = builder.Script()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// At this point, a scriptSig has been constructed.
|
|
// Remove all fields other than non-witness utxo (00)
|
|
// and finaliscriptsig (07)
|
|
newInput := NewPsbtInput(pInput.NonWitnessUtxo, nil)
|
|
newInput.FinalScriptSig = scriptSig
|
|
// overwrite the entry in the input list at the correct index
|
|
// Note that this removes all the other entries in the list for
|
|
// this input index.
|
|
p.Inputs[inIndex] = *newInput
|
|
return nil
|
|
}
|
|
|
|
// FinalizeWitness attempts to create PsbtInFinalScriptSig field
|
|
// and PsbtInFinalScriptWitness field for input at index inIndex,
|
|
// and removes all other fields except for the utxo field, for an
|
|
// input of type witness, or returns an error.
|
|
func FinalizeWitness(p *Psbt, inIndex int) error {
|
|
if checkFinalScriptSigWitness(p, inIndex) {
|
|
return ErrInputAlreadyFinalized
|
|
}
|
|
// Construct a scriptSig given the redeem script
|
|
// field (keytype 04) if present (if not present it's empty
|
|
// as per bip141).
|
|
// Fill this in in field FinalScriptSig (keytype 07).
|
|
// And/or construct a FinalScriptWitness field (keytype 08),
|
|
// assuming either p2wkh or p2wsh multisig.
|
|
var scriptSig []byte
|
|
var witness []byte
|
|
var err error
|
|
pInput := p.Inputs[inIndex]
|
|
containsRedeemScript := pInput.RedeemScript != nil
|
|
cointainsWitnessScript := pInput.WitnessScript != nil
|
|
var pubKeys [][]byte
|
|
var sigs [][]byte
|
|
for _, ps := range pInput.PartialSigs {
|
|
pubKeys = append(pubKeys, ps.PubKey)
|
|
sigOK := checkSigHashFlags(ps.Signature, &pInput)
|
|
if !sigOK {
|
|
return ErrInvalidSigHashFlags
|
|
}
|
|
sigs = append(sigs, ps.Signature)
|
|
}
|
|
if len(sigs) == 0 || len(pubKeys) == 0 {
|
|
return ErrNotFinalizable
|
|
}
|
|
if !containsRedeemScript {
|
|
if len(pubKeys) == 1 && len(sigs) == 1 && !cointainsWitnessScript {
|
|
// p2wkh case
|
|
witness, err = writePKHWitness(sigs[0], pubKeys[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Otherwise, we must have a witnessScript field,
|
|
// to fulfil the requirements of p2wsh
|
|
// NOTE (we tacitly assume multisig)
|
|
if !cointainsWitnessScript {
|
|
return ErrNotFinalizable
|
|
}
|
|
witness, err = getMultisigScriptWitness(pInput.WitnessScript,
|
|
pubKeys, sigs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// This is currently assumed p2wsh, multisig, nested in p2sh,
|
|
// or p2wkh, nested in p2sh.
|
|
// The scriptSig is taken from the redeemscript field, but embedded
|
|
// in a push
|
|
builder := txscript.NewScriptBuilder()
|
|
builder.AddData(pInput.RedeemScript)
|
|
scriptSig, err = builder.Script()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !cointainsWitnessScript {
|
|
// Assumed p2sh-p2wkh
|
|
// Here the witness is just (sig, pub) as for p2pkh case
|
|
if len(sigs) != 1 || len(pubKeys) != 1 {
|
|
return ErrNotFinalizable
|
|
}
|
|
witness, err = writePKHWitness(sigs[0], pubKeys[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Assumed p2sh-p2wsh with multisig.
|
|
// To build the witness, we do exactly as for the native p2wsh case.
|
|
witness, err = getMultisigScriptWitness(pInput.WitnessScript,
|
|
pubKeys, sigs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
// At this point, a witness has been constructed,
|
|
// and a scriptSig (if nested; else it's []).
|
|
// Remove all fields other than witness utxo (01)
|
|
// and finalscriptsig (07), finalscriptwitness (08)
|
|
newInput := NewPsbtInput(nil, pInput.WitnessUtxo)
|
|
if len(scriptSig) > 0 {
|
|
newInput.FinalScriptSig = scriptSig
|
|
}
|
|
newInput.FinalScriptWitness = witness
|
|
// overwrite the entry in the input list at the correct index
|
|
p.Inputs[inIndex] = *newInput
|
|
return nil
|
|
}
|