BitcoinjsLib – Earlybirds Invest https://earlybirdsinvest.com Latest Crypto News Mon, 18 Aug 2025 15:16:57 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.8 https://i0.wp.com/earlybirdsinvest.com/wp-content/uploads/2024/12/cropped-New-Project-2024-12-17T235703.455.png?fit=32%2C32&ssl=1 BitcoinjsLib – Earlybirds Invest https://earlybirdsinvest.com 32 32 240146708 Bitcoinjs-Lib: "Malicious non-standard" When using complex p2wsh in op_if/else https://earlybirdsinvest.com/bitcoinjs-lib-malicious-non-standard-when-using-complex-p2wsh-in-op_if-else/ https://earlybirdsinvest.com/bitcoinjs-lib-malicious-non-standard-when-using-complex-p2wsh-in-op_if-else/#respond Mon, 18 Aug 2025 15:16:56 +0000 https://earlybirdsinvest.com/bitcoinjs-lib-malicious-non-standard-when-using-complex-p2wsh-in-op_if-else/

I’m trying to create and spend a P2WSH transaction on TestNet with a “complex” script, but I’m running into a bad, purposeless error that I don’t know.

My goal

I want to create a P2WSH output using a script that allows spending under two conditions:

A 4-to-4 multi-sig signature is provided. Alternatively, a time lock (for example, 5 minutes) will pass and a single recovery signature will be provided. The script logic uses OP_IF for the Multisig path and OP_ELSE for the Timelock path.

problem

You can generate wallets, derive P2WSH addresses, and provide funding correctly. Funds trading creates standard P2WSH output (OP_0 <32-byte-hash>).

However, if you try to use this UTXO via a 3-4 multisig path, the transaction is rejected by testMempoolAccept in Bitcoin Core Node with an error.

What I’ve already verified

P2W

  1. The SH address derived from my script matches the address I funded.
  2. The debug script checks that my redeemscript is deterministically generated and that sha256 hash matches correctly with the hash of the funded utxo scriptpubkey.
  3. Funding transactions are correct and standard.

Reproduce the error

There is all the code and data needed to reproduce the problem.

  1. package.json dependencies:
{
  "dependencies": {
    "@types/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. Wallet Generation Script (index.ts): This script generates keys and wallet.json.

*Imported as Bitcoin from ‘bitcoinjs-lib’. Import ecpairfactory from “ecpair”. Imported as ECC from ‘tiny-secp256k1’. Import as fs from ‘fs’ *.

//Initialize const ecpair = ecpairfactory(ecc); bitcoin.initecclib(ecc);

const network = bitcoin.networks.testnet;

// — 1. Key Generation — const multisigkeys =(ecpair.makerandom({network}), ecpair.makerandom({network}), ecpair.makerandom({network}), ecpair.makerandom({network}), ); const multisigpubkeys = multisigkeys.map(key => buffer.from(key.publickey)). const recoverykey = ecpair.makerandom({network}); const recoverypubkey = buffer.from(RecoveryKey.publickey);

// — 2. Time lock definition (5 minutes for tests) — const date = new date(); date.setminutes(date.getMinutes() + 5); const locktime = math.floor(date.getTime() / 1000); const locktimebuffer = bitcoin.script.number.encode(locktime);

// -bitcoin.opcodes.op_else, locktimebuffer, bitcoin.opcodes.op_checktimeverify, bitcoin.opcodes.op_drop, recoveryubkey, bitcoin.opcodes.op_checksig, bitcoin.opcodes.op_endif));

// — 4. Address creation — const p2wsh = bitcoin.payments.p2wsh({redeeem:{output:redeeemscript,network},network,});

// — 5. Data storage — const wallet = {Network: ‘testnet’, locktime: locktime, locktimedate: date.toisostring(), p2wshaddress: p2wsh.address, reeemscriptex: redeemscript.toString(‘hex’), multigkeyswif: multisigkeys.map() RecoveryKeyWif: RecoveryKey.Towif(), };

fs.writefilesync(‘wallet.json’, json.stringify(wallet, null, 2));

console.log (‘wallets generated and saved in wallet.json’); console.log (‘p2wsh deposit address:’, wallet.p2wshaddress);

  1. Multisig Expense Script (1_SPEND_MULTISIG.TS): This is a script that fails with a bad purpose attitude.

*Imported as Bitcoin from ‘bitcoinjs-lib’. Import ecpairfactory from “ecpair”. Imported 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, const utxo_value_sats = 10000; // atshis const destination_address= ‘paste_a_testnet_address_here’; const fee_sats = 2000;

// —Initialization— const ecpair = ecpairfactory(ecc); bitcoin.initecclib(ecc); const network = bitcoin.networks.testnet;

// — 1. Load wallet — const wallet = json.parse(fs.readfilesync(‘wallet.json’, ‘utf-8’)); const redeemscript = buffer.from(wallet.redeemscriptex, ‘hex’); const p2wsh = bitcoin.payments.p2wsh({redeem:{output:redeemscript,network},network}); const multisigkeys = wallet.multisigkeyswif.map((wif:string)=> ecpair.fromwif(wif,network));

// —2. buildpsbt — const psbt = new bitcoin.psbt ({network}); psbt.addinput({hash: utxo_txid, index: utxo_index, witherututxo: {script: p2wsh.output!, value: utxo_value_sats}, withinsscript: redeemscript, }); psbt.addoutput({address: destination_address, value: utxo_value_sats -fee_sats});

// — 3. Symbol Transaction — const createsigner =(key: any)=>({{
publicKey: buffer.from(key.publickey), sign: (hash:buffer): buffer => buffer.from(key.sign(hash)), }); // Sign with 3 of the 4 keys (0, Createsigner(Multisigkeys(0))). psbt.signinput(0, createsigner(multisigkeys(1))); psbt.signinput(0, createsigner(multisigkeys(2)));

// — 4. Finalize transaction — constinginizer = (inputIndex: number, input: any) => {const emptysignature = buffer.from(()); // op_checkmultisig bug const partialsignatures = input.partialsig.map((ps: any)=> ps.signature); const veritionStack = (emptysignature, … partialsignatures, bitcoin.script.number.encode(1), // Standard way to push OP_1 RedeemScript); const videns = withingstack.reduce((acc, item)=> {const push = bitcoin.script.compile((item)); return buffer.concat((acc,push));}, buffer.from((withingstack.length))); return {finalalscriptwitness:withing}; }; psbt.finalizeinput(0, finalizer);

// — 5. Extract and create a validation command —- const tx = psbt.extracttransaction(); const txhex = tx.tohex(); console.log( ‘\n — testmempolaccept command —‘); console.log(bitcoin-cli -testnet testmempoolaccept '("${txHex}")');

  1. Data to replicate:

wallet.json (testnet key, no value):

{“network”: “testnet”, “locktime”: 1723986942, “locktimedate”: “2025-08-18t13:15:42.339z”, “p2wshaddress”: “TB1QZTQ5RG30LV8Y7KUP7TFTUELPPCY2F9U9YGM8DAQ7GV4LGF0DW3SS3HJ9QW”,
“Redeemscriptex”: “6353210200847c4a13f98cb1e3138bda175ba6f4c7ffd9e03a4c8617878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787 85477D88AC77EA00A68F85490D0C663FBA38FCDF582D043F2102C8225556D382A93476D20DE66C87C5E4E499765 4817BFACC69B29F2DC8B6A10210328C2213B0813B4DAC9C063F674B2C61DC50344C6E093DF045C8EE2FE09F67BD85 4AE6704C413A368B1752103C5512E31F8A2555A116146262382BE4BE774FCA326A2EE01D71E0FE33FFE4925AC68″”, “multisigkeyswif”: (“ct5h8lgj2a4v3c4yf5g6h7j8k9l0m1n2p3pp3pp3p3q4r5s6t7u8v9w0xyz”, “,” ct5h8lgj2a4vv3c4yf5g6h7j8k9l0m1n2q4r59ws6t7u8v25t7ws6t7ws6t7v259w0x4r50x4r50x4r50x4r59wt7 “CT5H8LGJ2A4V3C4YF5G6H7J8K9L0M1N2PP3Q4R5S6T7U8V9W0XYZ”, “CT5H8LGJ2A4V3C4YF5G6H7J8K9L0M1N2P3Q4R5S6T7U8V9WWWWF”: “: “CT5H8LGJ2A4V3C4YF5G6H7J8K9L0M1N2PP3Q4R5S6T7U8V9W0XYZ”}

(Note: this is generated using index.ts).

Funding Transactions:

Funding: TB1QZTQ5RG30LG30LV8Y7KUP7TFTUELPPCY2F9U9YGM8DAQ7GV4LGF0DW3SS3HJ9QWFundingTXID: E9E764B3C63740D0EEF68506970E80F819D360BDFC173D0B983F1E3D5411096DFundingVOUT: 1FundingScriptPubkey: OP_0 12C141A22FFB0E4F5B81F2D2BE67E10E08A49785223676F41E432BF425ED7461

Why is my manually constructed witness considered non-standard? thank you.

]]>
https://earlybirdsinvest.com/bitcoinjs-lib-malicious-non-standard-when-using-complex-p2wsh-in-op_if-else/feed/ 0 53830