I’ve a 2-of-3 multisig taproot script,create like bitcoinjs-lib:https://github.com/bitcoinjs/bitcoinjs-lib/blob/8d9775c20da03ab40ccbb1971251e71d117bcd8b/check/integration/taproot.spec.ts#L531-L593:
builder := txscript.NewScriptBuilder()
for i, pk := vary 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(ship satoshi from the script),and two keys signal it.however acquired “invalid schnorr signatures” when despatched to bitcoind.
my golang code:
func TestGenTapscript(t *testing.T) {
signpks := []string{
...
}
pks := []string{...}
tapscript := NewMultisigTaprootScript(pks, 2, 3, &chaincfg.TestNet3Params)
// construct 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)
}
{ // enter
...
}
// Construct PSBT
packet, err := psbt.New(inputs, outputs, 2, 0, nSequences)
if err != nil {
t.Deadly(err)
}
packet.Inputs[0].TaprootLeafScript = []*psbt.TaprootTapLeafScript{
&psbt.TaprootTapLeafScript{
ControlBlock: tapscript.controlBlock,
Script: tapscript.leafScript,
LeafVersion: 0xc0,
},
}
// replace
updater, err := psbt.NewUpdater(packet)
if err != nil {
t.Deadly(err)
}
if err := updater.AddInWitnessUtxo(wire.NewTxOut(utxoAmount, tapscript.output), 0); err != nil {
t.Deadly(err)
}
var serializedTx bytes.Buffer
if err := packet.Serialize(&serializedTx); err != nil {
t.Deadly(err)
}
psbtHex := hex.EncodeToString(serializedTx.Bytes())
t.Log("psbtHex earlier than signal:", psbtHex)
// signit
cu := cuabs.New()
signedPsbtHex, err := cu.PsbtSign(context.Background(), signpks[0], psbtHex)
if err != nil {
t.Deadly(err)
}
signedPsbtHex, err = cu.PsbtSign(context.Background(), signpks[2], signedPsbtHex)
if err != nil {
t.Deadly(err)
}
t.Log("psbtHex after signal:", signedPsbtHex)
// addDummySigs like bitcoinjs-lib
psbtBz, _ := hex.DecodeString(signedPsbtHex)
packet, err = psbt.NewFromRawBytes(bytes.NewReader(psbtBz), false)
if err != nil {
t.Deadly(err)
}
for index, enter := vary packet.Inputs {
for _, pk := vary pks {
pkbz, _ := hex.DecodeString(pk)
var signed bool
for _, x := vary enter.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
// type.Slice(sigs, func(i, j int) bool {
// return bytes.Examine(sigs[i].XOnlyPubKey, sigs[j].XOnlyPubKey) < 0
// })
// packet.Inputs[index].TaprootScriptSpendSig = sigs
}
{
var serializedTx bytes.Buffer
if err := packet.Serialize(&serializedTx); err != nil {
t.Deadly(err)
}
t.Log("psbtHex earlier than finalize", hex.EncodeToString(serializedTx.Bytes()))
}
if err = psbt.MaybeFinalizeAll(packet); err != nil {
t.Deadly(err)
}
{
var serializedTx bytes.Buffer
if err := packet.Serialize(&serializedTx); err != nil {
t.Deadly(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 comparability, it was discovered that the “psbtHex earlier than finalize” and “psbtHex after finalize” had been completely different.
my js code:
const psbt = bitcoin.Psbt.fromHex(signedPsbtHex)
{
const leafHash = tapleafHash({
output: leafScript,
model: LEAF_VERSION_TAPSCRIPT,
})
console.log(leafHash.toString('hex'))
for (const enter of psbt.knowledge.inputs) {
if (!enter.tapScriptSig) proceed
const signedPubkeys = enter.tapScriptSig.filter((ts) => ts.leafHash.equals(leafHash)).map((ts) => ts.pubkey)
for (const pubkey of leafPubkeys) {
if (signedPubkeys.some((sPub) => sPub.equals(pubkey))) proceed
enter.tapScriptSig.push({
// This may be reused for every dummy signature
leafHash,
// That is the pubkey that did not signal
pubkey,
// This have to be an empty Buffer.
signature: Buffer.from([]),
})
}
}
} // this code from bitcoinjs-lib
psbt.finalizeAllInputs()
const tx = psbt.extractTransaction()
broadcast(...)