Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support psetv2 taproot fields #221

Merged
merged 6 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions psetv2/bip32.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,25 @@ type DerivationPathWithPubKey struct {
Bip32Path []uint32
}

type TapDerivationPathWithPubKey struct {
DerivationPathWithPubKey
LeafHashes [][]byte
}

func (d *TapDerivationPathWithPubKey) sanityCheck() error {
if len(d.LeafHashes) == 0 {
return ErrInInvalidTapBip32Derivation
}

for _, leafHash := range d.LeafHashes {
if len(leafHash) != 32 {
return ErrInInvalidTapBip32Derivation
}
}

return nil
}

// Bip32Sorter implements sort.Interface for the DerivationPathWithPubKey struct.
type Bip32Sorter []*DerivationPathWithPubKey

Expand Down
86 changes: 86 additions & 0 deletions psetv2/finalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ package psetv2
// multisig and no other custom script.

import (
"bytes"
"fmt"

"github.com/btcsuite/btcd/txscript"
"github.com/vulpemventures/go-elements/internal/bufferutil"
)

var (
Expand Down Expand Up @@ -56,6 +58,10 @@ func Finalize(p *Pset, inIndex int) error {
// witness or legacy UTXO.
switch {
case input.WitnessUtxo != nil:
if input.isTaproot() {
return finalizeTaprootInput(p, inIndex)
}

if err := finalizeWitnessInput(p, inIndex); err != nil {
return err
}
Expand Down Expand Up @@ -128,6 +134,25 @@ func isFinalizableWitnessInput(input *Input) bool {
len(input.RedeemScript) > 0 {
return false
}
} else if txscript.IsPayToTaproot(pkScript) {
if len(input.TapKeySig) > 0 {
return true
}

for _, sig := range input.TapScriptSig {
hasTapLeafScript := false
for _, tapLeaf := range input.TapLeafScript {
h := tapLeaf.TapHash()
if bytes.Equal(sig.LeafHash, h[:]) {
hasTapLeafScript = true
break
}
}
if !hasTapLeafScript {
return false
}
}
return true
} else {
// A P2WKH output on the other hand doesn't need
// neither a witnessScript or redeemScript.
Expand Down Expand Up @@ -338,6 +363,67 @@ func finalizeNonWitnessInput(p *Pset, inIndex int) error {
return nil
}

// finalizeTaprootInput attempts to finalize a taproot input
// key-path taproot: the witness is just the key signature
// script-path taproot: the witness is signatures of the first tapLeafScript, assuming it's a checksig tapscript
// all other cases must be finalized with custom finalizer function
func finalizeTaprootInput(p *Pset, inIndex int) error {
if checkFinalScriptSigWitness(p, inIndex) {
return ErrFinalizerAlreadyFinalized
}

input := p.Inputs[inIndex]

// keypath finalization
if len(input.TapKeySig) > 0 {
witness := make([][]byte, 1)
witness[0] = input.TapKeySig
serializer := bufferutil.NewSerializer(nil)
if err := serializer.WriteVector(witness); err != nil {
return err
}
p.Inputs[inIndex].FinalScriptWitness = serializer.Bytes()
return nil
}

// if scriptpath, we'll finalize the first tapScriptLeaf by default
if len(input.TapScriptSig) > 0 {
if len(input.TapLeafScript) == 0 {
return ErrFinalizerForbiddenFinalization
}

leafToFinalize := input.TapLeafScript[0]
leafToFinalizeHash := leafToFinalize.TapHash()
signatures := make([][]byte, 0)
for _, sig := range input.TapScriptSig {
if bytes.Equal(sig.LeafHash, leafToFinalizeHash[:]) {
signatures = append(signatures, sig.Signature)
}
}

controlBlock, err := leafToFinalize.ControlBlock.ToBytes()
if err != nil {
return err
}

// witness = [signatures, script, controlBlock]
witness := make([][]byte, 0, len(signatures)+2)
witness = append(witness, signatures...)
witness = append(witness, leafToFinalize.Script)
witness = append(witness, controlBlock)

serializer := bufferutil.NewSerializer(nil)
if err := serializer.WriteVector(witness); err != nil {
return err
}

p.Inputs[inIndex].FinalScriptWitness = serializer.Bytes()
return nil
}

return ErrFinalizerForbiddenFinalization
}

// finalizeWitnessInput attempts to create PsetInFinalScriptSig field and
// PsetInFinalScriptWitness field for input at index inIndex, and removes all
// other fields except for the utxo field, for an input of type witness, or
Expand Down
Loading
Loading