PSBT BIP 174 implementation (#126)
Implements: PSBT struct, roles: creator, updater, signer, extractor. Passing test vectors.
This commit is contained in:
parent
9e5f4b9a99
commit
e17c9730c4
7 changed files with 3274 additions and 0 deletions
70
psbt/creator.go
Normal file
70
psbt/creator.go
Normal file
|
@ -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
|
||||||
|
}
|
66
psbt/extractor.go
Normal file
66
psbt/extractor.go
Normal file
|
@ -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
|
||||||
|
}
|
485
psbt/finalizer.go
Normal file
485
psbt/finalizer.go
Normal file
|
@ -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
|
||||||
|
}
|
987
psbt/psbt.go
Normal file
987
psbt/psbt.go
Normal file
|
@ -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
|
||||||
|
}
|
1222
psbt/psbt_test.go
Normal file
1222
psbt/psbt_test.go
Normal file
File diff suppressed because it is too large
Load diff
132
psbt/signer.go
Normal file
132
psbt/signer.go
Normal file
|
@ -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)
|
||||||
|
}
|
312
psbt/updater.go
Normal file
312
psbt/updater.go
Normal file
|
@ -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
|
||||||
|
}
|
Loading…
Reference in a new issue