<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:
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
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.
// 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  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:
{
  "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) and Taproot (by consensus), 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:
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  

---

