Friday, September 19, 2025

javascript – bitcoinjs-lib: “bad-witness-nonstandard” when spending complicated P2WSH with OP_IF/ELSE

I am attempting to create and spend a P2WSH transaction on testnet with a “complicated” script, however I am operating right into a bad-witness-nonstandard error that I can not determine.

My Aim

I wish to create a P2WSH output with a script that permits spending underneath two situations:

A 3-of-4 multisig signature is offered.
OR
A timelock (e.g., 5 minutes) has handed AND a single restoration signature is offered.
The script logic makes use of OP_IF for the multisig path and OP_ELSE for the timelock path.

The Drawback

I can generate a pockets, derive the P2WSH handle, and fund it accurately. The funding transaction creates a regular P2WSH output (OP_0 <32-byte-hash>).

Nevertheless, when I attempt to spend this UTXO by way of the 3-of-4 multisig path, my transaction is rejected by my Bitcoin Core node’s testmempoolaccept with the error: mandatory-script-verify-flag-failed (bad-witness-nonstandard)

What I’ve Already Verified

The P2W

  1. SH handle derived from my scripts matches the handle I funded.
  2. A debug script confirms that my redeemScript is generated deterministically and its SHA256 hash accurately matches the hash within the funded UTXO’s scriptPubKey.
  3. The funding transaction is appropriate and customary.

To Reproduce the Error

Right here is all of the code and information wanted to breed the issue.

  1. package deal.json dependencies:
    {
      "dependencies": {
        "@varieties/node": "^20.12.12",
        "bitcoinjs-lib": "^6.1.5",
        "ecpair": "^2.1.0",
        "ts-node": "^10.9.2",
        "tiny-secp256k1": "^2.2.3",
        "typescript": "^5.4.5"
      }
    }
  1. Pockets Era Script (index.ts): This script generates the keys and pockets.json.
import * as bitcoin from 'bitcoinjs-lib'; import ECPairFactory from 'ecpair'; 
import * as ecc from 'tiny-secp256k1'; import * as fs from 'fs';

// Initialisation const ECPair = ECPairFactory(ecc); bitcoin.initEccLib(ecc);

const community = bitcoin.networks.testnet;

// --- 1. Key Era --- 
const multisigKeys = [
    ECPair.makeRandom({ network }),
    ECPair.makeRandom({ network }),
    ECPair.makeRandom({ network }),
    ECPair.makeRandom({ network }), ]; 

const multisigPubkeys = multisigKeys.map(key => Buffer.from(key.publicKey)).type((a, b) => a.evaluate(b)); 
const recoveryKey = ECPair.makeRandom({ community }); const recoveryPubkey = Buffer.from(recoveryKey.publicKey);

// --- 2. Timelock Definition (5 minutes for testing) --- 
const date = new Date(); 
date.setMinutes(date.getMinutes() + 5); 
const lockTime = Math.ground(date.getTime() / 1000); 
const lockTimeBuffer = bitcoin.script.quantity.encode(lockTime);

// --- 3. Redeem Script Building --- 
const redeemScript = bitcoin.script.compile([
    bitcoin.opcodes.OP_IF,
    bitcoin.opcodes.OP_3,
    ...multisigPubkeys,
    bitcoin.opcodes.OP_4,
    bitcoin.opcodes.OP_CHECKMULTISIG,
    bitcoin.opcodes.OP_ELSE,
    lockTimeBuffer,
    bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY,
    bitcoin.opcodes.OP_DROP,
    recoveryPubkey,
    bitcoin.opcodes.OP_CHECKSIG,
    bitcoin.opcodes.OP_ENDIF, ]);

// --- 4. Handle Creation --- 
const p2wsh = bitcoin.funds.p2wsh({
    redeem: { output: redeemScript, community },
    community, });

// --- 5. Save Information --- 
const pockets = {
    community: 'testnet',
    lockTime: lockTime,
    lockTimeDate: date.toISOString(),
    p2wshAddress: p2wsh.handle,
    redeemScriptHex: redeemScript.toString('hex'),
    multisigKeysWIF: multisigKeys.map(ok => ok.toWIF()),
    recoveryKeyWIF: recoveryKey.toWIF(), };

fs.writeFileSync('pockets.json', JSON.stringify(pockets, null, 2));

console.log('Pockets generated and saved to pockets.json'); 
console.log('P2WSH Deposit Handle:', pockets.p2wshAddress);
  1. Multisig Spending Script (1_spend_multisig.ts): That is the script that fails with bad-witness-nonstandard.
import * as bitcoin from 'bitcoinjs-lib'; 
import ECPairFactory from 'ecpair'; 
import * as ecc from 'tiny-secp256k1'; 
import * as fs from 'fs';

// --- UTXO Configuration --- 
const UTXO_TXID = 'PASTE_YOUR_FUNDING_TXID_HERE'; 
const UTXO_INDEX = 0; // Or 1, relying on the output 
const UTXO_VALUE_SATS = 10000; // Quantity in satoshis 
const DESTINATION_ADDRESS = 'PASTE_A_TESTNET_ADDRESS_HERE'; 
const FEE_SATS = 2000;

// --- Initialization --- 
const ECPair = ECPairFactory(ecc); bitcoin.initEccLib(ecc); 
const community = bitcoin.networks.testnet;

// --- 1. Load Pockets --- 
const pockets = JSON.parse(fs.readFileSync('pockets.json', 'utf-8')); 
const redeemScript = Buffer.from(pockets.redeemScriptHex, 'hex'); 
const p2wsh = bitcoin.funds.p2wsh({ redeem: { output: redeemScript, community }, community }); 
const multisigKeys = pockets.multisigKeysWIF.map((wif: string) => ECPair.fromWIF(wif, community));

// --- 2. Construct PSBT --- 
const psbt = new bitcoin.Psbt({ community }); psbt.addInput({
    hash: UTXO_TXID,
    index: UTXO_INDEX,
    witnessUtxo: { script: p2wsh.output!, worth: UTXO_VALUE_SATS },
    witnessScript: redeemScript, }); 
psbt.addOutput({ handle: DESTINATION_ADDRESS, worth: UTXO_VALUE_SATS - FEE_SATS });

// --- 3. Signal Transaction --- 
const createSigner = (key: any) => ({   publicKey: Buffer.from(key.publicKey),   
    signal: (hash: Buffer): Buffer => Buffer.from(key.signal(hash)), }); // Signal with 3 of the 4 keys 
psbt.signInput(0, createSigner(multisigKeys[0])); 
psbt.signInput(0, createSigner(multisigKeys[1])); 
psbt.signInput(0, createSigner(multisigKeys[2]));

// --- 4. Finalize Transaction --- 
const finalizer = (inputIndex: quantity, enter: any) => {
    const emptySignature = Buffer.from([]); // Placeholder for OP_CHECKMULTISIG bug
    const partialSignatures = enter.partialSig.map((ps: any) => ps.signature);
    const witnessStack = [
        emptySignature,
        ...partialSignatures,
        bitcoin.script.number.encode(1), // Standard way to push OP_1
        redeemScript,
    ];
    const witness = witnessStack.scale back((acc, merchandise) => {
        const push = bitcoin.script.compile([item]);
        return Buffer.concat([acc, push]);
    }, Buffer.from([witnessStack.length]));
    return { finalScriptWitness: witness }; }; psbt.finalizeInput(0, finalizer);

// --- 5. Extract and create validation command --- 
const tx = psbt.extractTransaction(); 
const txHex = tx.toHex(); 
console.log('n--- testmempoolaccept command ---'); 
console.log(`bitcoin-cli -testnet testmempoolaccept '["${txHex}"]'`);
  1. Information to breed:

pockets.json (TESTNET KEYS, NO VALUE):

{   
    "community": "testnet",   
    "lockTime": 1723986942,   
    "lockTimeDate": "2025-08-18T13:15:42.339Z",   
    "p2wshAddress": "tb1qztq5rg30lv8y7kup7tftuelppcy2f9u9ygm8daq7gv4lgf0dw3ss3hj9qw",   
    "redeemScriptHex": "6353210200847c4a13f98cb1e3138bda175ba6f4c7ffd9e03a4c8617878ab03cf4a4a97921024b3e2544b4e311985477d88ac77ea00aa68f85490d0c663fba38fcdf582d043f2102c822f5026d382a93476d20de66c87c5e4e4997654817bfacc69b29f2dc8b6a10210328c2213b0813b4dac9c063f674b2c61dc50344c6e093df045c8ee2fe09f67bd854ae6704c413a368b1752103c5512e31f8a2555a116146262382be4be774fca326a2ee01d71e0fe33ffe4925ac68", 
    "multisigKeysWIF": [
        "cT5h8LgJ2a4V3c4yF5g6H7j8K9L0M1n2p3q4R5s6T7u8V9w0XyZ",
        "cT5h8LgJ2a4V3c4yF5g6H7j8K9L0M1n2p3q4R5s6T7u8V9w0XyZ",
        "cT5h8LgJ2a4V3c4yF5g6H7j8K9L0M1n2p3q4R5s6T7u8V9w0XyZ",
        "cT5h8LgJ2a4V3c4yF5g6H7j8K9L0M1n2p3q4R5s6T7u8V9w0XyZ"   ],   
"recoveryKeyWIF": "cT5h8LgJ2a4V3c4yF5g6H7j8K9L0M1n2p3q4R5s6T7u8V9w0XyZ" }

(Word: this might be generated utilizing index.ts).

Funding Transaction:
 
Funded Handle: tb1qztq5rg30lv8y7kup7tftuelppcy2f9u9ygm8daq7gv4lgf0dw3ss3hj9qw   
Funding TXID: e9e764b3c63740d0eef68506970e80f819d360bdfc173d0b983f1e3d5411096d  
Funding VOUT: 1   
Funding ScriptPubKey: OP_0 12c141a22ffb0e4f5b81f2d2be67e10e08a49785223676f41e432bf425ed7461  

Any thought why my manually constructed witness can be thought-about non-standard?
Thanks.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Stay Connected

0FansLike
0FollowersFollow
0SubscribersSubscribe
- Advertisement -spot_img

Latest Articles