I’ve a 2-of-3 multisig taproot script,create like bitcoinjs-lib:https://github.com/bitcoinjs/bitcoinjs-lib/blob/8d9775c20da03ab40ccbb1971251e71d117bcd8b/take a look at/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 bought “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” have been totally 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.information.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 should be an empty Buffer.
signature: Buffer.from([]),
})
}
}
} // this code from bitcoinjs-lib
psbt.finalizeAllInputs()
const tx = psbt.extractTransaction()
broadcast(...)