Tuesday, November 5, 2024

script – Why is my P2WSH OP_IF/NOTIF argument not minimal?

I’ve created an HTLC with the next Bitcoin script utilizing 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’m able to unlock the funds with within the OP_IF department by utilizing the right secret, and spending from the recipient public key. Nonetheless, I’m encountering a problem when making an attempt to redeem funds by way of the OP_ELSE department of the script utilizing the timelock lockduration and broadcasting utilizing the sender public key.

The particular error I get is:

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

I imagine the error stems from how I’m encoding my values. I’m constructing a PSBT which I then signal with xverse and convert right into a uncooked transaction utilizing bitcoin-cli finalizepsbt cHNidP8BAFMCAAAAAa/...

Firstly I create the PSBT

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

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


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

    // Recreate the HTLC script utilizing the supplied 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({ community: community })
      .addInput({
        hash: htlcTxId,
        index: htlcOutputIndex,
        sequence: 0xfffffffe, // Obligatory for OP_CHECKLOCKTIMEVERIFY
        witnessUtxo: {
          script: scriptPubKey,
          worth: refundAmount,
        },
        witnessScript: redeemScript,
      })
      .addOutput({
        deal with: recipientAddress,
        worth: refundAmount - 3000, // Subtract a nominal price
      })
      .setVersion(2)
      
      .setLocktime(lockduration);
  
    console.log("PSBT to be signed:", psbt.toBase64());

    return psbt.toBase64();
  }

Then I signal it with xverse pockets utilizing a entrance finish and try and finalize the PSBT.

// This script is for refund

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

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

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

    operate writeVarInt(i: quantity) {
        const varintLen = varuint.encodingLength(i);
        const currentLen = buffer.size;
        buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
        varuint.encode(i, buffer, currentLen);
    }

    operate writeVarSlice(slice: string) {
        writeVarInt(slice.size);
        writeSlice(slice);
    }

    operate writeVector(vector: any) {
        writeVarInt(vector.size);
        vector.forEach(writeVarSlice);
    }

    writeVector(witness);
    return buffer;
}

operate getFinalScriptsSpecial(inputIndex: quantity, enter: any, script: any, isSegwit: boolean, isP2SH: boolean, isP2WSH: boolean) {
    if (!enter.partialSig || enter.partialSig.size === 0) {
        throw new Error(`Can not finalize enter #${inputIndex}: Lacking partial signatures`);
    }
    if (!enter.witnessUtxo) {
        throw new Error(`Can not finalize enter #${inputIndex}: Lacking witness UTXO`);
    }
    if (!script) {
        throw new Error(`Can not finalize enter #${inputIndex}: Lacking script`);
    }

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

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

    return {
        finalScriptWitness: finalScriptWitness
    };
}

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

    let psbt: any;

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

        psbt.finalizeInput(0, getFinalScriptsSpecial);

        //including witness script hex again in from bitcoin-cli decodepsbt <psbtBase64> as a result of psbt.finalizeInput removes it for some purpose     
        psbt.information.inputs[0].witnessScript = Buffer.from(witnessHexWithdraw, 'hex');

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

    return psbt.toBase64();
}

This provides me a finalized PSBT which I convert right into a uncooked transaction. Right here is the uncooked transaction decoded:

{
  "txid": "d773911f5e0c2bd4590794676e7c95bd2e33d8156c22e64c80e8c5bf4cfcbece",
  "hash": "5f1025270b4f2b298d2a90647c2640cc222815d0df3e2d38b72b9e37b04c14ff",
  "model": 2,
  "measurement": 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 attempt to broadcast the transaction

bitcoin-cli sendrawtransaction 0200000000010194a1929b44104244a9e6d36539a29c31bc9da1dacc849041fbffb3e0cef969fa0000000000feffffff01581b00000000000017a914836676150f0f5892416d8bd9bc5e923b494677f38704483045022100cf0ca0b7cbdda5e0c6733d508031c160cb93c7f4e9fcc6bafed4e50e7c23dd6e02200bc9a300ed5d6ec1202de946a59b4fdbb07136d5ffc43f8f5fe1e8fae6d291cd01210269bf7a1301185625758c324221cf6614e4a4104f90f83f633a8790f62919aa0a01005963a820ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb8876a914a5102a75c05993aa082ca365b5ba7f49bed517586751b17576a914a5102a75c05993aa082ca365b5ba7f49bed517586888ac01000000

And I’m met with the next error:

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

May it’s this a part of the txinwitness that isn’t minimal?

"00",

Ought to it as an alternative be

"0",

Or even perhaps

"",

Or is there another purpose my OP_IF/NOTIF argument will not be minimal?

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles