<p>You must make sure there is a stack item &quot;signature&quot; for each pubkey that did not sign. This item is not actually a signature, it must be an empty byte vector.</p>
<hr />
<p><code>OP_CHECKSIG</code> and <code>OP_CHECKSIGADD</code> both require at least two stack items. The top stack item will be interpreted as the public key, and the next stack item the signature. <code>OP_CHECKSIGADD</code> requires a third stack item which is a counter. If the signature is valid, it will push a stack item that is the counter + 1, otherwise it pushes just the counter.</p>
<p>In order for this script to execute, you must still provide it sufficient stack items. So each <code>OP_CHECKSIGADD</code> for a pubkey that did not sign still needs something to be the signature. But that signature does not need to be valid. BIP 342 requires that such invalid signatures must be an empty byte vector.</p>

CC BY-SA 4.0
2024-06-13T14:58:56.380Z
Well I have tried this approach by adding the partial sign in the tapscript, which is like: 
(1) psbt.data.inputs[0].tapScriptSig.push(Buffer.from("0x0")), but it occur "mandatory-script-verify-flag-failed (Invalid Schnorr signature size)". 
(2) Use psbt.signinput(0, keypair), but with a random keypair, it occur "cannot sign input #0 with key <xxxx>
<br/> 
I wonder how could i added this "single 0 byte"
user152745
CC BY-SA 4.0
2024-06-13T15:06:55.977Z
Taproot does not have a "scriptSig" in the same way that scriptSig exists for non-segwit scripts. This field is probably something else, and so the wrong place to put your 0 bytes. I am not familiar with bitcoin-js so cannot help you with specifics. However, it is generally the job of the PSBT finalizer to figure out where in the stack signatures should go, including the 0 bytes. Since it is not doing that, you should open an issue with them.
Ava Chow
CC BY-SA 4.0
2024-06-13T15:22:50.980Z
@AvaChow The "invalid" argument to failed OP_CHECKSIGs is not a single 0 byte; it must be an empty byte sequence.
Pieter Wuille
CC BY-SA 4.0
2024-06-14T02:23:27.247Z
I'll try Buffer.from("")
user152745
CC BY-SA 4.0
2024-06-14T02:28:23.267Z
Thank you guys, I have find the right way to do it
user152745
CC BY-SA 4.0
2024-06-13T14:30:26.020Z
2024-06-14T03:00:22.080Z
2024-06-14T03:00:22.080Z
Ava Chow
<p>I write a simple taproot-based script in bitcoinjs, like:</p>
<p><strong>&lt;pk1&gt; OP_CHECKSIG &lt;pk2&gt; OP_CHECKSIGADD &lt;pk3&gt; OP_CHECKSIGADD OP_2 OP_EQUAL</strong></p>
<p>It should be a 3-2 threshold account, that require 2 signature. However, it actually required 3 signature, if we only sign it with 2 keypair, it will occur:</p>
<p><strong>Operation not valid with the current stack size</strong></p>
<p>So how do I able to spend this taproot address, with only 2 valid keypair?</p>
<p>Here is the code used to formulate the address and spend:</p>
<pre><code>const leafKeys = [];
const leafPubkeys = [];
const leafKeys_WIF = [];

for (let i = 0; i &lt; num; i++) {
    const leafKey = ECPair.makeRandom({ network });
    leafKeys.push(leafKey);
    leafPubkeys.push(toXOnly(leafKey.publicKey).toString('hex'));
    leafKeys_WIF.push(leafKey.toWIF())
}

// It returns the script that I wrote before
const leafScript = asm_builder(leafPubkeys, threshold);

const scriptTree: Taptree =
{
    output: leafScript,
};

const redeem = {
    output: leafScript,
    redeemVersion: LEAF_VERSION_TAPSCRIPT,
};

const p2tr = bitcoin.payments.p2tr({
    internalPubkey: toXOnly(keypair.publicKey),
    scriptTree,
    redeem,
    network: choose_network(`regtest`),
});

console.log(`Waiting till UTXO is detected at this Address: ${p2tr.address!}`)

let temp_trans = await pushTrans(p2tr.address!)
console.log(&quot;the new txid is:&quot;, temp_trans)

const utxo = await getUTXOfromTx(temp_trans, p2tr.address!)

let psbt_origin: string = build_psbt(redeem, [utxo], p2tr.address, &quot;bcrt1q5hk8re6mar775fxnwwfwse4ql9vtpn6x558g0w&quot;, &quot;regtest&quot;, p2tr, amt, fee)
let psbt_ = ''

for (var i = 0; i &lt; threshold; i++) {
    psbt_ = sign_psbt(psbt_origin, leafKeys_WIF[i], &quot;regtest&quot;)
    psbt_origin = psbt_
}

const txhex = await pay_psbt_hex(psbt_origin, num - threshold, &quot;regtest&quot;)
</code></pre>
<hr />
<p>Solution:</p>
<p>Based on the answer below, I find a way to spending the taproot address, which is by pushing a new tapscripsig into the psbt.data.inputs.</p>
<pre><code>let psbt = Psbt.fromBase64(psbt_)

for (let i = 0; i &lt; psbt.data.inputs.length; i++) {
    for (let j = threshold; j &lt; sign_num; j++) {
        psbt.data.inputs[i].tapScriptSig?.push({
            leafHash: psbt.data.inputs[i].tapScriptSig![0].leafHash,
            // pks here must be the correct pk of the tapscript, exactly equal to the reedem.output
            pubkey: pks[j],
            signature: Buffer.from(&quot;&quot;)
        });
    }
}

psbt.finalizeAllInputs();
</code></pre>

CC BY-SA 4.0
2024-06-13T10:58:26.920Z
Can you add the complete raw transaction to your post?
Vojtěch Strnad
CC BY-SA 4.0
2024-06-13T11:51:45.297Z
Sure, it was a script built in bitcoinJS
user152745
<script><multi-signature><taproot>
Help! About how to spend OP_checksigadd Multi-sig
user152745
# Help! About how to spend OP_checksigadd Multi-sig

(Asked by: username_0 on 2024-06-13)

I write a simple taproot-based script in bitcoinjs, like:

**<pk1> OP\_CHECKSIG <pk2> OP\_CHECKSIGADD <pk3> OP\_CHECKSIGADD OP\_2 OP\_EQUAL**

It should be a 3-2 threshold account, that require 2 signature. However, it actually required 3 signature, if we only sign it with 2 keypair, it will occur:

**Operation not valid with the current stack size**

So how do I able to spend this taproot address, with only 2 valid keypair?

Here is the code used to formulate the address and spend:

```
const leafKeys = [];
const leafPubkeys = [];
const leafKeys_WIF = [];

for (let i = 0; i < num; i++) {
    const leafKey = ECPair.makeRandom({ network });
    leafKeys.push(leafKey);
    leafPubkeys.push(toXOnly(leafKey.publicKey).toString('hex'));
    leafKeys_WIF.push(leafKey.toWIF())
}

// It returns the script that I wrote before
const leafScript = asm_builder(leafPubkeys, threshold);

const scriptTree: Taptree =
{
    output: leafScript,
};

const redeem = {
    output: leafScript,
    redeemVersion: LEAF_VERSION_TAPSCRIPT,
};

const p2tr = bitcoin.payments.p2tr({
    internalPubkey: toXOnly(keypair.publicKey),
    scriptTree,
    redeem,
    network: choose_network(`regtest`),
});

console.log(`Waiting till UTXO is detected at this Address: ${p2tr.address!}`)

let temp_trans = await pushTrans(p2tr.address!)
console.log("the new txid is:", temp_trans)

const utxo = await getUTXOfromTx(temp_trans, p2tr.address!)

let psbt_origin: string = build_psbt(redeem, [utxo], p2tr.address, "bcrt1q5hk8re6mar775fxnwwfwse4ql9vtpn6x558g0w", "regtest", p2tr, amt, fee)
let psbt_ = ''

for (var i = 0; i < threshold; i++) {
    psbt_ = sign_psbt(psbt_origin, leafKeys_WIF[i], "regtest")
    psbt_origin = psbt_
}

const txhex = await pay_psbt_hex(psbt_origin, num - threshold, "regtest")

```

---

Solution:

Based on the answer below, I find a way to spending the taproot address, which is by pushing a new tapscripsig into the psbt.data.inputs.

```
let psbt = Psbt.fromBase64(psbt_)

for (let i = 0; i < psbt.data.inputs.length; i++) {
    for (let j = threshold; j < sign_num; j++) {
        psbt.data.inputs[i].tapScriptSig?.push({
            leafHash: psbt.data.inputs[i].tapScriptSig![0].leafHash,
            // pks here must be the correct pk of the tapscript, exactly equal to the reedem.output
            pubkey: pks[j],
            signature: Buffer.from("")
        });
    }
}

psbt.finalizeAllInputs();

```

**Tags:** script, multi-signature, taproot

> **Comments:**
> **username_1**: Can you add the complete raw transaction to your post?  
> **username_0**: Sure, it was a script built in bitcoinJS  

---

## 1 answers

### Answer by username_2 on 2024-06-13. Score: 2 | Accepted answer

You must make sure there is a stack item "signature" for each pubkey that did not sign. This item is not actually a signature, it must be an empty byte vector.

---

`OP_CHECKSIG` and `OP_CHECKSIGADD` both require at least two stack items. The top stack item will be interpreted as the public key, and the next stack item the signature. `OP_CHECKSIGADD` requires a third stack item which is a counter. If the signature is valid, it will push a stack item that is the counter + 1, otherwise it pushes just the counter.

In order for this script to execute, you must still provide it sufficient stack items. So each `OP_CHECKSIGADD` for a pubkey that did not sign still needs something to be the signature. But that signature does not need to be valid. BIP 342 requires that such invalid signatures must be an empty byte vector.

> **Comments:**
> **username_0**: Well I have tried this approach by adding the partial sign in the tapscript, which is like:  (1) psbt.data.inputs[0].tapScriptSig.push(Buffer.from("0x0")), but it occur "mandatory-script-verify-flag-failed (Invalid Schnorr signature size)".  (2) Use psbt.signinput(0, keypair), but with a random keypair, it occur "cannot sign input #0 with key <xxxx> <br/>  I wonder how could i added this "single 0 byte"  
> **username_2**: Taproot does not have a "scriptSig" in the same way that scriptSig exists for non-segwit scripts. This field is probably something else, and so the wrong place to put your 0 bytes. I am not familiar with bitcoin-js so cannot help you with specifics. However, it is generally the job of the PSBT finalizer to figure out where in the stack signatures should go, including the 0 bytes. Since it is not doing that, you should open an issue with them.  
> **username_3**: @username_2 The "invalid" argument to failed OP_CHECKSIGs is not a single 0 byte; it must be an empty byte sequence.  
> **username_0**: I'll try Buffer.from("")  
> **username_0**: Thank you guys, I have find the right way to do it  

---

