<p>In SegWit (<a href="https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#new-script-semantics" rel="noreferrer">by standardness</a>) and Taproot (<a href="https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#script-execution" rel="noreferrer">by consensus</a>), the arguments to OP_IF and OP_NOTIF must be minimal, i.e. be either the empty vector or the one-byte vector <code>01</code>. Your transaction uses <code>00</code>, which is neither.</p>
<p>The code that composes your witness stack should probably be:</p>
<pre class="lang-typescript prettyprint-override"><code>const finalScriptWitness = witnessStackToScriptWitness([
    signature,
    pubkey,
    Buffer.from([]),
    script
]);
</code></pre>

CC BY-SA 4.0
2024-04-30T09:13:42.540Z
Thank you <3, this worked for me
Barney Chambers
CC BY-SA 4.0
2024-04-30T09:13:57.790Z
Yep that did it. :) Thank you for the help
Dev Oskii
CC BY-SA 4.0
2024-04-29T14:31:56.873Z
2024-04-29T14:37:01.673Z
2024-04-29T14:37:01.673Z
Vojtěch Strnad
<p>I have created an HTLC with the following Bitcoin script using bitcoinlib-js:</p>
<pre class="lang-typescript prettyprint-override"><code>const redeemScript = bitcoin.script.compile([
    bitcoin.opcodes.OP_IF,
    bitcoin.opcodes.OP_SHA256,
    secretHashBuffer,
    bitcoin.opcodes.OP_EQUALVERIFY,
    bitcoin.opcodes.OP_DUP,
    bitcoin.opcodes.OP_HASH160,
    recipientHash, // Hashed recipient public key
    bitcoin.opcodes.OP_ELSE,
    bitcoin.script.number.encode(lockduration),
    bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY,
    bitcoin.opcodes.OP_DROP,
    bitcoin.opcodes.OP_DUP,
    bitcoin.opcodes.OP_HASH160,
    senderHash, // Hashed sender public key
    bitcoin.opcodes.OP_ENDIF,
    bitcoin.opcodes.OP_EQUALVERIFY,
    bitcoin.opcodes.OP_CHECKSIG,
]);
</code></pre>
<p>I am able to unlock the funds with in the OP_IF branch by using the correct secret, and spending from the recipient public key. However, I am encountering an issue when attempting to redeem funds via the OP_ELSE branch of the script using the timelock <code>lockduration</code> and broadcasting using the sender public key.</p>
<p>The specific error I get is:</p>
<pre><code>error code: -26
error message:
non-mandatory-script-verify-flag (OP_IF/NOTIF argument must be minimal)
</code></pre>
<p>I believe the error stems from how I am encoding my values. I am building a PSBT which I then sign with xverse and convert into a raw transaction using <code>bitcoin-cli finalizepsbt cHNidP8BAFMCAAAAAa/...</code></p>
<p>Firstly I create the PSBT</p>
<pre class="lang-typescript prettyprint-override"><code>import * as bitcoin from 'bitcoinjs-lib';
import crypto from &quot;crypto&quot;;

export function createRefundPSBT(
    secretHash: string,
    lockduration: number,
    scriptPubKeyHex: string,
    htlcTxId: string,
    htlcOutputIndex: number,
    refundAmount: number,
    recipientPubKey : string,
    senderPubKey : string,
    recipientAddress : string,
    networkType : string
  ) {
    const network =
      networkType === &quot;testnet&quot;
        ? bitcoin.networks.testnet
        : bitcoin.networks.bitcoin;


    const secretHashBuffer = Buffer.from(secretHash, &quot;hex&quot;);

    // Recreate the HTLC script using the provided secret
    const recipientHash = bitcoin.crypto.hash160(
      Buffer.from(recipientPubKey, &quot;hex&quot;)
    );
    const senderHash = bitcoin.crypto.hash160(Buffer.from(senderPubKey, &quot;hex&quot;));
  
    const redeemScript = bitcoin.script.compile([
      bitcoin.opcodes.OP_IF,
      bitcoin.opcodes.OP_SHA256,
      secretHashBuffer,
      bitcoin.opcodes.OP_EQUALVERIFY,
      bitcoin.opcodes.OP_DUP,
      bitcoin.opcodes.OP_HASH160,
      recipientHash, // Hashed recipient public key
      bitcoin.opcodes.OP_ELSE,
      bitcoin.script.number.encode(lockduration),
      bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY,
      bitcoin.opcodes.OP_DROP,
      bitcoin.opcodes.OP_DUP,
      bitcoin.opcodes.OP_HASH160,
      senderHash, // Hashed sender public key
      bitcoin.opcodes.OP_ENDIF,
      bitcoin.opcodes.OP_EQUALVERIFY,
      bitcoin.opcodes.OP_CHECKSIG,
    ]);
  
    const scriptPubKey = Buffer.from(scriptPubKeyHex, &quot;hex&quot;);
  
    console.log(&quot;Creating PSBT&quot;);
    
    // Create a PSBT
    const psbt = new bitcoin.Psbt({ network: network })
      .addInput({
        hash: htlcTxId,
        index: htlcOutputIndex,
        sequence: 0xfffffffe, // Necessary for OP_CHECKLOCKTIMEVERIFY
        witnessUtxo: {
          script: scriptPubKey,
          value: refundAmount,
        },
        witnessScript: redeemScript,
      })
      .addOutput({
        address: recipientAddress,
        value: refundAmount - 3000, // Subtract a nominal fee
      })
      .setVersion(2)
      
      .setLocktime(lockduration);
  
    console.log(&quot;PSBT to be signed:&quot;, psbt.toBase64());

    return psbt.toBase64();
  }
</code></pre>
<p>Then I sign it with xverse wallet using a front end and attempt to finalize the PSBT.</p>
<pre class="lang-typescript prettyprint-override"><code>// This script is for refund

import * as bitcoin from 'bitcoinjs-lib';
import varuint from 'varuint-bitcoin';

function witnessStackToScriptWitness(witness: any) {
    let buffer = Buffer.allocUnsafe(0);

    function writeSlice(slice: string) {
        buffer = Buffer.concat([buffer, Buffer.from(slice)]);
    }

    function writeVarInt(i: number) {
        const varintLen = varuint.encodingLength(i);
        const currentLen = buffer.length;
        buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
        varuint.encode(i, buffer, currentLen);
    }

    function writeVarSlice(slice: string) {
        writeVarInt(slice.length);
        writeSlice(slice);
    }

    function writeVector(vector: any) {
        writeVarInt(vector.length);
        vector.forEach(writeVarSlice);
    }

    writeVector(witness);
    return buffer;
}

function getFinalScriptsSpecial(inputIndex: number, input: any, script: any, isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean) {
    if (!input.partialSig || input.partialSig.length === 0) {
        throw new Error(`Cannot finalize input #${inputIndex}: Missing partial signatures`);
    }
    if (!input.witnessUtxo) {
        throw new Error(`Cannot finalize input #${inputIndex}: Missing witness UTXO`);
    }
    if (!script) {
        throw new Error(`Cannot finalize input #${inputIndex}: Missing script`);
    }

    const signature = input.partialSig[0].signature;
    const pubkey = input.partialSig[0].pubkey;

    const finalScriptWitness = witnessStackToScriptWitness([
        signature,
        pubkey,
        Buffer.from([0]), // Byte array for OP_FALSE or 0, is this correct?
        //Buffer.alloc(0), // Or is this correct?
        script
    ]);

    return {
        finalScriptWitness: finalScriptWitness
    };
}

export function prepareFinalRefundPSBT(
    witnessHexWithdraw: string,
    psbtBase64: string,
): string | null {

    let psbt: any;

    try {  
        psbt = bitcoin.Psbt.fromBase64(psbtBase64);
        psbt.isSegwit = true;
        psbt.isP2SH = false;
        psbt.isP2WSH = true;

        psbt.finalizeInput(0, getFinalScriptsSpecial);

        //adding witness script hex back in from bitcoin-cli decodepsbt &lt;psbtBase64&gt; because psbt.finalizeInput removes it for some reason     
        psbt.data.inputs[0].witnessScript = Buffer.from(witnessHexWithdraw, 'hex');

    } catch (e) {
        console.error(`Error finalizing PSBT: ${e}`);
        process.exit(1);
    }

    return psbt.toBase64();
}
</code></pre>
<p>This gives me a finalized PSBT which I convert into a raw transaction. Here is the raw transaction decoded:</p>
<pre class="lang-json prettyprint-override"><code>{
  &quot;txid&quot;: &quot;d773911f5e0c2bd4590794676e7c95bd2e33d8156c22e64c80e8c5bf4cfcbece&quot;,
  &quot;hash&quot;: &quot;5f1025270b4f2b298d2a90647c2640cc222815d0df3e2d38b72b9e37b04c14ff&quot;,
  &quot;version&quot;: 2,
  &quot;size&quot;: 285,
  &quot;vsize&quot;: 134,
  &quot;weight&quot;: 534,
  &quot;locktime&quot;: 1,
  &quot;vin&quot;: [
    {
      &quot;txid&quot;: &quot;fa69f9cee0b3fffb419084ccdaa19dbc319ca23965d3e6a9444210449b92a194&quot;,
      &quot;vout&quot;: 0,
      &quot;scriptSig&quot;: {
        &quot;asm&quot;: &quot;&quot;,
        &quot;hex&quot;: &quot;&quot;
      },
      &quot;txinwitness&quot;: [
        &quot;3045022100cf0ca0b7cbdda5e0c6733d508031c160cb93c7f4e9fcc6bafed4e50e7c23dd6e02200bc9a300ed5d6ec1202de946a59b4fdbb07136d5ffc43f8f5fe1e8fae6d291cd01&quot;,
        &quot;0269bf7a1301185625758c324221cf6614e4a4104f90f83f633a8790f62919aa0a&quot;,
        &quot;00&quot;,
        &quot;63a820ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb8876a914a5102a75c05993aa082ca365b5ba7f49bed517586751b17576a914a5102a75c05993aa082ca365b5ba7f49bed517586888ac&quot;
      ],
      &quot;sequence&quot;: 4294967294
    }
  ],
  &quot;vout&quot;: [
    {
      &quot;value&quot;: 0.00007000,
      &quot;n&quot;: 0,
      &quot;scriptPubKey&quot;: {
        &quot;asm&quot;: &quot;OP_HASH160 836676150f0f5892416d8bd9bc5e923b494677f3 OP_EQUAL&quot;,
        &quot;desc&quot;: &quot;addr(2N5E1FYfiQgM5U8aTvU1btZYpiTVPE9ReaK)#00u0x08a&quot;,
        &quot;hex&quot;: &quot;a914836676150f0f5892416d8bd9bc5e923b494677f387&quot;,
        &quot;address&quot;: &quot;2N5E1FYfiQgM5U8aTvU1btZYpiTVPE9ReaK&quot;,
        &quot;type&quot;: &quot;scripthash&quot;
      }
    }
  ]
}
</code></pre>
<p>Then I try to broadcast the transaction</p>
<pre><code>bitcoin-cli sendrawtransaction 0200000000010194a1929b44104244a9e6d36539a29c31bc9da1dacc849041fbffb3e0cef969fa0000000000feffffff01581b00000000000017a914836676150f0f5892416d8bd9bc5e923b494677f38704483045022100cf0ca0b7cbdda5e0c6733d508031c160cb93c7f4e9fcc6bafed4e50e7c23dd6e02200bc9a300ed5d6ec1202de946a59b4fdbb07136d5ffc43f8f5fe1e8fae6d291cd01210269bf7a1301185625758c324221cf6614e4a4104f90f83f633a8790f62919aa0a01005963a820ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb8876a914a5102a75c05993aa082ca365b5ba7f49bed517586751b17576a914a5102a75c05993aa082ca365b5ba7f49bed517586888ac01000000
</code></pre>
<p>And I am met with the following error:</p>
<pre><code>error code: -26
error message:
non-mandatory-script-verify-flag (OP_IF/NOTIF argument must be minimal)
</code></pre>
<p>Could it be this part of the <code>txinwitness</code> that is not minimal?</p>
<pre><code>&quot;00&quot;,
</code></pre>
<p>Should it instead be</p>
<pre><code>&quot;0&quot;,
</code></pre>
<p>Or perhaps even</p>
<pre><code>&quot;&quot;,
</code></pre>
<p>Or is there some other reason my <code>OP_IF/NOTIF</code> argument is not minimal?</p>

<script><javascript><bip174-psbt><hash-time-locked-contract><p2wsh>
Why is my P2WSH OP_IF/NOTIF argument not minimal?
Dev Oskii
# Why is my P2WSH OP_IF/NOTIF argument not minimal?

(Asked by: username_0 on 2024-04-29)

I have created an HTLC with the following Bitcoin script using bitcoinlib-js:

```typescript
const redeemScript = bitcoin.script.compile([
    bitcoin.opcodes.OP_IF,
    bitcoin.opcodes.OP_SHA256,
    secretHashBuffer,
    bitcoin.opcodes.OP_EQUALVERIFY,
    bitcoin.opcodes.OP_DUP,
    bitcoin.opcodes.OP_HASH160,
    recipientHash, // Hashed recipient public key
    bitcoin.opcodes.OP_ELSE,
    bitcoin.script.number.encode(lockduration),
    bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY,
    bitcoin.opcodes.OP_DROP,
    bitcoin.opcodes.OP_DUP,
    bitcoin.opcodes.OP_HASH160,
    senderHash, // Hashed sender public key
    bitcoin.opcodes.OP_ENDIF,
    bitcoin.opcodes.OP_EQUALVERIFY,
    bitcoin.opcodes.OP_CHECKSIG,
]);

```

I am able to unlock the funds with in the OP\_IF branch by using the correct secret, and spending from the recipient public key. However, I am encountering an issue when attempting to redeem funds via the OP\_ELSE branch of the script using the timelock `lockduration` and broadcasting using the sender public key.

The specific error I get is:

```
error code: -26
error message:
non-mandatory-script-verify-flag (OP_IF/NOTIF argument must be minimal)

```

I believe the error stems from how I am encoding my values. I am building a PSBT which I then sign with xverse and convert into a raw transaction using `bitcoin-cli finalizepsbt cHNidP8BAFMCAAAAAa/...`

Firstly I create the PSBT

```typescript
import * as bitcoin from 'bitcoinjs-lib';
import crypto from "crypto";

export function createRefundPSBT(
    secretHash: string,
    lockduration: number,
    scriptPubKeyHex: string,
    htlcTxId: string,
    htlcOutputIndex: number,
    refundAmount: number,
    recipientPubKey : string,
    senderPubKey : string,
    recipientAddress : string,
    networkType : string
  ) {
    const network =
      networkType === "testnet"
        ? bitcoin.networks.testnet
        : bitcoin.networks.bitcoin;

    const secretHashBuffer = Buffer.from(secretHash, "hex");

    // Recreate the HTLC script using the provided secret
    const recipientHash = bitcoin.crypto.hash160(
      Buffer.from(recipientPubKey, "hex")
    );
    const senderHash = bitcoin.crypto.hash160(Buffer.from(senderPubKey, "hex"));
  
    const redeemScript = bitcoin.script.compile([
      bitcoin.opcodes.OP_IF,
      bitcoin.opcodes.OP_SHA256,
      secretHashBuffer,
      bitcoin.opcodes.OP_EQUALVERIFY,
      bitcoin.opcodes.OP_DUP,
      bitcoin.opcodes.OP_HASH160,
      recipientHash, // Hashed recipient public key
      bitcoin.opcodes.OP_ELSE,
      bitcoin.script.number.encode(lockduration),
      bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY,
      bitcoin.opcodes.OP_DROP,
      bitcoin.opcodes.OP_DUP,
      bitcoin.opcodes.OP_HASH160,
      senderHash, // Hashed sender public key
      bitcoin.opcodes.OP_ENDIF,
      bitcoin.opcodes.OP_EQUALVERIFY,
      bitcoin.opcodes.OP_CHECKSIG,
    ]);
  
    const scriptPubKey = Buffer.from(scriptPubKeyHex, "hex");
  
    console.log("Creating PSBT");
    
    // Create a PSBT
    const psbt = new bitcoin.Psbt({ network: network })
      .addInput({
        hash: htlcTxId,
        index: htlcOutputIndex,
        sequence: 0xfffffffe, // Necessary for OP_CHECKLOCKTIMEVERIFY
        witnessUtxo: {
          script: scriptPubKey,
          value: refundAmount,
        },
        witnessScript: redeemScript,
      })
      .addOutput({
        address: recipientAddress,
        value: refundAmount - 3000, // Subtract a nominal fee
      })
      .setVersion(2)
      
      .setLocktime(lockduration);
  
    console.log("PSBT to be signed:", psbt.toBase64());

    return psbt.toBase64();
  }

```

Then I sign it with xverse wallet using a front end and attempt to finalize the PSBT.

```typescript
// This script is for refund

import * as bitcoin from 'bitcoinjs-lib';
import varuint from 'varuint-bitcoin';

function witnessStackToScriptWitness(witness: any) {
    let buffer = Buffer.allocUnsafe(0);

    function writeSlice(slice: string) {
        buffer = Buffer.concat([buffer, Buffer.from(slice)]);
    }

    function writeVarInt(i: number) {
        const varintLen = varuint.encodingLength(i);
        const currentLen = buffer.length;
        buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
        varuint.encode(i, buffer, currentLen);
    }

    function writeVarSlice(slice: string) {
        writeVarInt(slice.length);
        writeSlice(slice);
    }

    function writeVector(vector: any) {
        writeVarInt(vector.length);
        vector.forEach(writeVarSlice);
    }

    writeVector(witness);
    return buffer;
}

function getFinalScriptsSpecial(inputIndex: number, input: any, script: any, isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean) {
    if (!input.partialSig || input.partialSig.length === 0) {
        throw new Error(`Cannot finalize input #${inputIndex}: Missing partial signatures`);
    }
    if (!input.witnessUtxo) {
        throw new Error(`Cannot finalize input #${inputIndex}: Missing witness UTXO`);
    }
    if (!script) {
        throw new Error(`Cannot finalize input #${inputIndex}: Missing script`);
    }

    const signature = input.partialSig[0].signature;
    const pubkey = input.partialSig[0].pubkey;

    const finalScriptWitness = witnessStackToScriptWitness([
        signature,
        pubkey,
        Buffer.from([0]), // Byte array for OP_FALSE or 0, is this correct?
        //Buffer.alloc(0), // Or is this correct?
        script
    ]);

    return {
        finalScriptWitness: finalScriptWitness
    };
}

export function prepareFinalRefundPSBT(
    witnessHexWithdraw: string,
    psbtBase64: string,
): string | null {

    let psbt: any;

    try {  
        psbt = bitcoin.Psbt.fromBase64(psbtBase64);
        psbt.isSegwit = true;
        psbt.isP2SH = false;
        psbt.isP2WSH = true;

        psbt.finalizeInput(0, getFinalScriptsSpecial);

        //adding witness script hex back in from bitcoin-cli decodepsbt <psbtBase64> because psbt.finalizeInput removes it for some reason     
        psbt.data.inputs[0].witnessScript = Buffer.from(witnessHexWithdraw, 'hex');

    } catch (e) {
        console.error(`Error finalizing PSBT: ${e}`);
        process.exit(1);
    }

    return psbt.toBase64();
}

```

This gives me a finalized PSBT which I convert into a raw transaction. Here is the raw transaction decoded:

```json
{
  "txid": "d773911f5e0c2bd4590794676e7c95bd2e33d8156c22e64c80e8c5bf4cfcbece",
  "hash": "5f1025270b4f2b298d2a90647c2640cc222815d0df3e2d38b72b9e37b04c14ff",
  "version": 2,
  "size": 285,
  "vsize": 134,
  "weight": 534,
  "locktime": 1,
  "vin": [
    {
      "txid": "fa69f9cee0b3fffb419084ccdaa19dbc319ca23965d3e6a9444210449b92a194",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "txinwitness": [
        "3045022100cf0ca0b7cbdda5e0c6733d508031c160cb93c7f4e9fcc6bafed4e50e7c23dd6e02200bc9a300ed5d6ec1202de946a59b4fdbb07136d5ffc43f8f5fe1e8fae6d291cd01",
        "0269bf7a1301185625758c324221cf6614e4a4104f90f83f633a8790f62919aa0a",
        "00",
        "63a820ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb8876a914a5102a75c05993aa082ca365b5ba7f49bed517586751b17576a914a5102a75c05993aa082ca365b5ba7f49bed517586888ac"
      ],
      "sequence": 4294967294
    }
  ],
  "vout": [
    {
      "value": 0.00007000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_HASH160 836676150f0f5892416d8bd9bc5e923b494677f3 OP_EQUAL",
        "desc": "addr(2N5E1FYfiQgM5U8aTvU1btZYpiTVPE9ReaK)#00u0x08a",
        "hex": "a914836676150f0f5892416d8bd9bc5e923b494677f387",
        "address": "2N5E1FYfiQgM5U8aTvU1btZYpiTVPE9ReaK",
        "type": "scripthash"
      }
    }
  ]
}

```

Then I try to broadcast the transaction

```
bitcoin-cli sendrawtransaction 0200000000010194a1929b44104244a9e6d36539a29c31bc9da1dacc849041fbffb3e0cef969fa0000000000feffffff01581b00000000000017a914836676150f0f5892416d8bd9bc5e923b494677f38704483045022100cf0ca0b7cbdda5e0c6733d508031c160cb93c7f4e9fcc6bafed4e50e7c23dd6e02200bc9a300ed5d6ec1202de946a59b4fdbb07136d5ffc43f8f5fe1e8fae6d291cd01210269bf7a1301185625758c324221cf6614e4a4104f90f83f633a8790f62919aa0a01005963a820ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb8876a914a5102a75c05993aa082ca365b5ba7f49bed517586751b17576a914a5102a75c05993aa082ca365b5ba7f49bed517586888ac01000000

```

And I am met with the following error:

```
error code: -26
error message:
non-mandatory-script-verify-flag (OP_IF/NOTIF argument must be minimal)

```

Could it be this part of the `txinwitness` that is not minimal?

```
"00",

```

Should it instead be

```
"0",

```

Or perhaps even

```
"",

```

Or is there some other reason my `OP_IF/NOTIF` argument is not minimal?

**Tags:** script, javascript, bip174-psbt, hash-time-locked-contract, p2wsh


---

## 1 answers

### Answer by username_1 on 2024-04-29. Score: 6 | Accepted answer

In SegWit ([by standardness](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#new-script-semantics)) and Taproot ([by consensus](https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#script-execution)), the arguments to OP\_IF and OP\_NOTIF must be minimal, i.e. be either the empty vector or the one-byte vector `01`. Your transaction uses `00`, which is neither.

The code that composes your witness stack should probably be:

```typescript
const finalScriptWitness = witnessStackToScriptWitness([
    signature,
    pubkey,
    Buffer.from([]),
    script
]);

```

> **Comments:**
> **username_2**: Thank you <3, this worked for me  
> **username_0**: Yep that did it. :) Thank you for the help  

---

