From e17c9730c422e7c745002430f2782b948b59c1c2 Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Thu, 19 Dec 2019 18:20:22 +0000 Subject: [PATCH] PSBT BIP 174 implementation (#126) Implements: PSBT struct, roles: creator, updater, signer, extractor. Passing test vectors. --- psbt/creator.go | 70 +++ psbt/extractor.go | 66 +++ psbt/finalizer.go | 485 ++++++++++++++++++ psbt/psbt.go | 987 ++++++++++++++++++++++++++++++++++++ psbt/psbt_test.go | 1222 +++++++++++++++++++++++++++++++++++++++++++++ psbt/signer.go | 132 +++++ psbt/updater.go | 312 ++++++++++++ 7 files changed, 3274 insertions(+) create mode 100644 psbt/creator.go create mode 100644 psbt/extractor.go create mode 100644 psbt/finalizer.go create mode 100644 psbt/psbt.go create mode 100644 psbt/psbt_test.go create mode 100644 psbt/signer.go create mode 100644 psbt/updater.go diff --git a/psbt/creator.go b/psbt/creator.go new file mode 100644 index 0000000..9258a27 --- /dev/null +++ b/psbt/creator.go @@ -0,0 +1,70 @@ +// 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 Creator has only one function: +// takes a list of inputs and outputs and converts them to +// the simplest Psbt, i.e. one with no data appended to inputs or outputs, +// but only the raw unsigned transaction in the global section. + +import ( + "github.com/btcsuite/btcd/wire" +) + +// Creator holds a reference to a created Psbt struct. +type Creator struct { + Cpsbt *Psbt +} + +// CreatePsbt , on provision of an input and output 'skeleton' for +// the transaction, returns a Creator struct. +// Note that we require OutPoints and not TxIn structs, as we will +// only populate the txid:n information, *not* any scriptSig/witness +// information. The values of nLockTime, nSequence (per input) and +// transaction version (must be 1 of 2) must be specified here. Note +// that the default nSequence value is wire.MaxTxInSequenceNum. +func (c *Creator) CreatePsbt(inputs []*wire.OutPoint, + outputs []*wire.TxOut, Version int32, nLockTime uint32, + nSequences []uint32) error { + // Create the new struct; the input and output lists will be empty, + // the unsignedTx object must be constructed and serialized, + // and that serialization should be entered as the only entry for + // the globalKVPairs list. + + // Check the version is a valid Bitcoin tx version; the nLockTime + // can be any valid uint32. There must be one sequence number per + // input. + if !(Version == 1 || Version == 2) || len(nSequences) != len(inputs) { + return ErrInvalidPsbtFormat + } + unsignedTx := wire.NewMsgTx(Version) + unsignedTx.LockTime = nLockTime + + for i, in := range inputs { + unsignedTx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: *in, + Sequence: nSequences[i], + }) + } + for _, out := range outputs { + unsignedTx.AddTxOut(out) + } + + // The input and output lists are empty, but there is a list of those + // two lists, and each one must be of length matching the unsigned + // transaction; the unknown list can be nil. + pInputs := make([]PInput, len(unsignedTx.TxIn)) + pOutputs := make([]POutput, len(unsignedTx.TxOut)) + c.Cpsbt = &Psbt{ + UnsignedTx: unsignedTx, + Inputs: pInputs, + Outputs: pOutputs, + Unknowns: nil, + } + + // This new Psbt is "raw" and contains no key-value fields, + // so sanity checking with c.Cpsbt.SanityCheck() is not required. + return nil +} diff --git a/psbt/extractor.go b/psbt/extractor.go new file mode 100644 index 0000000..de80fec --- /dev/null +++ b/psbt/extractor.go @@ -0,0 +1,66 @@ +// 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 Extractor requires provision of a single PSBT +// in which all necessary signatures are encoded, and +// uses it to construct a fully valid network serialized +// transaction. + +import ( + "bytes" + + "github.com/btcsuite/btcd/wire" +) + +// Extract takes a finalized psbt and outputs a network serialization +func Extract(p *Psbt) ([]byte, error) { + if !p.IsComplete() { + return nil, ErrIncompletePSBT + } + var err error + // We take the existing UnsignedTx field and append SignatureScript + // and Witness as appropriate, then allow MsgTx to do the serialization + // for us. + newTx := p.UnsignedTx.Copy() + for i, tin := range newTx.TxIn { + pInput := p.Inputs[i] + if pInput.FinalScriptSig != nil { + tin.SignatureScript = pInput.FinalScriptSig + } + if pInput.FinalScriptWitness != nil { + // to set the witness, need to re-deserialize the field + // For each input, the witness is encoded as a stack + // with one or more items. Therefore, we first read a + // varint which encodes the number of stack items. + r := bytes.NewReader(pInput.FinalScriptWitness) + witCount, err := wire.ReadVarInt(r, 0) + if err != nil { + return nil, err + } + + // Then for witCount number of stack items, each item + // has a varint length prefix, followed by the witness + // item itself. + tin.Witness = make([][]byte, witCount) + for j := uint64(0); j < witCount; j++ { + // the 10000 size limit is as per BIP141 for witness script; + // TODO this constant should be somewhere else in the lib, + // perhaps btcd/wire/common.go ? + wit, err := wire.ReadVarBytes(r, 0, 10000, "witness") + if err != nil { + return nil, err + } + tin.Witness[j] = wit + } + } + } + var networkSerializedTx bytes.Buffer + err = newTx.Serialize(&networkSerializedTx) + if err != nil { + return nil, err + } + return networkSerializedTx.Bytes(), nil +} diff --git a/psbt/finalizer.go b/psbt/finalizer.go new file mode 100644 index 0000000..5fa3f9e --- /dev/null +++ b/psbt/finalizer.go @@ -0,0 +1,485 @@ +// 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 +} diff --git a/psbt/psbt.go b/psbt/psbt.go new file mode 100644 index 0000000..f9d8ff5 --- /dev/null +++ b/psbt/psbt.go @@ -0,0 +1,987 @@ +// 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 is an implementation of Partially Signed Bitcoin +// Transactions (PSBT). The format is defined in BIP 174: +// https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki +package psbt + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "errors" + + "io" + "sort" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +// BIP-174 aka PSBT defined values + +// Key types are currently encoded with single bytes +type psbtKeyType = uint8 + +const psbtMagicLength = 5 + +var ( + psbtMagic = [psbtMagicLength]byte{0x70, + 0x73, 0x62, 0x74, 0xff, // = "psbt" + 0xff sep + } +) + +// MaxPsbtValueLength is the size of the largest transaction serialization +// that could be passed in a NonWitnessUtxo field. This is definitely +//less than 4M. +const MaxPsbtValueLength = 4000000 + +// The below are the known key types as per the BIP. +// Unknown types may be accepted but will not be processed. +const ( + + // Global known key types + PsbtGlobalUnsignedTx psbtKeyType = 0 + + // TxIn section known key types + PsbtInNonWitnessUtxo psbtKeyType = 0 + PsbtInWitnessUtxo psbtKeyType = 1 + PsbtInPartialSig psbtKeyType = 2 + PsbtInSighashType psbtKeyType = 3 + PsbtInRedeemScript psbtKeyType = 4 + PsbtInWitnessScript psbtKeyType = 5 + PsbtInBip32Derivation psbtKeyType = 6 + PsbtInFinalScriptSig psbtKeyType = 7 + PsbtInFinalScriptWitness psbtKeyType = 8 + + // TxOut section known key types + PsbtOutRedeemScript psbtKeyType = 0 + PsbtOutWitnessScript psbtKeyType = 1 + PsbtOutBip32Derivation psbtKeyType = 2 +) + +var ( + + // ErrInvalidPsbtFormat is a generic error for any situation in which a + // provided Psbt serialization does not conform to the rules of BIP174. + ErrInvalidPsbtFormat = errors.New("Invalid PSBT serialization format") + + // ErrDuplicateKey indicates that a passed Psbt serialization is invalid + // due to having the same key repeated in the same key-value pair. + ErrDuplicateKey = errors.New("Invalid Psbt due to duplicate key") + + // ErrInvalidKeydata indicates that a key-value pair in the PSBT + // serialization contains data in the key which is not valid. + ErrInvalidKeydata = errors.New("Invalid key data") + + // ErrInvalidMagicBytes indicates that a passed Psbt serialization is invalid + // due to having incorrect magic bytes. + ErrInvalidMagicBytes = errors.New("Invalid Psbt due to incorrect magic bytes") + + // ErrInvalidRawTxSigned indicates that the raw serialized transaction in the + // global section of the passed Psbt serialization is invalid because it + // contains scriptSigs/witnesses (i.e. is fully or partially signed), which + // is not allowed by BIP174. + ErrInvalidRawTxSigned = errors.New("Invalid Psbt, raw transaction must " + + "be unsigned.") + + // ErrInvalidPrevOutNonWitnessTransaction indicates that the transaction + // hash (i.e. SHA256^2) of the fully serialized previous transaction + // provided in the NonWitnessUtxo key-value field doesn't match the prevout + // hash in the UnsignedTx field in the PSBT itself. + ErrInvalidPrevOutNonWitnessTransaction = errors.New("Prevout hash does " + + "not match the provided non-witness utxo serialization") + + // ErrInvalidSignatureForInput indicates that the signature the user is + // trying to append to the PSBT is invalid, either because it does + // not correspond to the previous transaction hash, or redeem script, + // or witness script. + // NOTE this does not include ECDSA signature checking. + ErrInvalidSignatureForInput = errors.New("Signature does not correspond " + + "to this input") + + // ErrInputAlreadyFinalized indicates that the PSBT passed to a Finalizer + // already contains the finalized scriptSig or witness. + ErrInputAlreadyFinalized = errors.New("Cannot finalize PSBT, finalized " + + "scriptSig or scriptWitnes already exists") + + // ErrIncompletePSBT indicates that the Extractor object + // was unable to successfully extract the passed Psbt struct because + // it is not complete + ErrIncompletePSBT = errors.New("PSBT cannot be extracted as it is " + + "incomplete") + + // ErrNotFinalizable indicates that the PSBT struct does not have + // sufficient data (e.g. signatures) for finalization + ErrNotFinalizable = errors.New("PSBT is not finalizable") + + // ErrInvalidSigHashFlags indicates that a signature added to the PSBT + // uses Sighash flags that are not in accordance with the requirement + // according to the entry in PsbtInSighashType, or otherwise not the + // default value (SIGHASH_ALL) + ErrInvalidSigHashFlags = errors.New("Invalid Sighash Flags") + + // ErrUnsupportedScriptType indicates that the redeem script or + // scriptwitness given is not supported by this codebase, or is otherwise + // not valid. + ErrUnsupportedScriptType = errors.New("Unsupported script type") +) + +func serializeKVpair(w io.Writer, key []byte, value []byte) error { + err := wire.WriteVarBytes(w, 0, key) + if err != nil { + return err + } + err = wire.WriteVarBytes(w, 0, value) + if err != nil { + return err + } + return nil +} + +func serializeKVPairWithType(w io.Writer, kt psbtKeyType, keydata []byte, + value []byte) error { + if keydata == nil { + keydata = []byte{} + } + serializedKey := append([]byte{byte(kt)}, keydata...) + return serializeKVpair(w, serializedKey, value) +} + +// getKey retrieves a single key - both the key type and the keydata +// (if present) from the stream and returns the key type as an integer, +// or -1 if the key was of zero length, which is used to indicate the +// presence of a separator byte which indicates the end of a given key- +// value pair list, and the keydata as a byte slice or nil if none is +// present. +func getKey(r io.Reader) (int, []byte, error) { + + // For the key, we read the varint separately, instead of + // using the available ReadVarBytes, because we have a specific + // treatment of 0x00 here: + count, err := wire.ReadVarInt(r, 0) + if err != nil { + return -1, nil, ErrInvalidPsbtFormat + } + if count == 0 { + // separator indicates end of key-value pair list + return -1, nil, nil + } + + // read count bytes, this is the key (including type and any data) + var keyintanddata = make([]byte, count) + if _, err := io.ReadFull(r, keyintanddata[:]); err != nil { + return -1, nil, err + } + + keyType := int(string(keyintanddata)[0]) + // Note that the second return value will usually be empty, + // since most keys contain no more than the key type byte. + if len(keyintanddata) == 1 { + return keyType, nil, nil + } + return keyType, keyintanddata[1:], nil + +} + +// readTxOut is a limited version of wire.readTxOut, because +// the latter is not exported. +func readTxOut(txout []byte) (*wire.TxOut, error) { + if len(txout) < 10 { + return nil, ErrInvalidPsbtFormat + } + valueSer := binary.LittleEndian.Uint64(txout[:8]) + scriptPubKey := txout[9:] + return wire.NewTxOut(int64(valueSer), scriptPubKey), nil +} + +// PartialSig encapsulate a (BTC public key, ECDSA signature) +// pair, note that the fields are stored as byte slices, not +// btcec.PublicKey or btcec.Signature (because manipulations will +// be with the former not the latter, here); compliance with consensus +// serialization is enforced with .checkValid() +type PartialSig struct { + PubKey []byte + Signature []byte +} + +// PartialSigSorter implements sort.Interface. +type PartialSigSorter []*PartialSig + +func (s PartialSigSorter) Len() int { return len(s) } + +func (s PartialSigSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s PartialSigSorter) Less(i, j int) bool { + return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0 +} + +// validatePubkey checks if pubKey is *any* valid +// pubKey serialization in a Bitcoin context (compressed/uncomp. OK) +func validatePubkey(pubKey []byte) bool { + _, err := btcec.ParsePubKey(pubKey, btcec.S256()) + if err != nil { + return false + } + return true +} + +// validateSignature checks that the passed byte slice is a valid +// DER-encoded ECDSA signature, including the sighash flag. +// It does *not* of course validate the signature against any message +// or public key. +func validateSignature(sig []byte) bool { + _, err := btcec.ParseDERSignature(sig, btcec.S256()) + if err != nil { + return false + } + return true +} + +// See above notes (PartialSig, validatePubkey, validateSignature). +// NOTE update for Schnorr will be needed here if/when that activates. +func (ps *PartialSig) checkValid() bool { + return validatePubkey(ps.PubKey) && validateSignature(ps.Signature) +} + +// Bip32Derivation encapsulates the data for the input and output +// Bip32Derivation key-value fields. +type Bip32Derivation struct { + PubKey []byte + MasterKeyFingerprint uint32 + Bip32Path []uint32 +} + +// checkValid ensures that the PubKey in the Bip32Derivation +// struct is valid. +func (pb *Bip32Derivation) checkValid() bool { + return validatePubkey(pb.PubKey) +} + +// Bip32Sorter implements sort.Interface. +type Bip32Sorter []*Bip32Derivation + +func (s Bip32Sorter) Len() int { return len(s) } + +func (s Bip32Sorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s Bip32Sorter) Less(i, j int) bool { + return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0 +} + +// readBip32Derivation deserializes a byte slice containing +// chunks of 4 byte little endian encodings of uint32 values, +// the first of which is the masterkeyfingerprint and the remainder +// of which are the derivation path. +func readBip32Derivation(path []byte) (uint32, []uint32, error) { + + if len(path)%4 != 0 || len(path)/4-1 < 1 { + return 0, nil, ErrInvalidPsbtFormat + } + masterKeyInt := binary.LittleEndian.Uint32(path[:4]) + paths := make([]uint32, 0) + for i := 4; i < len(path); i += 4 { + paths = append(paths, binary.LittleEndian.Uint32(path[i:i+4])) + } + return masterKeyInt, paths, nil +} + +// SerializeBIP32Derivation takes a master key fingerprint +// as defined in BIP32, along with a path specified as a list +// of uint32 values, and returns a bytestring specifying the derivation +// in the format required by BIP174: +// // master key fingerprint (4) || child index (4) || child index (4) || ... +func SerializeBIP32Derivation(masterKeyFingerprint uint32, + bip32Path []uint32) []byte { + derivationPath := make([]byte, 0, 4+4*len(bip32Path)) + var masterKeyBytes [4]byte + binary.LittleEndian.PutUint32(masterKeyBytes[:], masterKeyFingerprint) + derivationPath = append(derivationPath, masterKeyBytes[:]...) + for _, path := range bip32Path { + var pathbytes [4]byte + binary.LittleEndian.PutUint32(pathbytes[:], path) + derivationPath = append(derivationPath, pathbytes[:]...) + } + return derivationPath +} + +// Unknown is a struct encapsulating a key-value pair for which +// the key type is unknown by this package; these fields are allowed +// in both the 'Global' and the 'Input' section of a PSBT. +type Unknown struct { + Key []byte + Value []byte +} + +// PInput is a struct encapsulating all the data that can be attached +// to any specific input of the PSBT. +type PInput struct { + NonWitnessUtxo *wire.MsgTx + WitnessUtxo *wire.TxOut + PartialSigs []*PartialSig + SighashType txscript.SigHashType + RedeemScript []byte + WitnessScript []byte + Bip32Derivation []*Bip32Derivation + FinalScriptSig []byte + FinalScriptWitness []byte + Unknowns []*Unknown +} + +// NewPsbtInput creates an instance of PsbtInput given either a +// nonWitnessUtxo or a witnessUtxo. +// NOTE only one of the two arguments should be specified, with the other +// being `nil`; otherwise the created PsbtInput object will fail IsSane() +// checks and will not be usable. +func NewPsbtInput(nonWitnessUtxo *wire.MsgTx, + witnessUtxo *wire.TxOut) *PInput { + return &PInput{ + NonWitnessUtxo: nonWitnessUtxo, + WitnessUtxo: witnessUtxo, + PartialSigs: []*PartialSig{}, + SighashType: 0, + RedeemScript: nil, + WitnessScript: nil, + Bip32Derivation: []*Bip32Derivation{}, + FinalScriptSig: nil, + FinalScriptWitness: nil, + Unknowns: nil, + } +} + +// IsSane returns true only if there are no conflicting +// values in the Psbt PInput. It checks that witness and non-witness +// utxo entries do not both exist, and that witnessScript entries are only +// added to witness inputs. +func (pi *PInput) IsSane() bool { + + if pi.NonWitnessUtxo != nil && pi.WitnessUtxo != nil { + return false + } + if pi.WitnessUtxo == nil && pi.WitnessScript != nil { + return false + } + if pi.WitnessUtxo == nil && pi.FinalScriptWitness != nil { + return false + } + + return true +} + +func (pi *PInput) deserialize(r io.Reader) error { + for { + keyint, keydata, err := getKey(r) + if err != nil { + return err + } + if keyint == -1 { + // Reached separator byte + break + } + value, err := wire.ReadVarBytes(r, 0, MaxPsbtValueLength, + "PSBT value") + if err != nil { + return err + } + + switch uint8(keyint) { + + case PsbtInNonWitnessUtxo: + if pi.NonWitnessUtxo != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + tx := wire.NewMsgTx(2) + if err := tx.Deserialize(bytes.NewReader(value)); err != nil { + return err + } + pi.NonWitnessUtxo = tx + + case PsbtInWitnessUtxo: + if pi.WitnessUtxo != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + txout, err := readTxOut(value) + if err != nil { + return err + } + pi.WitnessUtxo = txout + + case PsbtInPartialSig: + newPartialSig := PartialSig{PubKey: keydata, + Signature: value} + if !newPartialSig.checkValid() { + return ErrInvalidPsbtFormat + } + // Duplicate keys are not allowed + for _, x := range pi.PartialSigs { + if bytes.Equal(x.PubKey, newPartialSig.PubKey) { + return ErrDuplicateKey + } + } + pi.PartialSigs = append(pi.PartialSigs, &newPartialSig) + + case PsbtInSighashType: + if pi.SighashType != 0 { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + shtype := txscript.SigHashType(binary.LittleEndian.Uint32(value)) + pi.SighashType = shtype + + case PsbtInRedeemScript: + if pi.RedeemScript != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + pi.RedeemScript = value + + case PsbtInWitnessScript: + if pi.WitnessScript != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + pi.WitnessScript = value + + case PsbtInBip32Derivation: + if !validatePubkey(keydata) { + return ErrInvalidPsbtFormat + } + master, derivationPath, err := readBip32Derivation(value) + if err != nil { + return err + } + // Duplicate keys are not allowed + for _, x := range pi.Bip32Derivation { + if bytes.Equal(x.PubKey, keydata) { + return ErrDuplicateKey + } + } + pi.Bip32Derivation = append(pi.Bip32Derivation, + &Bip32Derivation{ + PubKey: keydata, + MasterKeyFingerprint: master, + Bip32Path: derivationPath, + }) + + case PsbtInFinalScriptSig: + if pi.FinalScriptSig != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + pi.FinalScriptSig = value + + case PsbtInFinalScriptWitness: + if pi.FinalScriptWitness != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + pi.FinalScriptWitness = value + + default: + keyintanddata := []byte{byte(keyint)} + keyintanddata = append(keyintanddata, keydata...) + newUnknown := &Unknown{ + Key: keyintanddata, + Value: value, + } + // Duplicate key+keydata are not allowed + for _, x := range pi.Unknowns { + if bytes.Equal(x.Key, newUnknown.Key) && bytes.Equal(x.Value, + newUnknown.Value) { + return ErrDuplicateKey + } + } + pi.Unknowns = append(pi.Unknowns, newUnknown) + } + } + return nil +} + +func (pi *PInput) serialize(w io.Writer) error { + + if !pi.IsSane() { + return ErrInvalidPsbtFormat + } + + if pi.NonWitnessUtxo != nil { + var buf bytes.Buffer + err := pi.NonWitnessUtxo.Serialize(&buf) + if err != nil { + return err + } + err = serializeKVPairWithType(w, PsbtInNonWitnessUtxo, nil, + buf.Bytes()) + if err != nil { + return err + } + } + if pi.WitnessUtxo != nil { + + var buf bytes.Buffer + err := wire.WriteTxOut(&buf, 0, 0, pi.WitnessUtxo) + if err != nil { + return err + } + err = serializeKVPairWithType(w, PsbtInWitnessUtxo, nil, buf.Bytes()) + if err != nil { + return err + } + } + if pi.FinalScriptSig == nil && pi.FinalScriptWitness == nil { + + sort.Sort(PartialSigSorter(pi.PartialSigs)) + for _, ps := range pi.PartialSigs { + err := serializeKVPairWithType(w, PsbtInPartialSig, ps.PubKey, + ps.Signature) + if err != nil { + return err + } + } + + if pi.SighashType != 0 { + var shtBytes [4]byte + binary.LittleEndian.PutUint32(shtBytes[:], + uint32(pi.SighashType)) + err := serializeKVPairWithType(w, PsbtInSighashType, nil, + shtBytes[:]) + if err != nil { + return err + } + } + if pi.RedeemScript != nil { + err := serializeKVPairWithType(w, PsbtInRedeemScript, nil, + pi.RedeemScript) + if err != nil { + return err + } + } + if pi.WitnessScript != nil { + err := serializeKVPairWithType(w, PsbtInWitnessScript, nil, + pi.WitnessScript) + if err != nil { + return err + } + } + + sort.Sort(Bip32Sorter(pi.Bip32Derivation)) + for _, kd := range pi.Bip32Derivation { + err := serializeKVPairWithType(w, PsbtInBip32Derivation, + kd.PubKey, + SerializeBIP32Derivation(kd.MasterKeyFingerprint, + kd.Bip32Path)) + if err != nil { + return err + } + } + } + + if pi.FinalScriptSig != nil { + err := serializeKVPairWithType(w, PsbtInFinalScriptSig, nil, + pi.FinalScriptSig) + if err != nil { + return err + } + } + + if pi.FinalScriptWitness != nil { + err := serializeKVPairWithType(w, PsbtInFinalScriptWitness, nil, + pi.FinalScriptWitness) + if err != nil { + return err + } + } + + // Unknown is a special case; we don't have a key type, only + // a key and a value field + for _, kv := range pi.Unknowns { + err := serializeKVpair(w, kv.Key, kv.Value) + if err != nil { + return err + } + } + + return nil +} + +// POutput is a struct encapsulating all the data that can be attached +// to any specific output of the PSBT. +type POutput struct { + RedeemScript []byte + WitnessScript []byte + Bip32Derivation []*Bip32Derivation +} + +// NewPsbtOutput creates an instance of PsbtOutput; the three parameters +// redeemScript, witnessScript and Bip32Derivation are all allowed to be +// `nil`. +func NewPsbtOutput(redeemScript []byte, witnessScript []byte, + bip32Derivation []*Bip32Derivation) *POutput { + return &POutput{ + RedeemScript: redeemScript, + WitnessScript: witnessScript, + Bip32Derivation: bip32Derivation, + } +} + +func (po *POutput) deserialize(r io.Reader) error { + for { + keyint, keydata, err := getKey(r) + if err != nil { + return err + } + if keyint == -1 { + // Reached separator byte + break + } + value, err := wire.ReadVarBytes(r, 0, MaxPsbtValueLength, + "PSBT value") + if err != nil { + return err + } + + switch uint8(keyint) { + + case PsbtOutRedeemScript: + if po.RedeemScript != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + po.RedeemScript = value + + case PsbtOutWitnessScript: + if po.WitnessScript != nil { + return ErrDuplicateKey + } + if keydata != nil { + return ErrInvalidKeydata + } + po.WitnessScript = value + + case PsbtOutBip32Derivation: + if !validatePubkey(keydata) { + return ErrInvalidKeydata + } + master, derivationPath, err := readBip32Derivation(value) + if err != nil { + return err + } + // Duplicate keys are not allowed + for _, x := range po.Bip32Derivation { + if bytes.Equal(x.PubKey, keydata) { + return ErrDuplicateKey + } + } + po.Bip32Derivation = append(po.Bip32Derivation, + &Bip32Derivation{ + PubKey: keydata, + MasterKeyFingerprint: master, + Bip32Path: derivationPath, + }) + + default: + // unknown type is allowed for inputs but not outputs + return ErrInvalidPsbtFormat + } + } + return nil +} + +func (po *POutput) serialize(w io.Writer) error { + if po.RedeemScript != nil { + err := serializeKVPairWithType(w, PsbtOutRedeemScript, nil, + po.RedeemScript) + if err != nil { + return err + } + } + if po.WitnessScript != nil { + err := serializeKVPairWithType(w, PsbtOutWitnessScript, nil, + po.WitnessScript) + if err != nil { + return err + } + } + sort.Sort(Bip32Sorter(po.Bip32Derivation)) + for _, kd := range po.Bip32Derivation { + err := serializeKVPairWithType(w, PsbtOutBip32Derivation, + kd.PubKey, + SerializeBIP32Derivation(kd.MasterKeyFingerprint, + kd.Bip32Path)) + if err != nil { + return err + } + } + return nil +} + +// Psbt is a set of 1 + N + M key-value pair lists, 1 global, +// defining the unsigned transaction structure with N inputs and M outputs. +// These key-value pairs can contain scripts, signatures, +// key derivations and other transaction-defining data. +type Psbt struct { + UnsignedTx *wire.MsgTx // Deserialization of unsigned tx + Inputs []PInput + Outputs []POutput + Unknowns []Unknown // Data of unknown type at global scope +} + +// validateUnsignedTx returns true if the transaction is unsigned. +// Note that more basic sanity requirements, +// such as the presence of inputs and outputs, is implicitly +// checked in the call to MsgTx.Deserialize() +func validateUnsignedTX(tx *wire.MsgTx) bool { + for _, tin := range tx.TxIn { + if len(tin.SignatureScript) != 0 || len(tin.Witness) != 0 { + return false + } + } + return true +} + +// NewPsbtFromUnsignedTx creates a new Psbt struct, without +// any signatures (i.e. only the global section is non-empty). +func NewPsbtFromUnsignedTx(tx *wire.MsgTx) (*Psbt, error) { + + if !validateUnsignedTX(tx) { + return nil, ErrInvalidRawTxSigned + } + + inSlice := make([]PInput, len(tx.TxIn)) + outSlice := make([]POutput, len(tx.TxOut)) + unknownSlice := make([]Unknown, 0) + + retPsbt := Psbt{ + UnsignedTx: tx, + Inputs: inSlice, + Outputs: outSlice, + Unknowns: unknownSlice, + } + + return &retPsbt, nil +} + +// NewPsbt returns a new instance of a Psbt struct created +// by reading from a byte slice. If the format is invalid, an error +// is returned. If the argument b64 is true, the passed byte slice +// is decoded from base64 encoding before processing. +// NOTE To create a Psbt from one's own data, rather than reading +// in a serialization from a counterparty, one should use a psbt.Creator. +func NewPsbt(psbtBytes []byte, b64 bool) (*Psbt, error) { + var err error + if b64 { + decoded := make([]byte, base64.StdEncoding.DecodedLen(len(psbtBytes))) + _, err = base64.StdEncoding.Decode(decoded, psbtBytes) + if err != nil { + return nil, err + } + psbtBytes = decoded + } + r := bytes.NewReader(psbtBytes) + // The Psbt struct does not store the fixed magic bytes, + // but they must be present or the serialization must + // be explicitly rejected. + var magic [5]byte + if _, err = io.ReadFull(r, magic[:]); err != nil { + return nil, err + } + if magic != psbtMagic { + return nil, ErrInvalidMagicBytes + } + + // Next we parse the GLOBAL section. + // There is currently only 1 known key type, UnsignedTx. + // We insist this exists first; unknowns are allowed, but + // only after. + keyint, keydata, err := getKey(r) + if err != nil { + return nil, err + } + if uint8(keyint) != PsbtGlobalUnsignedTx || keydata != nil { + return nil, ErrInvalidPsbtFormat + } + value, err := wire.ReadVarBytes(r, 0, MaxPsbtValueLength, + "PSBT value") + if err != nil { + return nil, err + } + + // Attempt to deserialize the unsigned transaction. + msgTx := wire.NewMsgTx(2) + err = msgTx.Deserialize(bytes.NewReader(value)) + if err != nil { + return nil, err + } + if !validateUnsignedTX(msgTx) { + return nil, ErrInvalidRawTxSigned + } + + // parse any unknowns that may be present, break at separator + unknownSlice := make([]Unknown, 0) + for { + keyint, keydata, err := getKey(r) + if err != nil { + return nil, ErrInvalidPsbtFormat + } + if keyint == -1 { + break + } + value, err := wire.ReadVarBytes(r, 0, MaxPsbtValueLength, + "PSBT value") + if err != nil { + return nil, err + } + keyintanddata := []byte{byte(keyint)} + keyintanddata = append(keyintanddata, keydata...) + newUnknown := Unknown{ + Key: keyintanddata, + Value: value, + } + unknownSlice = append(unknownSlice, newUnknown) + } + + // Next we parse the INPUT section + inSlice := make([]PInput, len(msgTx.TxIn)) + + for i := range msgTx.TxIn { + input := PInput{} + err = input.deserialize(r) + if err != nil { + return nil, err + } + inSlice[i] = input + } + + //Next we parse the OUTPUT section + outSlice := make([]POutput, len(msgTx.TxOut)) + + for i := range msgTx.TxOut { + output := POutput{} + err = output.deserialize(r) + if err != nil { + return nil, err + } + outSlice[i] = output + } + + // Populate the new Psbt object + newPsbt := Psbt{ + UnsignedTx: msgTx, + Inputs: inSlice, + Outputs: outSlice, + Unknowns: unknownSlice, + } + // Extended sanity checking is applied here + // to make sure the externally-passed Psbt follows + // all the rules. + if err = newPsbt.SanityCheck(); err != nil { + return nil, err + } + + return &newPsbt, nil +} + +// Serialize creates a binary serialization of the referenced +// Psbt struct with lexicographical ordering (by key) of the subsections +func (p *Psbt) Serialize() ([]byte, error) { + + serPsbt := []byte{} + serPsbt = append(serPsbt, psbtMagic[:]...) + + // Create serialization of unsignedtx + serializedTx := bytes.NewBuffer(make([]byte, 0, + p.UnsignedTx.SerializeSize())) + if err := p.UnsignedTx.Serialize(serializedTx); err != nil { + return nil, err + } + var buf bytes.Buffer + err := serializeKVPairWithType(&buf, PsbtGlobalUnsignedTx, + nil, serializedTx.Bytes()) + if err != nil { + return nil, err + } + serPsbt = append(serPsbt, buf.Bytes()...) + serPsbt = append(serPsbt, 0x00) + + for _, pInput := range p.Inputs { + var buf bytes.Buffer + err := pInput.serialize(&buf) + if err != nil { + return nil, err + } + serPsbt = append(serPsbt, buf.Bytes()...) + serPsbt = append(serPsbt, 0x00) + } + + for _, pOutput := range p.Outputs { + var buf bytes.Buffer + err := pOutput.serialize(&buf) + if err != nil { + return nil, err + } + serPsbt = append(serPsbt, buf.Bytes()...) + serPsbt = append(serPsbt, 0x00) + } + + return serPsbt, nil +} + +// B64Encode returns the base64 encoding of the serialization of +// the current PSBT, or an error if the encoding fails. +func (p *Psbt) B64Encode() (string, error) { + raw, err := p.Serialize() + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(raw), nil +} + +// IsComplete returns true only if all of the inputs are +// finalized; this is particularly important in that it decides +// whether the final extraction to a network serialized signed +// transaction will be possible. +func (p *Psbt) IsComplete() bool { + for i := 0; i < len(p.UnsignedTx.TxIn); i++ { + if !isFinalized(p, i) { + return false + } + } + return true +} + +// SanityCheck checks conditions on a PSBT to ensure that it obeys the +// rules of BIP174, and returns true if so, false if not. +func (p *Psbt) SanityCheck() error { + + if !validateUnsignedTX(p.UnsignedTx) { + return ErrInvalidRawTxSigned + } + + for _, tin := range p.Inputs { + if !tin.IsSane() { + return ErrInvalidPsbtFormat + } + } + + return nil +} diff --git a/psbt/psbt_test.go b/psbt/psbt_test.go new file mode 100644 index 0000000..23b1a9d --- /dev/null +++ b/psbt/psbt_test.go @@ -0,0 +1,1222 @@ +// 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 + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" +) + +// Test vectors from: +// // https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#test-vectors + +// createPsbtFromSignedTx is a utility function to create a PSBT from an +// already-signed transaction, so we can test reconstructing, signing and +// extracting it. Returned are: an unsigned transaction serialization, a list +// of scriptSigs, one per input, and a list of witnesses, one per input. +func createPsbtFromSignedTx(serializedSignedTx []byte) ( + *Psbt, [][]byte, []wire.TxWitness, error) { + tx := wire.NewMsgTx(2) + err := tx.Deserialize(bytes.NewReader(serializedSignedTx)) + if err != nil { + return nil, nil, nil, err + } + scriptSigs := make([][]byte, 0, len(tx.TxIn)) + witnesses := make([]wire.TxWitness, 0, len(tx.TxIn)) + tx2 := tx.Copy() + // Blank out signature info in inputs + for i, tin := range tx2.TxIn { + tin.SignatureScript = nil + scriptSigs = append(scriptSigs, tx.TxIn[i].SignatureScript) + tin.Witness = nil + witnesses = append(witnesses, tx.TxIn[i].Witness) + + } + // Outputs always contain (value, scriptPubkey so don't need amending) + + // Now tx2 is tx with all signing data stripped out + unsignedPsbt, err := NewPsbtFromUnsignedTx(tx2) + if err != nil { + return nil, nil, nil, err + } + return unsignedPsbt, scriptSigs, witnesses, nil +} + +// These are all valid PSBTs +var validPsbtHex = map[int]string{ + 0: "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000", + 1: "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000", + 2: "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001030401000000000000", + 3: "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000100df0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e13000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb8230800220202ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e9910b4a6ba670000008000000080020000800022020394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d0510b4a6ba6700000080010000800200008000", + 4: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + 5: "70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000", +} + +// These are all invalid PSBTs for the indicated +// reasons. +var invalidPsbtHex = map[int]string{ + // wire format, not PSBT format + 0: "0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300", + // missing outputs + 1: "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000", + // Filled in scriptSig in unsigned tx + 2: "70736274ff0100fd0a010200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be4000000006a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000", + // No unsigned tx + 3: "70736274ff000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000", + // Duplicate keys in an input + 4: "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000000", + // Invalid global transaction typed key + 5: "70736274ff020001550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid input witness utxo typed key + 6: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac000000000002010020955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid pubkey length for input partial signature typed key + 7: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87210203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid redeemscript typed key + 8: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01020400220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid witness script typed key + 9: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d568102050047522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid bip32 typed key + 10: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae210603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd10b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid non-witness utxo typed key + 11: "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f0000000000020000bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + // Invalid final scriptsig typed key + 12: "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000020700da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + // Invalid final script witness typed key + 13: "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903020800da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + // Invalid pubkey in output BIP32 derivation paths typed key + 14: "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00210203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58710d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + // Invalid input sighash type typed key + 15: "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0203000100000000010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00", + // Invalid output redeemscript typed key + 16: "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c0002000016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65010125512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00", + // Invalid output witnessScript typed key + 17: "70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c00010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a6521010025512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00", + // Additional cases outside the existing test vectors. + // Invalid duplicate PartialSig + 18: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", + // Invalid duplicate BIP32 derivation (different derivs, same key) + 19: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba670000008000000080050000800000", +} + +// This tests that valid PSBT serializations can be parsed +// into Psbt structs. +func TestReadValidPsbtAndReserialize(t *testing.T) { + for _, v := range validPsbtHex { + PsbtBytes, err := hex.DecodeString(v) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + testPsbt, err := NewPsbt(PsbtBytes, false) + if err != nil { + t.Fatalf("unable to parse psbt: %v", err) + } + t.Logf("Successfully parsed test, got transaction: %v", + spew.Sdump(testPsbt.UnsignedTx)) + raw, err := testPsbt.Serialize() + if err != nil { + t.Fatalf("Unable to serialize created Psbt: %v", err) + } + if !bytes.Equal(raw, PsbtBytes) { + t.Fatalf("Serialized PSBT didn't match: %v", + hex.EncodeToString(raw)) + } + } +} + +func TestReadInvalidPsbt(t *testing.T) { + for _, v := range invalidPsbtHex { + PsbtBytes, err := hex.DecodeString(v) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + _, err = NewPsbt(PsbtBytes, false) + if err == nil { + t.Fatalf("Incorrectly validated psbt: %v", + hex.EncodeToString(PsbtBytes)) + } + t.Logf("Correctly got error: %v", err) + } +} + +func TestSanityCheck(t *testing.T) { + // Test strategy: + // 1. Create an invalid PSBT from a serialization + // Then ensure that the sanity check fails. + // 2. Create a valid PSBT from a serialization + // Then create an updater, add a witness utxo to a non-witness + // utxo. + // Then ensure that the sanity check fails. + // Then add a witnessScript field to a non-witness utxo. + // Then ensure that the sanity check fails. + + // index 1 contains a psbt with two inputs, first non-witness, + // second witness. + psbtraw1, err := hex.DecodeString(validPsbtHex[1]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + psbt1, err := NewPsbt(psbtraw1, false) + if err != nil { + t.Fatalf("Unable to create Psbt struct: %v", err) + } + // Add a non-witness utxo field to input2 using raw insertion function, + // so that it becomes invalid, then NewUpdater should fail: + nonWitnessUtxoRaw, err := hex.DecodeString( + CUTestHexData["NonWitnessUtxo"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + nonWitnessUtxo := wire.NewMsgTx(2) + err = nonWitnessUtxo.Deserialize(bytes.NewReader(nonWitnessUtxoRaw)) + if err != nil { + t.Fatalf("Unable to deserialize: %v", err) + } + inputs1 := &psbt1.Inputs[1] + inputs1.NonWitnessUtxo = nonWitnessUtxo + // The PSBT is now in an inconsistent state; Updater creation should fail + updater, err := NewUpdater(psbt1) + if err == nil { + t.Fatalf("Failed to identify invalid PSBT state ( " + + "witness, non-witness fields)") + } + // Overwrite back with the correct psbt + psbtraw1, err = hex.DecodeString(validPsbtHex[1]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + psbt1, err = NewPsbt(psbtraw1, false) + updater, err = NewUpdater(psbt1) + if err != nil { + t.Fatalf("Unable to create Updater: %v", err) + } + // Create a fake non-witness utxo field to overlap with + // the existing witness input at index 1. + tx := wire.NewMsgTx(2) + err = tx.Deserialize(bytes.NewReader(nonWitnessUtxoRaw)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + err = updater.AddInNonWitnessUtxo(tx, 1) + if err == nil { + t.Fatalf("Incorrectly accepted Psbt with conflicting witness " + + "and non-witness utxo entries in the same input.") + } + // Now we try again; this time we try to add a witnessScript + // key-value pair to an input which is non-witness, which should + // also be rejected. + psbt2, err := NewPsbt(psbtraw1, false) + if err != nil { + t.Fatalf("Unable to create Psbt struct: %v", err) + } + updater2, err := NewUpdater(psbt2) + if err != nil { + t.Fatalf("Got error creating updater2: %v", err) + } + witnessScript, err := hex.DecodeString( + CUTestHexData["Input2WitnessScript"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + err = updater2.AddInWitnessScript(witnessScript, 0) + if err == nil { + t.Fatalf("Incorrectly accepted adding witness script field " + + "to non-witness utxo") + } + +} + +// Data for creation and updating tests +// =============================================================================== +var CUTestHexData = map[string]string{ + "scriptPubkey1": "0014d85c2b71d0060b09c9886aeb815e50991dda124d", + "scriptPubkey2": "001400aea9a2e5f0f876a588df5546e8742d1d87008f", + "txid1": "75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858", + "txid2": "1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83", + "COPsbtHex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000", + "NonWitnessUtxo": "0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000", + "WitnessUtxo": "00c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887", + // After adding witnessutxo and nonwitness utxo to inputs: + "UOPsbtHex": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887000000", + "Input1RedeemScript": "5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae", + "Input2RedeemScript": "00208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903", + "Input2WitnessScript": "522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae", + // After adding redeemscripts and witness scripts to inputs: + "UOPsbtHex2": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae000000", + // After adding bip32 derivations to inputs and outputs: + "UOPsbtHex3": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + //After adding sighash types to inputs + "UOPsbtHex4": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", +} + +// Just one example sanity check of B64 construction; after sighash appending above +var CUTestB64Data = map[string]string{ + "UOPsbtB644": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", +} + +var CUTestAmountData = map[string]int64{ + "amount1": 149990000, + "amount2": 100000000, + "amount3": 200000000, +} + +var CUTestIndexData = map[string]uint32{ + "index1": 0, + "index2": 1, +} + +var CUMasterKeyFingerPrint = "d90c6a4f" + +var CUTestPathData = map[string][]uint32{ + "dpath1": {0 + 0x80000000, 0 + 0x80000000, 0 + 0x80000000}, + "dpath2": {0 + 0x80000000, 0 + 0x80000000, 1 + 0x80000000}, + "dpath3": {0 + 0x80000000, 0 + 0x80000000, 2 + 0x80000000}, + "dpath4": {0 + 0x80000000, 0 + 0x80000000, 3 + 0x80000000}, + "dpath5": {0 + 0x80000000, 0 + 0x80000000, 4 + 0x80000000}, + "dpath6": {0 + 0x80000000, 0 + 0x80000000, 5 + 0x80000000}, +} + +var CUTestPubkeyData = map[string]string{ + "pub1": "029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f", + "pub2": "02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7", + "pub3": "03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc", + "pub4": "023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73", + "pub5": "03a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58771", + "pub6": "027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b50051096", +} + +// =============================================================================== + +func TestPsbtCreator(t *testing.T) { + spkOut1, err := hex.DecodeString(CUTestHexData["scriptPubkey1"]) + if err != nil { + t.Fatalf("Error: %v", err) + } + spkOut2, err := hex.DecodeString(CUTestHexData["scriptPubkey2"]) + if err != nil { + t.Fatalf("Error: %v", err) + } + out1 := wire.NewTxOut(CUTestAmountData["amount1"], spkOut1) + out2 := wire.NewTxOut(CUTestAmountData["amount2"], spkOut2) + outputs := []*wire.TxOut{out1, out2} + hash1, err := chainhash.NewHashFromStr(CUTestHexData["txid1"]) + if err != nil { + t.Fatalf("Error: %v", err) + } + prevOut1 := wire.NewOutPoint(hash1, uint32(0)) + hash2, err := chainhash.NewHashFromStr(CUTestHexData["txid2"]) + if err != nil { + t.Fatalf("Error: %v", err) + } + prevOut2 := wire.NewOutPoint(hash2, uint32(1)) + inputs := []*wire.OutPoint{prevOut1, prevOut2} + creator := Creator{} + // Check creation fails with invalid sequences: + nSequences := []uint32{wire.MaxTxInSequenceNum} + err = creator.CreatePsbt(inputs, outputs, int32(3), uint32(0), nSequences) + if err == nil { + t.Fatalf("Did not error when creating transaction with " + + "invalid nSequences") + } + nSequences = append(nSequences, wire.MaxTxInSequenceNum) + // Check creation fails with invalid version + err = creator.CreatePsbt(inputs, outputs, int32(3), uint32(0), nSequences) + if err == nil { + t.Fatalf("Did not error when creating transaction with " + + "invalid version (3)") + } + // Use valid data to create: + creator.CreatePsbt(inputs, outputs, int32(2), uint32(0), nSequences) + rawCreated, err := creator.Cpsbt.Serialize() + if err != nil { + t.Fatalf("Unable to serialize created Psbt: %v", err) + } + if CUTestHexData["COPsbtHex"] != hex.EncodeToString(rawCreated) { + t.Fatalf("Failed to create expected psbt, instead got: %v", + hex.EncodeToString(rawCreated)) + } + // Now simulate passing the created PSBT to an Updater + updater, err := NewUpdater(creator.Cpsbt) + if err != nil { + t.Fatalf("Unable to create Updater object") + } + tx := wire.NewMsgTx(2) + nonWitnessUtxoHex, err := hex.DecodeString( + CUTestHexData["NonWitnessUtxo"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + err = tx.Deserialize(bytes.NewReader(nonWitnessUtxoHex)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + witnessUtxoHex, err := hex.DecodeString( + CUTestHexData["WitnessUtxo"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + txout := wire.TxOut{Value: CUTestAmountData["amount3"], + PkScript: witnessUtxoHex[9:]} + err = updater.AddInNonWitnessUtxo(tx, 0) + if err != nil { + t.Fatalf("Unable to add NonWitness Utxo to inputs: %v", err) + } + err = updater.AddInWitnessUtxo(&txout, 1) + if err != nil { + t.Fatalf("Unable to add Witness Utxo to inputs: %v", err) + } + + rawUpdated, err := updater.Upsbt.Serialize() + if err != nil { + t.Fatalf("Unable to serialize updated Psbt: %v", err) + } + if CUTestHexData["UOPsbtHex"] != hex.EncodeToString(rawUpdated) { + t.Fatal("Failed to create valid updated PSBT after utxos") + } + input1RedeemScript, err := hex.DecodeString(CUTestHexData["Input1RedeemScript"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + err = updater.AddInRedeemScript(input1RedeemScript, 0) + if err != nil { + t.Fatalf("Unable to add redeem script: %v", err) + } + input2RedeemScript, err := hex.DecodeString(CUTestHexData["Input2RedeemScript"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + err = updater.AddInRedeemScript(input2RedeemScript, 1) + if err != nil { + t.Fatalf("Unable to add redeem script: %v", err) + } + input2WitnessScript, err := hex.DecodeString(CUTestHexData["Input2WitnessScript"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + err = updater.AddInWitnessScript(input2WitnessScript, 1) + if err != nil { + t.Fatalf("Unable to add witness script: %v", err) + } + rawUpdated, err = updater.Upsbt.Serialize() + if err != nil { + t.Fatalf("Unable to serialize updated Psbt: %v", err) + } + if CUTestHexData["UOPsbtHex2"] != hex.EncodeToString(rawUpdated) { + t.Fatal("Failed to create valid updated PSBT after redeem scripts") + } + masterKey, err := hex.DecodeString(CUMasterKeyFingerPrint) + masterKeyInt := binary.LittleEndian.Uint32(masterKey) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + input1Path1 := CUTestPathData["dpath1"] + input1Path2 := CUTestPathData["dpath2"] + input1Key1, err := hex.DecodeString(CUTestPubkeyData["pub1"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + input1Key2, err := hex.DecodeString(CUTestPubkeyData["pub2"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + err = updater.AddInBip32Derivation(masterKeyInt, input1Path1, input1Key1, 0) + if err != nil { + t.Fatal("Failed to add first key derivation for input 1") + } + err = updater.AddInBip32Derivation(masterKeyInt, input1Path2, input1Key2, 0) + if err != nil { + t.Fatal("Failed to add second key derivation for input 1") + } + input2Path1 := CUTestPathData["dpath3"] + input2Path2 := CUTestPathData["dpath4"] + input2Key1, err := hex.DecodeString(CUTestPubkeyData["pub3"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + input2Key2, err := hex.DecodeString(CUTestPubkeyData["pub4"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + // check invalid pubkeys are not accepted + borkedInput2Key1 := append([]byte{0xff}, input2Key1...) + err = updater.AddInBip32Derivation(masterKeyInt, input2Path1, + borkedInput2Key1, 1) + if err == nil { + t.Fatalf("Expected invalid pubkey, got: %v", err) + } + + err = updater.AddInBip32Derivation(masterKeyInt, input2Path1, input2Key1, 1) + if err != nil { + t.Fatal("Failed to add first key derivation for input 2") + } + err = updater.AddInBip32Derivation(masterKeyInt, input2Path2, input2Key2, 1) + if err != nil { + t.Fatal("Failed to add second key derivation for input 2") + } + output1Key1, err := hex.DecodeString(CUTestPubkeyData["pub5"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + output1Path := CUTestPathData["dpath5"] + + // check invalid pubkeys are not accepted + borkedOutput1Key1 := append([]byte{0xab}, output1Key1[:13]...) + err = updater.AddOutBip32Derivation(masterKeyInt, output1Path, + borkedOutput1Key1, 0) + if err == nil { + t.Fatalf("Expected invalid pubkey, got: %v", err) + } + + err = updater.AddOutBip32Derivation(masterKeyInt, output1Path, output1Key1, 0) + if err != nil { + t.Fatal("Failed to add key to first output") + } + output2Key1, err := hex.DecodeString(CUTestPubkeyData["pub6"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + output2Path := CUTestPathData["dpath6"] + err = updater.AddOutBip32Derivation(masterKeyInt, output2Path, output2Key1, 1) + if err != nil { + t.Fatal("Failed to add key to second output") + } + rawUpdated, err = updater.Upsbt.Serialize() + if err != nil { + t.Fatalf("Unable to serialize updated Psbt: %v", err) + } + if CUTestHexData["UOPsbtHex3"] != hex.EncodeToString(rawUpdated) { + t.Fatal("Failed to create valid updated PSBT after BIP32 derivations") + } + err = updater.AddInSighashType(txscript.SigHashType(1), 0) + if err != nil { + t.Fatal("Failed to add sighash type to first input") + } + err = updater.AddInSighashType(txscript.SigHashType(1), 1) + if err != nil { + t.Fatal("Failed to add sighash type to second input") + } + rawUpdated, err = updater.Upsbt.Serialize() + if err != nil { + t.Fatalf("Unable to serialize updated Psbt: %v", err) + } + if CUTestHexData["UOPsbtHex4"] != hex.EncodeToString(rawUpdated) { + t.Fatal("Failed to create valid updated PSBT after sighash types") + } + b644, err := updater.Upsbt.B64Encode() + if err != nil { + t.Fatalf("Unable to B64Encode updated Psbt: %v", err) + } + if b644 != CUTestB64Data["UOPsbtB644"] { + t.Fatalf("Failed to base64 encode updated PSBT after sighash "+ + "types: %v", b644) + } +} + +// Signing test data taken from +// https://github.com/achow101/bitcoin/blob/020628e3a4e88e36647eaf92bac4b3552796ac6a/test/functional/data/rpc_psbt.json +var signerPsbtData = map[string]string{ + "signer1Privkey1": "cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr", + "signer1Privkey2": "cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d", + "signer1PsbtB64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAQMEAQAAAAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQMEAQAAAAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "signer1Result": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + "signer2Privkey1": "cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au", + "signer2Privkey2": "cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE", + "signer2Psbt": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f000000800000008001000080010304010000000001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f0000008000000080020000800103040100000000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + "signer2Result": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", +} + +func TestPsbtSigner(t *testing.T) { + psbt1, err := NewPsbt([]byte(signerPsbtData["signer1PsbtB64"]), true) + if err != nil { + t.Fatalf("Failed to parse PSBT: %v", err) + } + psbtUpdater1 := Updater{ + Upsbt: psbt1, + } + sig1, err := hex.DecodeString("3044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01") + pub1, err := hex.DecodeString("029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f") + res, err := psbtUpdater1.Sign(0, sig1, pub1, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Error from adding signatures: %v %v", err, res) + } + sig2, err := hex.DecodeString("3044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01") + pub2, err := hex.DecodeString("03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc") + res, err = psbtUpdater1.Sign(1, sig2, pub2, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Error from adding signatures: %v %v", err, res) + } + signer1Result, err := hex.DecodeString(signerPsbtData["signer1Result"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + rawUpdated, err := psbtUpdater1.Upsbt.Serialize() + if err != nil { + t.Fatalf("Unable to serialize updated Psbt: %v", err) + } + if !bytes.Equal(rawUpdated, signer1Result) { + t.Fatalf("Failed to add signatures correctly") + } +} + +// Finalizer-extractor test + +var finalizerPsbtData = map[string]string{ + "finalizeb64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "finalize": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + "resultb64": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==", + "result": "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000", + "network": "0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000", +} + +func TestPsbtExtractor(t *testing.T) { + rawToFinalize, err := base64.StdEncoding.DecodeString( + finalizerPsbtData["finalizeb64"]) + if err != nil { + t.Fatalf("Error decoding b64: %v", err) + } + + psbt1, err := NewPsbt(rawToFinalize, false) + if err != nil { + t.Fatalf("Failed to parse PSBT: %v", err) + } + + for i := range psbt1.Inputs { + err = Finalize(psbt1, i) + if err != nil { + t.Fatalf("Error from finalizing PSBT: %v", err) + } + } + + finalizer1Result, err := base64.StdEncoding.DecodeString( + finalizerPsbtData["resultb64"]) + if err != nil { + t.Fatalf("Unable to decode b64: %v", err) + } + finalToNetworkExpected, err := hex.DecodeString(finalizerPsbtData["network"]) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + resultToNetwork, err := Extract(psbt1) + if err != nil { + t.Fatalf("Failed to extract: %v", err) + } + rawPsbt1, err := psbt1.Serialize() + if err != nil { + t.Fatalf("Unable to serialize updated Psbt: %v", err) + } + if !bytes.Equal(rawPsbt1, finalizer1Result) { + t.Fatalf("Failed to finalize transaction %x", rawPsbt1) + } + if !bytes.Equal(finalToNetworkExpected, resultToNetwork) { + t.Fatalf("Failed to network serialize transaction: %x", resultToNetwork) + } +} + +func TestImportFromCore1(t *testing.T) { + // This example #1 was created manually using Bitcoin Core 0.17 regtest. + // It contains two inputs, one p2wkh and one p2pkh (non-witness). + // We take the created PSBT as input, then add the fields for each input + // separately, then finalize and extract, and compare with the network + // serialized tx output from Core. + imported := "cHNidP8BAJwCAAAAAjaoF6eKeGsPiDQxxqqhFDfHWjBtZzRqmaZmvyCVWZ5JAQAAAAD/////RhypNiFfnQSMNpo0SGsgIvDOyMQFAYEHZXD5jp4kCrUAAAAAAP////8CgCcSjAAAAAAXqRQFWy8ScSkkhlGMwfOnx15YwRzApofwX5MDAAAAABepFAt4TyLfGnL9QY6GLYHbpSQj+QclhwAAAAAAAAAAAA==" + psbt1, err := NewPsbt([]byte(imported), true) + if err != nil { + t.Fatalf("Failed to parse PSBT: %v", err) + } + + // update with the first input's utxo (witness) and the second input's utxo + // (non-witness) + fundingTxInput1Hex := "02000000014f2cbac7d7691fafca30313097d79be9e78aa6670752fcb1fc15508e77586efb000000004847304402201b5568d7cab977ae0892840b779d84e36d62e42fd93b95e648aaebeacd2577d602201d2ebda2b0cddfa0c1a71d3cbcb602e7c9c860a41ed8b4d18d40c92ccbe92aed01feffffff028c636f91000000001600147447b6d7e6193499565779c8eb5184fcfdfee6ef00879303000000001600149e88f2828a074ebf64af23c2168d1816258311d72d010000" + fundingTxInput2Hex := "020000000001012f03f70c673d83d65da0e8d0db3867b3e7d7bfbd34fd6be65892042e57576eb00000000000feffffff028027128c000000001976a91485780899b61a5506f342bd67a2f635181f50c8b788acb8032c040000000017a914e2e3d32d42d6f043cab39708a6073301df5039db8702473044022047ae396fd8aba8f67482ad16e315fe680db585c1ac6422ffb18dacd9cf5bac350220321176fd6157ef51d9eae9230b0b5bd7dd29bb6247a879189e6aaa8091f3020201210368081f7ff37dfadbed407eba17b232f959e41e6ac78741192c805ebf80d487852f010000" + fundingTxInput1Bytes, err := hex.DecodeString(fundingTxInput1Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + txFund1 := wire.NewMsgTx(2) + err = txFund1.Deserialize(bytes.NewReader(fundingTxInput1Bytes)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + // First input is witness, take correct output: + txFund1Out := txFund1.TxOut[1] + + fundingTxInput2Bytes, err := hex.DecodeString(fundingTxInput2Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + txFund2 := wire.NewMsgTx(2) + err = txFund2.Deserialize(bytes.NewReader(fundingTxInput2Bytes)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + psbtupdater1 := Updater{Upsbt: psbt1} + psbtupdater1.AddInWitnessUtxo(txFund1Out, 0) + err = psbtupdater1.AddInNonWitnessUtxo(txFund2, 1) + if err != nil { + t.Fatalf("Error inserting non-witness utxo: %v", err) + } + + // Signing was done with Core; we manually insert the relevant input + // entries here. + sig1Hex := "304402200da03ac9890f5d724c42c83c2a62844c08425a274f1a5bca50dcde4126eb20dd02205278897b65cb8e390a0868c9582133c7157b2ad3e81c1c70d8fbd65f51a5658b01" + sig1, err := hex.DecodeString(sig1Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + pub1Hex := "024d6b24f372dd4551277c8df4ecc0655101e11c22894c8e05a3468409c865a72c" + pub1, err := hex.DecodeString(pub1Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + // Check that invalid pubkeys are not accepted. + pubInvalid := append(pub1, 0x00) + + res, err := psbtupdater1.Sign(0, sig1, pubInvalid, nil, nil) + if err == nil { + t.Fatalf("Incorrectly accepted invalid pubkey: %v", + pubInvalid) + } + + res, err = psbtupdater1.Sign(0, sig1, pub1, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Error from adding signatures: %v %v", err, res) + } + + sig2Hex := "3044022014eb9c4858f71c9f280bc68402aa742a5187f54c56c8eb07c902eb1eb5804e5502203d66656de8386b9b044346d5605f5ae2b200328fb30476f6ac993fc0dbb0455901" + sig2, err := hex.DecodeString(sig2Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + pub2Hex := "03b4c79acdf4e7d978bef4019c421e4c6c67044ed49d27322dc90e808d8080e862" + pub2, err := hex.DecodeString(pub2Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + // =============================================================== + // Before adding the signature, we'll make a new PSBT with + // modifications to the input data and check it fails sanity checks. + + // First an invalid tx: + psbtBorkedInput2, _ := NewPsbt([]byte(imported), true) + borkedUpdater, err := NewUpdater(psbtBorkedInput2) + if err != nil { + t.Fatalf("NewUpdater failed while trying to create borked "+ + "version: %v", err) + } + borkedUpdater.AddInWitnessUtxo(txFund1Out, 0) + + res, err = borkedUpdater.Sign(0, sig2, pub2, nil, nil) + if err != ErrInvalidSignatureForInput { + t.Fatalf("AddPartialSig succeeded, but should have failed "+ + "due to mismatch between pubkey and prevOut; err was: %v", err) + } + + // Next, a valid tx serialization, but not the right one + wrongTxBytes, err := hex.DecodeString("020000000001012d1d7b17356d0ad8232a5817d2d2fa5cd97d803c0ed03e013e97b65f4f1e5e7501000000171600147848cfb25bb163c7c63732615980a25eddbadc7bfeffffff022a8227630000000017a91472128ae6b6a1b74e499bedb5efb1cb09c9a6713287107240000000000017a91485f81cb970d854e2513ebf5c5b5d09e4509f4af3870247304402201c09aa8bcd18753ef01d8712a55eea5a0f69b6c4cc2944ac942264ff0662c91402201fc1390bf8b0023dd12ae78d7ec181124e106de57bc8f00812ae92bd024d3045012103ba077fc011aa59393bfe17cf491b3a02a9c4d39df122b2148322da0ec23508f459430800") + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + wrongTx := wire.NewMsgTx(2) + err = wrongTx.Deserialize(bytes.NewReader(wrongTxBytes)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + psbtBorkedInput2.Inputs[1] = *NewPsbtInput(wrongTx, nil) + res, err = borkedUpdater.Sign(1, sig2, pub2, nil, nil) + if err != ErrInvalidSignatureForInput { + t.Fatalf("Error should have been invalid sig for input, was: %v", err) + } + // ====================================================== + + res, err = psbtupdater1.Sign(1, sig2, pub2, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Failed to add signature to second input: %v %v", err, res) + } + + // Neither input (p2pkh and p2wkh) require redeem script nor witness script, + // so there are no more fields to add; we are ready to finalize. + err = Finalize(psbt1, 0) + if err != nil { + t.Fatalf("Failed to finalize the first input, %v", err) + } + if psbt1.IsComplete() { + t.Fatalf("PSBT was complete but has not been fully finalized") + } + err = Finalize(psbt1, 1) + if err != nil { + t.Fatalf("Failed to finalize second input, %v", err) + } + + networkSerializedTx, err := Extract(psbt1) + expectedTx := "0200000000010236a817a78a786b0f883431c6aaa11437c75a306d67346a99a666bf2095599e490100000000ffffffff461ca936215f9d048c369a34486b2022f0cec8c4050181076570f98e9e240ab5000000006a473044022014eb9c4858f71c9f280bc68402aa742a5187f54c56c8eb07c902eb1eb5804e5502203d66656de8386b9b044346d5605f5ae2b200328fb30476f6ac993fc0dbb04559012103b4c79acdf4e7d978bef4019c421e4c6c67044ed49d27322dc90e808d8080e862ffffffff028027128c0000000017a914055b2f1271292486518cc1f3a7c75e58c11cc0a687f05f93030000000017a9140b784f22df1a72fd418e862d81dba52423f90725870247304402200da03ac9890f5d724c42c83c2a62844c08425a274f1a5bca50dcde4126eb20dd02205278897b65cb8e390a0868c9582133c7157b2ad3e81c1c70d8fbd65f51a5658b0121024d6b24f372dd4551277c8df4ecc0655101e11c22894c8e05a3468409c865a72c0000000000" + expectedTxBytes, err := hex.DecodeString(expectedTx) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + if !bytes.Equal(expectedTxBytes, networkSerializedTx) { + t.Fatalf("The produced network transaction did not match the expected: %x \n %x \n", + networkSerializedTx, expectedTxBytes) + } + +} + +func TestImportFromCore2(t *testing.T) { + // This example #2 was created manually using Bitcoin Core 0.17 regtest. + // It contains two inputs, one p2sh-p2wkh and one fake utxo. + // The PSBT has been created with walletcreatepsbt and then partial-signed + // on the real input with walletprocessbst in Core. + // We first check that the updating here, using the Core created signature, + // redeem script and signature for the p2sh-p2wkh input, creates the + // same partial-signed intermediate transaction as Core did after + // walletprocesspsbt. + // We then attach a fake + // input of type p2sh-p2wsh, attach its witnessUtxo, redeemscript and + // witnessscript fields, and then finalize the whole transaction. Unlike + // the previous example, we cannot here compare with a Core produced + // network serialized final transaction, because of the fake input. + imported := "cHNidP8BAJsCAAAAAkxTQ+rig5QNnUS5nMc+Pccow4IcOJeQRcNNw+7p5ZA5AQAAAAD/////qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqoNAAAAAP////8CAIYOcAAAAAAWABQ1l7nn13RubTwqRQU2BnVV5WlXBWAxMbUAAAAAF6kUkiuXUjfWFgTp6nl/gf9+8zIWR6KHAAAAAAAAAAAA" + psbt1, err := NewPsbt([]byte(imported), true) + if err != nil { + t.Fatalf("Failed to parse PSBT: %v", err) + } + + // update with the first input's utxo, taken from its funding + // transaction + fundingTxInput1Hex := "02000000017b260536a3c17aee49c41a9b36fdf01a418e0c04df06fbabcb0d4f590b95d175000000006a473044022074a5a13159b6c12d77881c9501aa5c18616fb76c1809fc4d55f18a2e63159a6702200d1aa72be6056a41808898d24da93c0c0192cad65b7c2cc86e00b3e0fbbd57f601210212cc429d61fde565d0c2271a3e4fdb063cb49ae2257fa71460be753ceb56d175feffffff02bc060d8f0000000017a9140b56c31b5dc5a5a22c45a7850e707ad602d94a3087008352840000000017a9149f3679d67a9a486238764f618a93b82a7d999103879a000000" + fundingTxInput1Bytes, err := hex.DecodeString(fundingTxInput1Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + txFund1 := wire.NewMsgTx(2) + err = txFund1.Deserialize(bytes.NewReader(fundingTxInput1Bytes)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + // First input is witness, take correct output: + txFund1Out := txFund1.TxOut[1] + + psbtupdater1 := Updater{Upsbt: psbt1} + psbtupdater1.AddInWitnessUtxo(txFund1Out, 0) + + // This input is p2sh-p2wkh, so it requires a redeemscript but not + // a witness script. The redeemscript is the witness program. + redeemScript, err := hex.DecodeString("00147aed39420a8b7ab98a83791327ccb70819d1fbe2") + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + psbtupdater1.AddInRedeemScript(redeemScript, 0) + + // Signing for the first input was done with Core; we manually insert the + // relevant input entries here. + sig1Hex := "30440220546d182d00e45ef659c329dce6197dc19e0abc795e2c9279873f5a887998b273022044143113fc3475d04fc8d5113e0bbcb42d80514a9f1a2247e9b2a7878e20d44901" + sig1, err := hex.DecodeString(sig1Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + pub1Hex := "02bb3ce35af26f4c826eab3e5fc263ef56871b26686a8a995599b7ee6576613104" + pub1, err := hex.DecodeString(pub1Hex) + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + res, err := psbtupdater1.Sign(0, sig1, pub1, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Unable to add partial signature: %v %v", err, res) + } + + // Since this input is now finalizable, we do so: + err = Finalize(psbt1, 0) + if err != nil { + t.Fatalf("Failed to finalize the first input: %v", err) + } + if psbt1.IsComplete() { + t.Fatalf("PSBT was complete but has not been fully finalized") + } + + // Core also adds the OutRedeemScript field for the output it knows about. + // Note that usually we would not of course re-create, but rather start + // from the half-signed version; so this is needed only for a sanity check + // that we can recreate the half-signed. + output2RedeemScript, err := hex.DecodeString("0014e0846bd17848ab40ca1f56b655c6fa31667880cc") + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + psbtupdater1.AddOutRedeemScript(output2RedeemScript, 1) + // The main function of the test is to compare the thus-generated + // partially (not completely) signed transaction with that generated and + // encoded by Core. + expectedPsbtPartialB64 := "cHNidP8BAJsCAAAAAkxTQ+rig5QNnUS5nMc+Pccow4IcOJeQRcNNw+7p5ZA5AQAAAAD/////qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqoNAAAAAP////8CAIYOcAAAAAAWABQ1l7nn13RubTwqRQU2BnVV5WlXBWAxMbUAAAAAF6kUkiuXUjfWFgTp6nl/gf9+8zIWR6KHAAAAAAABASAAg1KEAAAAABepFJ82edZ6mkhiOHZPYYqTuCp9mZEDhwEHFxYAFHrtOUIKi3q5ioN5EyfMtwgZ0fviAQhrAkcwRAIgVG0YLQDkXvZZwync5hl9wZ4KvHleLJJ5hz9aiHmYsnMCIEQUMRP8NHXQT8jVET4LvLQtgFFKnxoiR+myp4eOINRJASECuzzjWvJvTIJuqz5fwmPvVocbJmhqiplVmbfuZXZhMQQAAAABABYAFOCEa9F4SKtAyh9WtlXG+jFmeIDMAA==" + generatedPsbtPartialB64, err := psbt1.B64Encode() + if err != nil { + t.Fatalf("Unable to B64Encode Psbt: %v", err) + } + if expectedPsbtPartialB64 != generatedPsbtPartialB64 { + t.Fatalf("Partial did not match expected: %v", generatedPsbtPartialB64) + } + + // We now simulate adding the signing data for the second (fake) input, + // and check that we can finalize and extract. This input is p2sh-p2wsh. + // the second input is fake, we're going to make it witness type, + // so create a TxOut struct that fits + fakeTxOutSerialized, err := hex.DecodeString("00c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + fakevalSerialized := binary.LittleEndian.Uint64(fakeTxOutSerialized[:8]) + fakeScriptPubKey := fakeTxOutSerialized[9:] + txFund2Out := wire.NewTxOut(int64(fakevalSerialized), fakeScriptPubKey) + psbt2, err := NewPsbt([]byte(expectedPsbtPartialB64), true) + if err != nil { + t.Fatalf("Failed to load partial PSBT: %v", err) + } + psbtupdater2, err := NewUpdater(psbt2) + if err != nil { + t.Fatalf("Failed to create updater: %v", err) + } + psbtupdater2.AddInWitnessUtxo(txFund2Out, 1) + // Add redeemScript, which is the witnessscript/program: + redeemScript, err = hex.DecodeString("00208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + err = psbtupdater2.AddInRedeemScript(redeemScript, 1) + if err != nil { + t.Fatalf("Failed to add redeemscript to second input: %v", err) + } + // Add witnessScript, which here is multisig: + witnessScript, err := hex.DecodeString("522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + // To test multisig checks, add a nonsense version of the multisig script + witnessScriptNonsense, err := hex.DecodeString("52ffff") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + err = psbtupdater2.AddInWitnessScript(witnessScript, 1) + if err != nil { + t.Fatalf("Failed to add witnessscript to second input: %v", err) + } + // Construct the two partial signatures to be added + sig21, err := hex.DecodeString("3044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + pub21, err := hex.DecodeString("03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + sig22, err := hex.DecodeString("3044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d201") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + pub22, err := hex.DecodeString("023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + res, err = psbtupdater2.Sign(1, sig21, pub21, nil, nil) + + // Check that the finalization procedure fails here due to not + // meeting the multisig policy + success, err := MaybeFinalize(psbt2, 1) + if success { + t.Fatalf("Incorrectly succeeded in finalizing without sigs") + } + if err != ErrUnsupportedScriptType { + t.Fatalf("Got unexpected error type: %v", err) + } + + res, err = psbtupdater2.Sign(1, sig22, pub22, nil, nil) + + // Check that the finalization procedure also fails with a nonsense + // script + err = psbtupdater2.AddInWitnessScript(witnessScriptNonsense, 1) + if err != nil { + t.Fatalf("Failed to add witnessscript to second input: %v", err) + } + success, err = MaybeFinalize(psbt2, 1) + if success { + t.Fatalf("Incorrectly succeeded in finalizing with invalid msigscript") + } + if err != ErrUnsupportedScriptType { + t.Fatalf("Got unexpected error type: %v", err) + } + + // Restore the correct witnessScript to complete correctly + err = psbtupdater2.AddInWitnessScript(witnessScript, 1) + if err != nil { + t.Fatalf("Failed to add witnessscript to second input: %v", err) + } + + success, err = MaybeFinalize(psbt2, 1) + if !success { + if err != nil { + t.Fatalf("Failed to finalize second input: %v", err) + } else { + t.Fatalf("Input was not finalizable") + } + } + + // Add a (fake) witnessOut descriptor field to one of the outputs, + // for coverage purposes (we aren't currently using this field) + psbtupdater2.AddOutWitnessScript([]byte{0xff, 0xff, 0xff}, 0) + + // Sanity check; we should not have lost the additional output entry + // provided by Core initially + uoutput1 := psbtupdater2.Upsbt.Outputs[1] + if uoutput1.RedeemScript == nil { + t.Fatalf("PSBT should contain outredeemscript entry, but it does not.") + } + // Nor should we have lost our fake witnessscript output entry + uoutput2 := psbtupdater2.Upsbt.Outputs[0] + if uoutput2.WitnessScript == nil { + t.Fatalf("PSBT should contain outwitnessscript but it does not.") + } + networkSerializedTx, err := Extract(psbt2) + expectedSerializedTx, err := hex.DecodeString("020000000001024c5343eae283940d9d44b99cc73e3dc728c3821c38979045c34dc3eee9e5903901000000171600147aed39420a8b7ab98a83791327ccb70819d1fbe2ffffffffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0d000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0200860e70000000001600143597b9e7d7746e6d3c2a450536067555e5695705603131b50000000017a914922b975237d61604e9ea797f81ff7ef3321647a287024730440220546d182d00e45ef659c329dce6197dc19e0abc795e2c9279873f5a887998b273022044143113fc3475d04fc8d5113e0bbcb42d80514a9f1a2247e9b2a7878e20d449012102bb3ce35af26f4c826eab3e5fc263ef56871b26686a8a995599b7ee65766131040400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000") + if err != nil { + t.Fatalf("Failed to decode hex: %v", err) + } + if !bytes.Equal(expectedSerializedTx, networkSerializedTx) { + t.Fatalf("Failed to create correct network serialized transaction: "+ + "%x\n", networkSerializedTx) + } +} + +func TestMaybeFinalizeAll(t *testing.T) { + // The following data is from a 3rd transaction from Core, + // using 3 inputs, all p2wkh. + imported := "cHNidP8BAKQCAAAAAzJyXH13IqBFvvZ7y1VSgUgkMvMoPgP5CfFNqsjQexKQAQAAAAD/////fMdLydu5bsoiHN9cFSaBL0Qnq2KLSKx0RA4b938CAgQAAAAAAP/////yKNgfsDAHr/zFz8R9k8EFI26allfg9DdE8Gzj6tGlegEAAAAA/////wHw9E0OAAAAABYAFDnPCRduiEWmmSc1j30SJ8k9u7PHAAAAAAAAAAAA" + psbt1, err := NewPsbt([]byte(imported), true) + if err != nil { + t.Fatalf("Failed to parse PSBT: %v", err) + } + + // update with the first input's utxo, taken from its funding + // transaction + fundingTxInput1, err := hex.DecodeString("020000000001017b260536a3c17aee49c41a9b36fdf01a418e0c04df06fbabcb0d4f590b95d1750100000017160014af82cd4409241b1de892726324bd780e3b5cd8aafeffffff02a85f9800000000001600149d21f8b306ddfd4dd035080689e88b4c3471e3cc801d2c0400000000160014d97ccd3dfb60820d7d33d862371ca5a73039bd560247304402201a1d2fdb5a7190b7fa59907769f0fc9c91fd3b34f6424acf5868a8ac21ec287102200a59b9d076ecf98c88f2196ed2be0aafff4966ead754041182fff5f92115a783012103604ffd31dc71db2e32c20f09eafe6353cd7515d3648aff829bb4879b553e30629a000000") + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + fundingTxInput2, err := hex.DecodeString("020000000001019c27b886e420fcadb077706b0933efa8bb53e3a250c3ec45cfdba5e05e233f360100000000feffffff0200b4c404000000001600140853f50c7d2d5d2af326a75efdbc83b62551e89afce31c0d000000001600142d6936c082c35607ec3bdb334a932d928150b75802473044022000d962f5e5e6425f9de21da7ac65b4fd8af8f6bfbd33c7ba022827c73866b477022034c59935c1ea10b5ba335d93f55a200c2588ec6058b8c7aedd10d5cbc4654f99012102c30e9f0cd98f6a805464d6b8a326b5679b6c3262934341855ee0436eaedfd2869a000000") + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + fundingTxInput3, err := hex.DecodeString("02000000012bf4331bb95df4eadb14f7a28db3fecdc5e87f08c29c2332b66338dd606699f60000000048473044022075ed43f508528da47673550a785702e9a93eca84a11faea91c4e9c66fcab3c9e022054a37610bd40b12263a5933188f062b718e007f290cecde2b6e41da3e1ebbddf01feffffff020c99a8240100000016001483bd916985726094d6d1c5b969722da580b5966a804a5d05000000001600140a2ee13a6696d75006af5e8a026ea49316087dae9a000000") + if err != nil { + t.Fatalf("Unable to decode hex: %v", err) + } + + psbtupdater1 := Updater{Upsbt: psbt1} + tx := wire.NewMsgTx(2) + err = tx.Deserialize(bytes.NewReader(fundingTxInput1)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + txFund1Out := tx.TxOut[1] + psbtupdater1.AddInWitnessUtxo(txFund1Out, 0) + + tx = wire.NewMsgTx(2) + err = tx.Deserialize(bytes.NewReader(fundingTxInput2)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + txFund2Out := tx.TxOut[0] + psbtupdater1.AddInWitnessUtxo(txFund2Out, 1) + + tx = wire.NewMsgTx(2) + err = tx.Deserialize(bytes.NewReader(fundingTxInput3)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + txFund3Out := tx.TxOut[1] + psbtupdater1.AddInWitnessUtxo(txFund3Out, 2) + + // To be ready for finalization, we need to have partial signature + // fields for each input + sig1, _ := hex.DecodeString("30440220027605ee8015970baf02a72652967a543e1b29a6882d799738ed1baee508822702203818a2f1b9770c46a473f47ad7ae90bcc129a5d047f00fae354c80197a7cf50601") + pub1, _ := hex.DecodeString("03235fc1f9dc8bbf6fa3df35dfeb0dd486f2d488f139579885eb684510f004f6c1") + sig2, _ := hex.DecodeString("304402206f5aea4621696610de48736b95a89b1d3a434a4e536d9aae65e039c477cf4c7202203b27a18b0f63be7d3bbf5be1bc2306a7ec8c2da12c2820ff07b73c7f3f1d4d7301") + pub2, _ := hex.DecodeString("022011b496f0603a268b55a781c7be0c3849f605f09cb2e917ed44288b8144a752") + sig3, _ := hex.DecodeString("3044022036dbc6f8f85a856e7803cbbcf0a97b7a74806fc592e92d7c06826f911610b98e0220111d43c4b20f756581791334d9c5cbb1a9c07558f28404cabf01c782897ad50501") + pub3, _ := hex.DecodeString("0381772a80c69e275e20d7f014555b13031e9cacf1c54a44a67ab2bc7eba64f227") + res, err := psbtupdater1.Sign(0, sig1, pub1, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Failed to add partial signature for input 0: %v %v", err, res) + } + res, err = psbtupdater1.Sign(1, sig2, pub2, nil, nil) + if err != nil || res != 0 { + t.Fatalf("Failed to add partial signature for input 1: %v %v", err, res) + } + + // Not ready for finalize all, check it fails: + err = MaybeFinalizeAll(psbt1) + if err != ErrNotFinalizable { + t.Fatalf("Expected finalization failure, got: %v", err) + } + + res, err = psbtupdater1.Sign(2, sig3, pub3, nil, nil) + + // Since this input is now finalizable and is p2wkh only, we can do + // all at once: + err = MaybeFinalizeAll(psbt1) + if err != nil { + t.Fatalf("Failed to finalize PSBT: %v", err) + } + if !psbt1.IsComplete() { + t.Fatalf("PSBT was finalized but not marked complete") + } + +} + +func TestFromUnsigned(t *testing.T) { + serTx, err := hex.DecodeString("00000000000101e165f072311e71825b47a4797221d7ae56d4b40b7707c540049aee43302448a40000000000feffffff0212f1126a0000000017a9143e836801b2b15aa193449d815c62d6c4b6227c898780778e060000000017a914ba4bdb0b07d67bc60f59c1f4fe54170565254974870000000000") + if err != nil { + t.Fatalf("Error: %v", err) + } + tx := wire.NewMsgTx(2) + err = tx.Deserialize(bytes.NewReader(serTx)) + if err != nil { + t.Fatalf("Error: %v", err) + } + psbt1, err := NewPsbtFromUnsignedTx(tx) + if err != nil { + t.Fatalf("Error: %v", err) + } + encoded, err := psbt1.B64Encode() + if err != nil { + t.Fatalf("Unable to B64Encode Psbt: %v", err) + } + + // Compare with output of Core: + fromCoreB64 := "cHNidP8BAHMAAAAAAeFl8HIxHnGCW0ekeXIh165W1LQLdwfFQASa7kMwJEikAAAAAAD+////AhLxEmoAAAAAF6kUPoNoAbKxWqGTRJ2BXGLWxLYifImHgHeOBgAAAAAXqRS6S9sLB9Z7xg9ZwfT+VBcFZSVJdIcAAAAAAAAAAA==" + if encoded != fromCoreB64 { + t.Fatalf("Got incorrect b64: %v", encoded) + } + _, err = NewPsbt([]byte(fromCoreB64), true) + if err != nil { + t.Fatalf("Error: %v", err) + } +} + +func TestNonWitnessToWitness(t *testing.T) { + // We'll start with a PSBT produced by Core for which + // the first input is signed and we'll provided the signatures for + // the other three inputs; they are p2sh-p2wkh, p2wkh and legacy + // respectively. + // In each case we'll *first* attach the NonWitnessUtxo field, + // and then call sign; in the first two but not the third case, the + // NonWitnessUtxo will automatically be replaced with the WitnessUtxo. + // Finally we'll check that the fully finalized PSBT produced matches + // the one produced by Core for the same keys. + + psbt1B64 := "cHNidP8BAM4CAAAABHtBMXY+SX95xidmWJP67CTQ02FPUpbNhIxNplAdlvk+AQAAAAD/////G2mt4bX7+sVi1jdbuBa5Q/xsJdgzFCgdHHSZq3ewK6YAAAAAAP/////NrbZb7GzfAg4kOqFWAIbXabq4cAvtVGv+eecIIv1KggEAAAAA/////73s9ifprgErlaONH1rgpNs3l6+t+mz2XGTHsTVWCem/AQAAAAD/////AfAmclMAAAAAF6kUQwsEC5nzbdY5meON2ZQ2thmeFgOHAAAAAAABASAAZc0dAAAAABepFPAv3VTMu5+4WN+/HIji6kG9RpzKhwEHFxYAFLN3PqXSyIHWKqm4ah5m9erc/3OoAQhrAkcwRAIgH7kzGO2iskfCvX0dgkDuzfqJ7tAu7KUZOeykTkJ1SYkCIBv4QRZK1hLz45D0gs+Lz93OE4s37lkPVE+SlXZtazWEASEC3jaf19MMferBn0Bn5lxXJGOqoqmfSvnHclQvB5gJ3nEAAAAAAQAWABTB+Qcq6iqdSvvc6959kd7XHrhYFgA=" + nwutxo1ser, _ := hex.DecodeString("02000000017f7baa6b7377541c4aca372d2dce8e1098ba44aa8379b7ea87644ef27e08ec240000000048473044022072e3b94c33cb5128518cd3903cc0ca19e8c234ac6d462e01ae2bb1da7768ed7d0220167d7ad89f6e1bbb3b866ae6fc2f67b5e7d51eb4f33f7bfe3f4b2673856b815001feffffff0200c2eb0b0000000017a9142dd25c78db2e2e09376eab9cb342e1b03005abe487e4ab953e0000000017a914120b8ca3fb4c7f852e30d4e3714fb64027a0b4c38721020000") + nwutxo2ser, _ := hex.DecodeString("0200000001f51b0bb5d945dd5532448a4d3fb88134d0bd90493813515f9c2ddb1fa15b9ba60000000048473044022047d83caf88d398245c006374bfa9f27ae968f5f51d640cacd5a214ed2cba397a02204519b26035496855f574a72b73bdcfa46d53995faf64c8f0ab394b628cc5383901feffffff020ccb9f3800000000160014e13544a3c718faa6c5ad7089a6660383c12b072700a3e11100000000160014a5439b477c116b79bd4c7c5131f3e58d54f27bb721020000") + nwutxo3ser, _ := hex.DecodeString("0200000001eb452f0fc9a8c39edb79f7174763f3cb25dc56db455926e411719a115ef16509000000004847304402205aa80cc615eb4b3f6e89696db4eadd192581a6c46f5c09807d3d98ece1d77355022025007e58c1992a1e5d877ee324bfe0a65db26d29f80941cfa277ac3efbcad2a701feffffff02bce9a9320000000017a9141590e852ac66eb8798afeb2a5ed67c568a2d6561870084d717000000001976a914a57ea05eacf94900d5fb92bccd273cfdb90af36f88ac21020000") + + nwutxo1 := wire.NewMsgTx(2) + err := nwutxo1.Deserialize(bytes.NewReader(nwutxo1ser)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + nwutxo2 := wire.NewMsgTx(2) + err = nwutxo2.Deserialize(bytes.NewReader(nwutxo2ser)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + nwutxo3 := wire.NewMsgTx(2) + err = nwutxo3.Deserialize(bytes.NewReader(nwutxo3ser)) + if err != nil { + t.Fatalf("Error deserializing transaction: %v", err) + } + + // import the PSBT + psbt1, err := NewPsbt([]byte(psbt1B64), true) + if err != nil { + t.Fatalf("Failed to create PSBT: %v", err) + } + + // check that we recognize the finality of the first input + if !isFinalized(psbt1, 0) { + t.Fatalf("First input incorrectly read as not finalized.") + } + + // Add NonWitnessUtxo fields for each of the other three inputs + u := Updater{Upsbt: psbt1} + u.AddInNonWitnessUtxo(nwutxo1, 1) + u.AddInNonWitnessUtxo(nwutxo2, 2) + u.AddInNonWitnessUtxo(nwutxo3, 3) + + // Signatures for each of those inputs were created with Core: + sig1, _ := hex.DecodeString("304402205676877e6162ce40a49ee5a74443cdc1e7915637c42da7b872c2ec2298fd371b02203c1d4a05b1e2a7a588d9ec9b8d4892d2cd59bebe0e777483477a0ec692ebbe6d01") + pub1, _ := hex.DecodeString("02534f23cb88a048b649672967263bd7570312d5d31d066fa7b303970010a77b2b") + redeemScript1, _ := hex.DecodeString("00142412be29368c0260cb841eecd9b59d7e01174aa1") + + sig2, _ := hex.DecodeString("3044022065d0a349709b8d8043cfd644cf6c196c1f601a22e1b3fdfbf8c0cc2a80fe2f1702207c87d36b666a8862e81ec5df288707f517d2f35ea1548feb82019de2c8de90f701") + pub2, _ := hex.DecodeString("0257d88eaf1e79b72ea0a33ae89b57dae95ea68499bdc6770257e010ab899f0abb") + + sig3, _ := hex.DecodeString("30440220290abcaacbd759c4f989762a9ee3468a9231788aab8f50bf65955d8597d8dd3602204d7e394f4419dc5392c6edba6945837458dd750a030ac67a746231903a8eb7db01") + pub3, _ := hex.DecodeString("0388025f50bb51c0469421ed13381f22f9d46a070ec2837e055c49c5876f0d0968") + + // Add the signatures and any scripts needed to the inputs + res, err := u.Sign(1, sig1, pub1, redeemScript1, nil) + if res != 0 || err != nil { + t.Fatalf("Failed to sign at index %v res %v err %v", 1, res, err) + } + res, err = u.Sign(2, sig2, pub2, nil, nil) + if res != 0 || err != nil { + t.Fatalf("Failed to sign at index %v res %v err %v", 2, res, err) + } + res, err = u.Sign(3, sig3, pub3, nil, nil) + if res != 0 || err != nil { + t.Fatalf("Failed to sign at index %v res %v err %v", 3, res, err) + } + + // Attempt to finalize the rest of the transaction + _, err = MaybeFinalize(psbt1, 1) + if err != nil { + t.Fatalf("Failed to finalize input 1 %v", err) + } + _, err = MaybeFinalize(psbt1, 2) + if err != nil { + t.Fatalf("Failed to finalize input 2 %v", err) + } + _, err = MaybeFinalize(psbt1, 3) + if err != nil { + t.Fatalf("Failed to finalize input 3 %v", err) + } + + // Finally we can check whether both the B64 encoding of the PSBT, + // and the final network serialized signed transaction, that we generated + // with Core using the 2 wallets, matches what this code produces: + expectedFinalizedPsbt := "cHNidP8BAM4CAAAABHtBMXY+SX95xidmWJP67CTQ02FPUpbNhIxNplAdlvk+AQAAAAD/////G2mt4bX7+sVi1jdbuBa5Q/xsJdgzFCgdHHSZq3ewK6YAAAAAAP/////NrbZb7GzfAg4kOqFWAIbXabq4cAvtVGv+eecIIv1KggEAAAAA/////73s9ifprgErlaONH1rgpNs3l6+t+mz2XGTHsTVWCem/AQAAAAD/////AfAmclMAAAAAF6kUQwsEC5nzbdY5meON2ZQ2thmeFgOHAAAAAAABASAAZc0dAAAAABepFPAv3VTMu5+4WN+/HIji6kG9RpzKhwEHFxYAFLN3PqXSyIHWKqm4ah5m9erc/3OoAQhrAkcwRAIgH7kzGO2iskfCvX0dgkDuzfqJ7tAu7KUZOeykTkJ1SYkCIBv4QRZK1hLz45D0gs+Lz93OE4s37lkPVE+SlXZtazWEASEC3jaf19MMferBn0Bn5lxXJGOqoqmfSvnHclQvB5gJ3nEAAQEgAMLrCwAAAAAXqRQt0lx42y4uCTduq5yzQuGwMAWr5IcBBxcWABQkEr4pNowCYMuEHuzZtZ1+ARdKoQEIawJHMEQCIFZ2h35hYs5ApJ7lp0RDzcHnkVY3xC2nuHLC7CKY/TcbAiA8HUoFseKnpYjZ7JuNSJLSzVm+vg53dINHeg7Gkuu+bQEhAlNPI8uIoEi2SWcpZyY711cDEtXTHQZvp7MDlwAQp3srAAEBHwCj4REAAAAAFgAUpUObR3wRa3m9THxRMfPljVTye7cBCGsCRzBEAiBl0KNJcJuNgEPP1kTPbBlsH2AaIuGz/fv4wMwqgP4vFwIgfIfTa2ZqiGLoHsXfKIcH9RfS816hVI/rggGd4sjekPcBIQJX2I6vHnm3LqCjOuibV9rpXqaEmb3GdwJX4BCriZ8KuwABAL0CAAAAAetFLw/JqMOe23n3F0dj88sl3FbbRVkm5BFxmhFe8WUJAAAAAEhHMEQCIFqoDMYV60s/bolpbbTq3RklgabEb1wJgH09mOzh13NVAiAlAH5YwZkqHl2HfuMkv+CmXbJtKfgJQc+id6w++8rSpwH+////ArzpqTIAAAAAF6kUFZDoUqxm64eYr+sqXtZ8VootZWGHAITXFwAAAAAZdqkUpX6gXqz5SQDV+5K8zSc8/bkK82+IrCECAAABB2pHMEQCICkKvKrL11nE+Yl2Kp7jRoqSMXiKq49Qv2WVXYWX2N02AiBNfjlPRBncU5LG7bppRYN0WN11CgMKxnp0YjGQOo632wEhA4gCX1C7UcBGlCHtEzgfIvnUagcOwoN+BVxJxYdvDQloAAEAFgAUwfkHKuoqnUr73OvefZHe1x64WBYA" + calculatedPsbt, err := u.Upsbt.B64Encode() + if err != nil { + t.Fatalf("Failed to base64 encode") + } + if expectedFinalizedPsbt != calculatedPsbt { + t.Fatalf("Failed to generate correct PSBT") + } + + expectedNetworkSer, _ := hex.DecodeString("020000000001047b4131763e497f79c627665893faec24d0d3614f5296cd848c4da6501d96f93e0100000017160014b3773ea5d2c881d62aa9b86a1e66f5eadcff73a8ffffffff1b69ade1b5fbfac562d6375bb816b943fc6c25d83314281d1c7499ab77b02ba600000000171600142412be29368c0260cb841eecd9b59d7e01174aa1ffffffffcdadb65bec6cdf020e243aa1560086d769bab8700bed546bfe79e70822fd4a820100000000ffffffffbdecf627e9ae012b95a38d1f5ae0a4db3797afadfa6cf65c64c7b1355609e9bf010000006a4730440220290abcaacbd759c4f989762a9ee3468a9231788aab8f50bf65955d8597d8dd3602204d7e394f4419dc5392c6edba6945837458dd750a030ac67a746231903a8eb7db01210388025f50bb51c0469421ed13381f22f9d46a070ec2837e055c49c5876f0d0968ffffffff01f02672530000000017a914430b040b99f36dd63999e38dd99436b6199e1603870247304402201fb93318eda2b247c2bd7d1d8240eecdfa89eed02eeca51939eca44e4275498902201bf841164ad612f3e390f482cf8bcfddce138b37ee590f544f9295766d6b3584012102de369fd7d30c7deac19f4067e65c572463aaa2a99f4af9c772542f079809de710247304402205676877e6162ce40a49ee5a74443cdc1e7915637c42da7b872c2ec2298fd371b02203c1d4a05b1e2a7a588d9ec9b8d4892d2cd59bebe0e777483477a0ec692ebbe6d012102534f23cb88a048b649672967263bd7570312d5d31d066fa7b303970010a77b2b02473044022065d0a349709b8d8043cfd644cf6c196c1f601a22e1b3fdfbf8c0cc2a80fe2f1702207c87d36b666a8862e81ec5df288707f517d2f35ea1548feb82019de2c8de90f701210257d88eaf1e79b72ea0a33ae89b57dae95ea68499bdc6770257e010ab899f0abb0000000000") + serializedtx, err := Extract(psbt1) + if err != nil { + t.Fatalf("Failed to extract: %v", err) + } + if !bytes.Equal(expectedNetworkSer, serializedtx) { + t.Fatalf("Expected serialized transaction was not produced: %x", serializedtx) + } +} diff --git a/psbt/signer.go b/psbt/signer.go new file mode 100644 index 0000000..f1b1df5 --- /dev/null +++ b/psbt/signer.go @@ -0,0 +1,132 @@ +// 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 + +// signer encapsulates the role 'Signer' +// as specified in BIP174; it controls the insertion of signatures; +// the Sign() function will attempt to insert signatures using +// Updater.addPartialSignature, after first ensuring the Psbt is in the +// correct state. + +import ( + "github.com/btcsuite/btcd/txscript" +) + +// Sign allows the caller to sign a PSBT at a particular input; they +// must provide a signature and a pubkey, both as byte slices; they can also +// optionally provide both witnessScript and/or redeemScript, otherwise +// these arguments must be set as nil (and in that case, they must already +// be present in the PSBT if required for signing to succeed). +// +// Return value: +// 0 indicates that the partial signature was successfully attached. +// 1 indicates that this input is already finalized, so the provided +// signature was *not* attached +// -1 indicates that the provided signature data was not valid. In this +// case an error will also be returned. +// +// This serves as a wrapper around Updater.addPartialSignature; +// it ensures that the redeemScript and witnessScript are updated as needed +// (note that the Updater is allowed to add redeemScripts and witnessScripts +// independently, before signing), and ensures that the right form of utxo +// field (NonWitnessUtxo or WitnessUtxo) is included in the input so that +// signature insertion (and then finalization) can take place. +func (u *Updater) Sign(inIndex int, sig []byte, pubKey []byte, + redeemScript []byte, witnessScript []byte) (int, error) { + + if isFinalized(u.Upsbt, inIndex) { + return 1, nil + } + + if witnessScript != nil { + // Add the witnessScript to the PSBT in preparation. + // If it already exists, it will be overwritten. + err := u.AddInWitnessScript(witnessScript, inIndex) + if err != nil { + return -1, err + } + } + + if redeemScript != nil { + // Add the redeemScript to the PSBT in preparation. + // If it already exists, it will be overwritten. + err := u.AddInRedeemScript(redeemScript, inIndex) + if err != nil { + return -1, err + } + } + + // At this point, the PSBT must have the requisite + // witnessScript or redeemScript fields for signing to succeed. + + // case 1: if witnessScript is present, it must be of type witness; + // if not, signature insertion will of course fail. + if u.Upsbt.Inputs[inIndex].WitnessScript != nil { + if u.Upsbt.Inputs[inIndex].WitnessUtxo == nil { + err := nonWitnessToWitness(u.Upsbt, inIndex) + if err != nil { + return -1, err + } + } + err := u.addPartialSignature(inIndex, sig, pubKey) + if err != nil { + return -1, err + } + } else if u.Upsbt.Inputs[inIndex].RedeemScript != nil { + // case 2: no witness script, only redeem script; can be legacy + // p2sh or p2sh-wrapped p2wkh + // We only need to decide if the input is witness, and we don't + // rely on the witnessutxo/nonwitnessutxo in the PSBT, instead + // we check the redeemScript content: + if txscript.IsWitnessProgram(redeemScript) { + if u.Upsbt.Inputs[inIndex].WitnessUtxo == nil { + err := nonWitnessToWitness(u.Upsbt, inIndex) + if err != nil { + return -1, err + } + } + } + // If it is not a valid witness program, we here assume + // that the provided WitnessUtxo/NonWitnessUtxo field was correct. + err := u.addPartialSignature(inIndex, sig, pubKey) + if err != nil { + return -1, err + } + } else { + // case 3: Neither provided only works for native p2wkh, or + // non-segwit non-p2sh. To check if it's segwit, check + // the scriptPubKey of the output. + if u.Upsbt.Inputs[inIndex].WitnessUtxo == nil { + outIndex := u.Upsbt.UnsignedTx.TxIn[inIndex]. + PreviousOutPoint.Index + script := u.Upsbt.Inputs[inIndex].NonWitnessUtxo. + TxOut[outIndex].PkScript + if txscript.IsWitnessProgram(script) { + err := nonWitnessToWitness(u.Upsbt, inIndex) + if err != nil { + return -1, err + } + } + } + err := u.addPartialSignature(inIndex, sig, pubKey) + if err != nil { + return -1, err + } + } + return 0, nil +} + +// nonWitnessToWitness extracts the TxOut from the existing +// NonWitnessUtxo field in the given PSBT input and sets it as type +// witness by replacing the NonWitnessUtxo field with a WitnessUtxo +// field. See https://github.com/bitcoin/bitcoin/pull/14197 +func nonWitnessToWitness(p *Psbt, inIndex int) error { + outIndex := p.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Index + txout := p.Inputs[inIndex].NonWitnessUtxo.TxOut[outIndex] + // Remove the non-witness first, else sanity check will not pass: + p.Inputs[inIndex].NonWitnessUtxo = nil + u := Updater{Upsbt: p} + return u.AddInWitnessUtxo(txout, inIndex) +} diff --git a/psbt/updater.go b/psbt/updater.go new file mode 100644 index 0000000..434e05b --- /dev/null +++ b/psbt/updater.go @@ -0,0 +1,312 @@ +// 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 Updater requires provision of a single PSBT and is able +// to add data to both input and output sections. +// It can be called repeatedly to add more data. +// It also allows addition of signatures via the addPartialSignature +// function; this is called internally to the package in the Sign() +// function of Updater, located in signer.go + +import ( + "bytes" + "crypto/sha256" + + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" +) + +// Updater encapsulates the role 'Updater' +// as specified in BIP174; it accepts Psbt structs +// and has methods to add fields to the inputs and outputs. +type Updater struct { + Upsbt *Psbt +} + +// NewUpdater returns a new instance of Updater, +// if the passed Psbt struct is in a valid form, +// else an error. +func NewUpdater(p *Psbt) (*Updater, error) { + if err := p.SanityCheck(); err != nil { + return nil, err + } + return &Updater{Upsbt: p}, nil + +} + +// AddInNonWitnessUtxo adds the utxo information for an input which +// is non-witness. This requires provision of a full transaction +// (which is the source of the corresponding prevOut), and the input +// index. If addition of this key-value pair to the Psbt fails, an +// error is returned. +func (p *Updater) AddInNonWitnessUtxo(tx *wire.MsgTx, inIndex int) error { + if inIndex > len(p.Upsbt.Inputs)-1 { + return ErrInvalidPrevOutNonWitnessTransaction + } + p.Upsbt.Inputs[inIndex].NonWitnessUtxo = tx + if err := p.Upsbt.SanityCheck(); err != nil { + return ErrInvalidPsbtFormat + } + return nil +} + +// AddInWitnessUtxo adds the utxo information for an input which +// is witness. This requires provision of a full transaction *output* +// (which is the source of the corresponding prevOut); not the full +// transaction because BIP143 means the output information is sufficient, +// and the input index. If addition of this key-value pair to the Psbt fails, +// an error is returned. +func (p *Updater) AddInWitnessUtxo(txout *wire.TxOut, inIndex int) error { + if inIndex > len(p.Upsbt.Inputs)-1 { + return ErrInvalidPsbtFormat + } + p.Upsbt.Inputs[inIndex].WitnessUtxo = txout + if err := p.Upsbt.SanityCheck(); err != nil { + return ErrInvalidPsbtFormat + } + return nil +} + +// addPartialSignature allows the Updater role to insert fields +// of type partial signature into a Psbt, consisting of both +// the pubkey (as keydata) and the ECDSA signature (as value). +// Note that the Signer role is encapsulated in this function; +// signatures are only allowed to be added that follow the sanity-check +// on signing rules explained in the BIP under `Signer`; if the rules are not +// satisfied, an ErrInvalidSignatureForInput is returned. +// NOTE this function does *not* validate the ECDSA signature itself. +func (p *Updater) addPartialSignature(inIndex int, sig []byte, + pubkey []byte) error { + + partialSig := PartialSig{PubKey: pubkey, Signature: sig} + //First validate the passed (sig, pub): + if !partialSig.checkValid() { + return ErrInvalidPsbtFormat + } + + pInput := p.Upsbt.Inputs[inIndex] + + // First check; don't add duplicates + for _, x := range pInput.PartialSigs { + if bytes.Equal(x.PubKey, partialSig.PubKey) { + return ErrDuplicateKey + } + } + + // Sanity checks + if pInput.NonWitnessUtxo != nil { + if len(p.Upsbt.UnsignedTx.TxIn) < inIndex+1 { + return ErrInvalidPrevOutNonWitnessTransaction + } + if pInput.NonWitnessUtxo.TxHash() != + p.Upsbt.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Hash { + return ErrInvalidSignatureForInput + } + if pInput.RedeemScript != nil { + // To validate that the redeem script matches, we must pull out the + // scriptPubKey of the corresponding output and compare that with + // the P2SH scriptPubKey that is generated by redeemScript: + outIndex := p.Upsbt.UnsignedTx.TxIn[inIndex].PreviousOutPoint.Index + scriptPubKey := pInput.NonWitnessUtxo.TxOut[outIndex].PkScript + scriptHash := btcutil.Hash160(pInput.RedeemScript) + scriptHashScript, err := txscript.NewScriptBuilder().AddOp( + txscript.OP_HASH160).AddData(scriptHash).AddOp( + txscript.OP_EQUAL).Script() + if err != nil { + return err + } + if !bytes.Equal(scriptHashScript, scriptPubKey) { + return ErrInvalidSignatureForInput + } + } + } else if pInput.WitnessUtxo != nil { + scriptPubKey := pInput.WitnessUtxo.PkScript + var script []byte + if pInput.RedeemScript != nil { + scriptHash := btcutil.Hash160(pInput.RedeemScript) + scriptHashScript, err := txscript.NewScriptBuilder().AddOp( + txscript.OP_HASH160).AddData(scriptHash).AddOp( + txscript.OP_EQUAL).Script() + if err != nil { + return err + } + if !bytes.Equal(scriptHashScript, scriptPubKey) { + return ErrInvalidSignatureForInput + } + script = pInput.RedeemScript + } else { + script = scriptPubKey + } + // If a witnessScript field is present, this is a P2WSH, + // whether nested or not (that is handled by the assignment to + // `script` above); in that case, sanity check that `script` + // is the p2wsh of witnessScript. Contrariwise, if no witnessScript + // field is present, this will be signed as p2wkh. + if pInput.WitnessScript != nil { + witnessScriptHash := sha256.Sum256(pInput.WitnessScript) + witnessScriptHashScript, err := txscript.NewScriptBuilder().AddOp( + txscript.OP_0).AddData(witnessScriptHash[:]).Script() + if err != nil { + return err + } + if !bytes.Equal(script, witnessScriptHashScript[:]) { + return ErrInvalidSignatureForInput + } + } else { // p2wkh + pubkeyHash := btcutil.Hash160(pubkey) + pubkeyHashScript, err := txscript.NewScriptBuilder().AddOp( + txscript.OP_0).AddData(pubkeyHash).Script() + if err != nil { + return err + } + if !bytes.Equal(pubkeyHashScript, script) { + return ErrInvalidSignatureForInput + } + } + } else { + // attaching signature without utxo field is not allowed + return ErrInvalidPsbtFormat + } + + p.Upsbt.Inputs[inIndex].PartialSigs = append( + p.Upsbt.Inputs[inIndex].PartialSigs, &partialSig) + + if err := p.Upsbt.SanityCheck(); err != nil { + return err + } + // Addition of a non-duplicate-key partial signature + // cannot violate sanity-check rules. + return nil +} + +// AddInSighashType adds the sighash type information for an input. +// The sighash type is passed as a 32 bit unsigned integer, along with the +// index for the input. An error is returned if addition of this key-value pair +// to the Psbt fails. +func (p *Updater) AddInSighashType(sighashType txscript.SigHashType, + inIndex int) error { + p.Upsbt.Inputs[inIndex].SighashType = sighashType + if err := p.Upsbt.SanityCheck(); err != nil { + return err + } + return nil +} + +// AddInRedeemScript adds the redeem script information for an input. +// The redeem script is passed serialized, as a byte slice, along with the +// index of the input. An error is returned if addition of this key-value pair +// to the Psbt fails. +func (p *Updater) AddInRedeemScript(redeemScript []byte, + inIndex int) error { + p.Upsbt.Inputs[inIndex].RedeemScript = redeemScript + if err := p.Upsbt.SanityCheck(); err != nil { + return ErrInvalidPsbtFormat + } + return nil +} + +// AddInWitnessScript adds the witness script information for an input. +// The witness script is passed serialized, as a byte slice, along with the +// index of the input. An error is returned if addition of this key-value pair +// to the Psbt fails. +func (p *Updater) AddInWitnessScript(witnessScript []byte, + inIndex int) error { + p.Upsbt.Inputs[inIndex].WitnessScript = witnessScript + if err := p.Upsbt.SanityCheck(); err != nil { + return err + } + return nil +} + +// AddInBip32Derivation takes a master key fingerprint +// as defined in BIP32, a BIP32 path as a slice of uint32 values, +// and a serialized pubkey as a byte slice, along with the +// integer index of the input, and inserts this data into that input. +// NOTE that this can be called multiple times for the same input. +// An error is returned if addition of this key-value pair +// to the Psbt fails. +func (p *Updater) AddInBip32Derivation(masterKeyFingerprint uint32, + bip32Path []uint32, pubKeyData []byte, inIndex int) error { + bip32Derivation := Bip32Derivation{ + PubKey: pubKeyData, + MasterKeyFingerprint: masterKeyFingerprint, + Bip32Path: bip32Path, + } + + if !bip32Derivation.checkValid() { + return ErrInvalidPsbtFormat + } + + // Don't allow duplicate keys + for _, x := range p.Upsbt.Inputs[inIndex].Bip32Derivation { + if bytes.Equal(x.PubKey, bip32Derivation.PubKey) { + return ErrDuplicateKey + } + } + + p.Upsbt.Inputs[inIndex].Bip32Derivation = append( + p.Upsbt.Inputs[inIndex].Bip32Derivation, &bip32Derivation) + if err := p.Upsbt.SanityCheck(); err != nil { + return err + } + return nil +} + +// AddOutBip32Derivation takes a master key fingerprint +// as defined in BIP32, a BIP32 path as a slice of uint32 values, +// and a serialized pubkey as a byte slice, along with the +// integer index of the output, and inserts this data into that output. +// NOTE that this can be called multiple times for the same output. +// An error is returned if addition of this key-value pair +// to the Psbt fails. +func (p *Updater) AddOutBip32Derivation(masterKeyFingerprint uint32, + bip32Path []uint32, pubKeyData []byte, outIndex int) error { + bip32Derivation := Bip32Derivation{ + PubKey: pubKeyData, + MasterKeyFingerprint: masterKeyFingerprint, + Bip32Path: bip32Path, + } + if !bip32Derivation.checkValid() { + return ErrInvalidPsbtFormat + } + + // Don't allow duplicate keys + for _, x := range p.Upsbt.Outputs[outIndex].Bip32Derivation { + if bytes.Equal(x.PubKey, bip32Derivation.PubKey) { + return ErrDuplicateKey + } + } + + p.Upsbt.Outputs[outIndex].Bip32Derivation = append( + p.Upsbt.Outputs[outIndex].Bip32Derivation, &bip32Derivation) + if err := p.Upsbt.SanityCheck(); err != nil { + return err + } + return nil +} + +// AddOutRedeemScript takes a redeem script as a byte slice +// and appends it to the output at index outIndex. +func (p *Updater) AddOutRedeemScript(redeemScript []byte, + outIndex int) error { + p.Upsbt.Outputs[outIndex].RedeemScript = redeemScript + if err := p.Upsbt.SanityCheck(); err != nil { + return ErrInvalidPsbtFormat + } + return nil +} + +// AddOutWitnessScript takes a witness script as a byte slice +// and appends it to the output at index outIndex. +func (p *Updater) AddOutWitnessScript(witnessScript []byte, + outIndex int) error { + p.Upsbt.Outputs[outIndex].WitnessScript = witnessScript + if err := p.Upsbt.SanityCheck(); err != nil { + return err + } + return nil +}