mirror of
https://github.com/btcsuite/btcd.git
synced 2025-02-24 06:47:59 +01:00
psbt: make Taproot PSBT finalizable
This commit is contained in:
parent
0572702cec
commit
886a8f41db
2 changed files with 153 additions and 10 deletions
|
@ -12,7 +12,10 @@ package psbt
|
||||||
// multisig and no other custom script.
|
// multisig and no other custom script.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// isFinalized considers this input finalized if it contains at least one of
|
// isFinalized considers this input finalized if it contains at least one of
|
||||||
|
@ -32,16 +35,36 @@ func isFinalizableWitnessInput(pInput *PInput) bool {
|
||||||
// If this is a native witness output, then we require both
|
// If this is a native witness output, then we require both
|
||||||
// the witness script, but not a redeem script.
|
// the witness script, but not a redeem script.
|
||||||
case txscript.IsWitnessProgram(pkScript):
|
case txscript.IsWitnessProgram(pkScript):
|
||||||
if txscript.IsPayToWitnessScriptHash(pkScript) {
|
switch {
|
||||||
|
case txscript.IsPayToWitnessScriptHash(pkScript):
|
||||||
if pInput.WitnessScript == nil ||
|
if pInput.WitnessScript == nil ||
|
||||||
pInput.RedeemScript != nil {
|
pInput.RedeemScript != nil {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
case txscript.IsPayToTaproot(pkScript):
|
||||||
|
if pInput.TaprootKeySpendSig == nil &&
|
||||||
|
pInput.TaprootScriptSpendSig == nil {
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each of the script spend signatures we need a
|
||||||
|
// corresponding tap script leaf with the control block.
|
||||||
|
for _, sig := range pInput.TaprootScriptSpendSig {
|
||||||
|
_, err := FindLeafScript(pInput, sig.LeafHash)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
// A P2WKH output on the other hand doesn't need
|
// A P2WKH output on the other hand doesn't need
|
||||||
// neither a witnessScript or redeemScript.
|
// neither a witnessScript or redeemScript.
|
||||||
if pInput.WitnessScript != nil ||
|
if pInput.WitnessScript != nil ||
|
||||||
pInput.RedeemScript != nil {
|
pInput.RedeemScript != nil {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,8 +90,8 @@ func isFinalizableWitnessInput(pInput *PInput) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this isn't a nested nested P2SH output or a native witness
|
// If this isn't a nested P2SH output or a native witness output, then
|
||||||
// output, then we can't finalize this input as we don't understand it.
|
// we can't finalize this input as we don't understand it.
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -106,8 +129,10 @@ func isFinalizableLegacyInput(p *Packet, pInput *PInput, inIndex int) bool {
|
||||||
func isFinalizable(p *Packet, inIndex int) bool {
|
func isFinalizable(p *Packet, inIndex int) bool {
|
||||||
pInput := p.Inputs[inIndex]
|
pInput := p.Inputs[inIndex]
|
||||||
|
|
||||||
// The input cannot be finalized without any signatures
|
// The input cannot be finalized without any signatures.
|
||||||
if pInput.PartialSigs == nil {
|
if pInput.PartialSigs == nil && pInput.TaprootKeySpendSig == nil &&
|
||||||
|
pInput.TaprootScriptSpendSig == nil {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +184,6 @@ func MaybeFinalize(p *Packet, inIndex int) (bool, error) {
|
||||||
// MaybeFinalizeAll attempts to finalize all inputs of the psbt.Packet that are
|
// MaybeFinalizeAll attempts to finalize all inputs of the psbt.Packet that are
|
||||||
// not already finalized, and returns an error if it fails to do so.
|
// not already finalized, and returns an error if it fails to do so.
|
||||||
func MaybeFinalizeAll(p *Packet) error {
|
func MaybeFinalizeAll(p *Packet) error {
|
||||||
|
|
||||||
for i := range p.UnsignedTx.TxIn {
|
for i := range p.UnsignedTx.TxIn {
|
||||||
success, err := MaybeFinalize(p, i)
|
success, err := MaybeFinalize(p, i)
|
||||||
if err != nil || !success {
|
if err != nil || !success {
|
||||||
|
@ -184,8 +208,18 @@ func Finalize(p *Packet, inIndex int) error {
|
||||||
// witness or legacy UTXO.
|
// witness or legacy UTXO.
|
||||||
switch {
|
switch {
|
||||||
case pInput.WitnessUtxo != nil:
|
case pInput.WitnessUtxo != nil:
|
||||||
if err := finalizeWitnessInput(p, inIndex); err != nil {
|
pkScript := pInput.WitnessUtxo.PkScript
|
||||||
return err
|
|
||||||
|
switch {
|
||||||
|
case txscript.IsPayToTaproot(pkScript):
|
||||||
|
if err := finalizeTaprootInput(p, inIndex); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if err := finalizeWitnessInput(p, inIndex); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case pInput.NonWitnessUtxo != nil:
|
case pInput.NonWitnessUtxo != nil:
|
||||||
|
@ -460,3 +494,87 @@ func finalizeWitnessInput(p *Packet, inIndex int) error {
|
||||||
p.Inputs[inIndex] = *newInput
|
p.Inputs[inIndex] = *newInput
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// finalizeTaprootInput attempts to create PsbtInFinalScriptWitness field for
|
||||||
|
// input at index inIndex, and removes all other fields except for the utxo
|
||||||
|
// field, for an input of type p2tr, or returns an error.
|
||||||
|
func finalizeTaprootInput(p *Packet, inIndex int) error {
|
||||||
|
// If this input has already been finalized, then we'll return an error
|
||||||
|
// as we can't proceed.
|
||||||
|
if checkFinalScriptSigWitness(p, inIndex) {
|
||||||
|
return ErrInputAlreadyFinalized
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any p2tr input will only have a witness script, no sig script.
|
||||||
|
var (
|
||||||
|
serializedWitness []byte
|
||||||
|
err error
|
||||||
|
pInput = &p.Inputs[inIndex]
|
||||||
|
)
|
||||||
|
|
||||||
|
// What spend path did we take?
|
||||||
|
switch {
|
||||||
|
// Key spend path.
|
||||||
|
case len(pInput.TaprootKeySpendSig) > 0:
|
||||||
|
serializedWitness, err = writeWitness(pInput.TaprootKeySpendSig)
|
||||||
|
|
||||||
|
// Script spend path.
|
||||||
|
case len(pInput.TaprootScriptSpendSig) > 0:
|
||||||
|
var witnessStack wire.TxWitness
|
||||||
|
|
||||||
|
// If there are multiple script spend signatures, we assume they
|
||||||
|
// are from multiple signing participants for the same leaf
|
||||||
|
// script that uses OP_CHECKSIGADD for multi-sig. Signing
|
||||||
|
// multiple possible execution paths at the same time is
|
||||||
|
// currently not supported by this library.
|
||||||
|
targetLeafHash := pInput.TaprootScriptSpendSig[0].LeafHash
|
||||||
|
leafScript, err := FindLeafScript(pInput, targetLeafHash)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("control block for script spend " +
|
||||||
|
"signature not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The witness stack will contain all signatures, followed by
|
||||||
|
// the script itself and then the control block.
|
||||||
|
for idx, scriptSpendSig := range pInput.TaprootScriptSpendSig {
|
||||||
|
// Make sure that if there are indeed multiple
|
||||||
|
// signatures, they all reference the same leaf hash.
|
||||||
|
if !bytes.Equal(scriptSpendSig.LeafHash, targetLeafHash) {
|
||||||
|
return fmt.Errorf("script spend signature %d "+
|
||||||
|
"references different target leaf "+
|
||||||
|
"hash than first signature; only one "+
|
||||||
|
"script path is supported", idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := append([]byte{}, scriptSpendSig.Signature...)
|
||||||
|
if scriptSpendSig.SigHash != txscript.SigHashDefault {
|
||||||
|
sig = append(sig, byte(scriptSpendSig.SigHash))
|
||||||
|
}
|
||||||
|
witnessStack = append(witnessStack, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete the witness stack with the executed script and the
|
||||||
|
// serialized control block.
|
||||||
|
witnessStack = append(witnessStack, leafScript.Script)
|
||||||
|
witnessStack = append(witnessStack, leafScript.ControlBlock)
|
||||||
|
|
||||||
|
serializedWitness, err = writeWitness(witnessStack...)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ErrInvalidPsbtFormat
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, a witness has been constructed. Remove all fields
|
||||||
|
// other than witness utxo (01) and finalscriptsig (07),
|
||||||
|
// finalscriptwitness (08).
|
||||||
|
newInput := NewPsbtInput(nil, pInput.WitnessUtxo)
|
||||||
|
newInput.FinalScriptWitness = serializedWitness
|
||||||
|
|
||||||
|
// Finally, we overwrite the entry in the input list at the correct
|
||||||
|
// index.
|
||||||
|
p.Inputs[inIndex] = *newInput
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -35,9 +35,14 @@ func WriteTxWitness(w io.Writer, wit [][]byte) error {
|
||||||
|
|
||||||
// writePKHWitness writes a witness for a p2wkh spending input
|
// writePKHWitness writes a witness for a p2wkh spending input
|
||||||
func writePKHWitness(sig []byte, pub []byte) ([]byte, error) {
|
func writePKHWitness(sig []byte, pub []byte) ([]byte, error) {
|
||||||
|
return writeWitness(sig, pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeWitness serializes a witness stack from the given items.
|
||||||
|
func writeWitness(stackElements ...[]byte) ([]byte, error) {
|
||||||
var (
|
var (
|
||||||
buf bytes.Buffer
|
buf bytes.Buffer
|
||||||
witnessItems = [][]byte{sig, pub}
|
witnessItems = append([][]byte{}, stackElements...)
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := WriteTxWitness(&buf, witnessItems); err != nil {
|
if err := WriteTxWitness(&buf, witnessItems); err != nil {
|
||||||
|
@ -420,3 +425,23 @@ func NewFromSignedTx(tx *wire.MsgTx) (*Packet, [][]byte,
|
||||||
}
|
}
|
||||||
return unsignedPsbt, scriptSigs, witnesses, nil
|
return unsignedPsbt, scriptSigs, witnesses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindLeafScript attempts to locate the leaf script of a given target Tap Leaf
|
||||||
|
// hash in the list of leaf scripts of the given input.
|
||||||
|
func FindLeafScript(pInput *PInput,
|
||||||
|
targetLeafHash []byte) (*TaprootTapLeafScript, error) {
|
||||||
|
|
||||||
|
for _, leaf := range pInput.TaprootLeafScript {
|
||||||
|
leafHash := txscript.TapLeaf{
|
||||||
|
LeafVersion: leaf.LeafVersion,
|
||||||
|
Script: leaf.Script,
|
||||||
|
}.TapHash()
|
||||||
|
|
||||||
|
if bytes.Equal(targetLeafHash, leafHash[:]) {
|
||||||
|
return leaf, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("leaf script for target leaf hash %x not "+
|
||||||
|
"found in input", targetLeafHash)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue