Bitcoin

script – Why are my P2WSH OP_IF/NOTIF arguments not minimal?

I 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,
));

You can unlock funds at the OP_IF branch by using the correct secret and spending from the recipient public key. However, I ran into a problem when trying to redeem funds through the OP_ELSE branch of the script using a time lock. lockduration Broadcast using the sender’s 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 think the error is coming from the way I encode my values. I’m building a PSBT that I sign with xverse and convert to a raw transaction using: bitcoin-cli finalizepsbt cHNidP8BAFMCAAAAAa/...

First, create a 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();
  

We then use the frontend to sign with the xverse wallet and attempt PSBT finalization.

// 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) 

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 will give you the final PSBT that is converted into a raw transaction. Here is the decoded raw transaction:


  "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 we want to broadcast the transaction.

bitcoin-cli sendrawtransaction 0200000000010194a1929b44104244a9e6d36539a29c31bc9da1dacc849041fbffb3e0cef969fa0000000000feffffff01581b00000000000017a914836676150f0f5892416d8bd9bc5e923b494677f38704483045022100cf0ca0b7cbdda5e0c6733d508031c160cb93c7f4e9fcc6bafed4e50e7c23dd6e02200bc9a300ed5d6ec1202de946a59b4fdbb07136d5ffc43f8f5fe1e8fae6d291cd01210269bf7a1301185625758c324221cf6614e4a4104f90f83f633a8790f62919aa0a01005963a820ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb8876a914a5102a75c05993aa082ca365b5ba7f49bed517586751b17576a914a5102a75c05993aa082ca365b5ba7f49bed517586888ac01000000

And I got the following error:

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

Isn’t this the part? txinwitness That’s not the minimum, is it?

"00",

Rather, should it be so?

"0",

Or maybe

"",

Or is there another reason? OP_IF/NOTIF Isn’t the debate minimal?

Related Articles

Back to top button