I have a 2-of-3 multisig taproot script,create like bitcoinjs-lib:https://github.com/bitcoinjs/bitcoinjs-lib/blob/8d9775c20da03ab40ccbb1971251e71d117bcd8b/test/integration/taproot.spec.ts#L531-L593:
builder := txscript.NewScriptBuilder()
for i, pk := range leafPubkeys {
if i == 0 {
builder.AddData(pk)
builder.AddOp(byte(txscript.OP_CHECKSIG))
} else {
builder.AddData(pk)
builder.AddOp(byte(txscript.OP_CHECKSIGADD))
}
}
builder.AddOp(byte(txscript.OP_1 - 1 + 2))
builder.AddOp(byte(txscript.OP_GREATERTHANOREQUAL))
then create a tx(send satoshi from the script),and two keys sign it.but got “invalid schnorr signatures” when sent to bitcoind.
my golang code:
func TestGenTapscript(t *testing.T) {
signpks := []string{
...
}
pks := []string{...}
tapscript := NewMultisigTaprootScript(pks, 2, 3, &chaincfg.TestNet3Params)
// build tx
const utxoHash = "..."
const utxoVout = 0
const utxoAmount = 1000000
const sendAmount = utxoAmount - 1000
var (
inputs []*wire.OutPoint
nSequences []uint32
outputs []*wire.TxOut
)
{ // output
...
outputs = append(outputs, redeemTxOut)
}
{ // input
...
}
// Build PSBT
packet, err := psbt.New(inputs, outputs, 2, 0, nSequences)
if err != nil {
t.Fatal(err)
}
packet.Inputs[0].TaprootLeafScript = []*psbt.TaprootTapLeafScript{
&psbt.TaprootTapLeafScript{
ControlBlock: tapscript.controlBlock,
Script: tapscript.leafScript,
LeafVersion: 0xc0,
},
}
// update
updater, err := psbt.NewUpdater(packet)
if err != nil {
t.Fatal(err)
}
if err := updater.AddInWitnessUtxo(wire.NewTxOut(utxoAmount, tapscript.output), 0); err != nil {
t.Fatal(err)
}
var serializedTx bytes.Buffer
if err := packet.Serialize(&serializedTx); err != nil {
t.Fatal(err)
}
psbtHex := hex.EncodeToString(serializedTx.Bytes())
t.Log("psbtHex before sign:", psbtHex)
// signit
cu := cuabs.New()
signedPsbtHex, err := cu.PsbtSign(context.Background(), signpks[0], psbtHex)
if err != nil {
t.Fatal(err)
}
signedPsbtHex, err = cu.PsbtSign(context.Background(), signpks[2], signedPsbtHex)
if err != nil {
t.Fatal(err)
}
t.Log("psbtHex after sign:", signedPsbtHex)
// addDummySigs like bitcoinjs-lib
psbtBz, _ := hex.DecodeString(signedPsbtHex)
packet, err = psbt.NewFromRawBytes(bytes.NewReader(psbtBz), false)
if err != nil {
t.Fatal(err)
}
for index, input := range packet.Inputs {
for _, pk := range pks {
pkbz, _ := hex.DecodeString(pk)
var signed bool
for _, x := range input.TaprootScriptSpendSig {
if bytes.Equal(pkbz, x.XOnlyPubKey) {
signed = true
break
}
}
if !signed {
packet.Inputs[index].TaprootScriptSpendSig = append(packet.Inputs[index].TaprootScriptSpendSig, &psbt.TaprootScriptSpendSig{
XOnlyPubKey: pkbz,
LeafHash: tapscript.tapleafHash,
Signature: nil,
SigHash: 0,
})
}
}
// sigs := packet.Inputs[index].TaprootScriptSpendSig
// sort.Slice(sigs, func(i, j int) bool {
// return bytes.Compare(sigs[i].XOnlyPubKey, sigs[j].XOnlyPubKey) < 0
// })
// packet.Inputs[index].TaprootScriptSpendSig = sigs
}
{
var serializedTx bytes.Buffer
if err := packet.Serialize(&serializedTx); err != nil {
t.Fatal(err)
}
t.Log("psbtHex before finalize", hex.EncodeToString(serializedTx.Bytes()))
}
if err = psbt.MaybeFinalizeAll(packet); err != nil {
t.Fatal(err)
}
{
var serializedTx bytes.Buffer
if err := packet.Serialize(&serializedTx); err != nil {
t.Fatal(err)
}
t.Log("psbtHex after finalize", hex.EncodeToString(serializedTx.Bytes()))
}
//extract()
//broadcast()
}
Use js code,it work. “tapscript.tapleafHash” in golang is equal “leafHash” in js,and “pubkey” is equal.
After comparison, it was found that the “psbtHex before finalize” and “psbtHex after finalize” were different.
my js code:
const psbt = bitcoin.Psbt.fromHex(signedPsbtHex)
{
const leafHash = tapleafHash({
output: leafScript,
version: LEAF_VERSION_TAPSCRIPT,
})
console.log(leafHash.toString('hex'))
for (const input of psbt.data.inputs) {
if (!input.tapScriptSig) continue
const signedPubkeys = input.tapScriptSig.filter((ts) => ts.leafHash.equals(leafHash)).map((ts) => ts.pubkey)
for (const pubkey of leafPubkeys) {
if (signedPubkeys.some((sPub) => sPub.equals(pubkey))) continue
input.tapScriptSig.push({
// This can be reused for each dummy signature
leafHash,
// This is the pubkey that didn't sign
pubkey,
// This must be an empty Buffer.
signature: Buffer.from([]),
})
}
}
} // this code from bitcoinjs-lib
psbt.finalizeAllInputs()
const tx = psbt.extractTransaction()
broadcast(...)