diff --git a/psbt/updater.go b/psbt/updater.go index 434e05b..b8c2350 100644 --- a/psbt/updater.go +++ b/psbt/updater.go @@ -4,12 +4,11 @@ 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 +// 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" @@ -20,217 +19,259 @@ import ( "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. +// 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 + Upsbt *Packet } -// 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) { +// NewUpdater returns a new instance of Updater, if the passed Psbt struct is +// in a valid form, else an error. +func NewUpdater(p *Packet) (*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. +// 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. +// 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. +// 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): + 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 + // First check; don't add duplicates. for _, x := range pInput.PartialSigs { if bytes.Equal(x.PubKey, partialSig.PubKey) { return ErrDuplicateKey } } - // Sanity checks + // Next, we perform a series of additional 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 } + + // 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. 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() + + 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() + 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. + // `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() + 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 + } else { + // Otherwise, this is a p2wkh input. pubkeyHash := btcutil.Hash160(pubkey) - pubkeyHashScript, err := txscript.NewScriptBuilder().AddOp( - txscript.OP_0).AddData(pubkeyHash).Script() + pubkeyHashScript, err := txscript.NewScriptBuilder(). + AddOp(txscript.OP_0). + AddData(pubkeyHash). + Script() if err != nil { return err } + + // Validate that we're able to properly reconstruct the + // witness program. if !bytes.Equal(pubkeyHashScript, script) { return ErrInvalidSignatureForInput } } } else { - // attaching signature without utxo field is not allowed + + // Attaching signature without utxo field is not allowed. return ErrInvalidPsbtFormat } p.Upsbt.Inputs[inIndex].PartialSigs = append( - p.Upsbt.Inputs[inIndex].PartialSigs, &partialSig) + 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. + + // 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. +// 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. +// 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. +// 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. +// 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: 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, @@ -249,27 +290,32 @@ func (p *Updater) AddInBip32Derivation(masterKeyFingerprint uint32, } p.Upsbt.Inputs[inIndex].Bip32Derivation = append( - p.Upsbt.Inputs[inIndex].Bip32Derivation, &bip32Derivation) + 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. +// 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 } @@ -282,31 +328,40 @@ func (p *Updater) AddOutBip32Derivation(masterKeyFingerprint uint32, } p.Upsbt.Outputs[outIndex].Bip32Derivation = append( - p.Upsbt.Outputs[outIndex].Bip32Derivation, &bip32Derivation) + 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. +// 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. +// 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 }