From 6ec2822357e48cad2c9b1a31c0b97af0d6baad89 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 10 May 2024 12:08:16 +0800 Subject: [PATCH 1/4] perfs: optimize script.decomplie return type --- src/payments/p2sh.js | 2 +- src/payments/p2tr.js | 2 +- src/payments/p2wsh.js | 9 +++------ src/psbt.js | 6 +++--- src/psbt/psbtutils.js | 6 +++--- src/script.d.ts | 2 +- src/script.js | 5 ++--- test/script.spec.ts | 2 +- ts_src/payments/embed.ts | 7 +++---- ts_src/payments/p2ms.ts | 4 ++-- ts_src/payments/p2sh.ts | 9 +++------ ts_src/payments/p2tr.ts | 2 +- ts_src/payments/p2wsh.ts | 9 +++------ ts_src/psbt.ts | 6 +++--- ts_src/psbt/psbtutils.ts | 6 +++--- ts_src/script.ts | 13 +++++-------- ts_src/transaction.ts | 2 +- 17 files changed, 39 insertions(+), 53 deletions(-) diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index 1386966be..7a7bbd243 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -138,7 +138,7 @@ function p2sh(a, opts) { // is the redeem output empty/invalid? if (redeem.output) { const decompile = bscript.decompile(redeem.output); - if (!decompile || decompile.length < 1) + if (decompile.length < 1) throw new TypeError('Redeem.output too short'); if (redeem.output.byteLength > 520) throw new TypeError( diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index 33fedb464..ccf803aa8 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -230,7 +230,7 @@ function p2tr(a, opts) { throw new TypeError('Redeem.redeemVersion and witness mismatch'); } if (a.redeem.output) { - if (bscript.decompile(a.redeem.output).length === 0) + if (!bscript.decompile(a.redeem.output).length) throw new TypeError('Redeem.output is invalid'); // output redeem is constructed from the witness if (o.redeem.output && !a.redeem.output.equals(o.redeem.output)) diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js index a3422e50a..b45e3e6e3 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -171,8 +171,7 @@ function p2wsh(a, opts) { // is the redeem output non-empty/valid? if (a.redeem.output) { const decompile = bscript.decompile(a.redeem.output); - if (!decompile || decompile.length < 1) - throw new TypeError('Redeem.output is invalid'); + if (!decompile.length) throw new TypeError('Redeem.output is invalid'); if (a.redeem.output.byteLength > 3600) throw new TypeError( 'Redeem.output unspendable if larger than 3600 bytes', @@ -198,9 +197,7 @@ function p2wsh(a, opts) { if ( (a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) || (a.redeem.output && - (bscript.decompile(a.redeem.output) || []).some( - chunkHasUncompressedPubkey, - )) + bscript.decompile(a.redeem.output).some(chunkHasUncompressedPubkey)) ) { throw new TypeError( 'redeem.input or redeem.output contains uncompressed pubkey', @@ -213,7 +210,7 @@ function p2wsh(a, opts) { throw new TypeError('Witness and redeem.output mismatch'); if ( a.witness.some(chunkHasUncompressedPubkey) || - (bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey) + bscript.decompile(wScript).some(chunkHasUncompressedPubkey) ) throw new TypeError('Witness contains uncompressed pubkey'); } diff --git a/src/psbt.js b/src/psbt.js index cc0e5c8ad..b95bd8b9d 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1636,7 +1636,7 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) { function redeemFromFinalScriptSig(finalScript) { if (!finalScript) return; const decomp = bscript.decompile(finalScript); - if (!decomp) return; + if (!decomp.length) return; const lastItem = decomp[decomp.length - 1]; if ( !Buffer.isBuffer(lastItem) || @@ -1645,7 +1645,7 @@ function redeemFromFinalScriptSig(finalScript) { ) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp) return; + if (!sDecomp.length) return; return lastItem; } function redeemFromFinalWitnessScript(finalScript) { @@ -1654,7 +1654,7 @@ function redeemFromFinalWitnessScript(finalScript) { const lastItem = decomp[decomp.length - 1]; if (isPubkeyLike(lastItem)) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp) return; + if (!sDecomp.length) return; return lastItem; } function compressPubkey(pubkey) { diff --git a/src/psbt/psbtutils.js b/src/psbt/psbtutils.js index ea5f1d719..bf1cbd2d0 100644 --- a/src/psbt/psbtutils.js +++ b/src/psbt/psbtutils.js @@ -79,7 +79,7 @@ function pubkeyPositionInScript(pubkey, script) { const pubkeyHash = (0, crypto_1.hash160)(pubkey); const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling? const decompiled = bscript.decompile(script); - if (decompiled === null) throw new Error('Unknown script error'); + if (!decompiled.length) throw new Error('Unknown script error'); return decompiled.findIndex(element => { if (typeof element === 'number') return false; return ( @@ -173,10 +173,10 @@ function extractPartialSigs(input) { function getPsigsFromInputFinalScripts(input) { const scriptItems = !input.finalScriptSig ? [] - : bscript.decompile(input.finalScriptSig) || []; + : bscript.decompile(input.finalScriptSig); const witnessItems = !input.finalScriptWitness ? [] - : bscript.decompile(input.finalScriptWitness) || []; + : bscript.decompile(input.finalScriptWitness); return scriptItems .concat(witnessItems) .filter(item => { diff --git a/src/script.d.ts b/src/script.d.ts index ffc8c89bb..e8563cf03 100644 --- a/src/script.d.ts +++ b/src/script.d.ts @@ -14,7 +14,7 @@ export declare function countNonPushOnlyOPs(value: Stack): number; * @throws Error if the compilation fails. */ export declare function compile(chunks: Buffer | Stack): Buffer; -export declare function decompile(buffer: Buffer | Array): Array | null; +export declare function decompile(buffer: Buffer | Array): Stack; /** * Converts the given chunks into an ASM (Assembly) string representation. * If the chunks parameter is a Buffer, it will be decompiled into a Stack before conversion. diff --git a/src/script.js b/src/script.js index be2805190..996d640d0 100644 --- a/src/script.js +++ b/src/script.js @@ -115,7 +115,6 @@ function compile(chunks) { } exports.compile = compile; function decompile(buffer) { - // TODO: remove me if (chunksIsArray(buffer)) return buffer; typeforce(types.Buffer, buffer); const chunks = []; @@ -126,10 +125,10 @@ function decompile(buffer) { if (opcode > ops_1.OPS.OP_0 && opcode <= ops_1.OPS.OP_PUSHDATA4) { const d = pushdata.decode(buffer, i); // did reading a pushDataInt fail? - if (d === null) return null; + if (d === null) return []; i += d.size; // attempt to read too much data? - if (i + d.number > buffer.length) return null; + if (i + d.number > buffer.length) return []; const data = buffer.slice(i, i + d.number); i += d.number; // decompile minimally diff --git a/test/script.spec.ts b/test/script.spec.ts index d593ab17d..570d1ccc0 100644 --- a/test/script.spec.ts +++ b/test/script.spec.ts @@ -158,7 +158,7 @@ describe('script', () => { () => { const chunks = bscript.decompile(Buffer.from(f.script, 'hex')); - assert.strictEqual(chunks, null); + assert.deepStrictEqual(chunks, []); }, ); }); diff --git a/ts_src/payments/embed.ts b/ts_src/payments/embed.ts index aef14e1b7..1acbab6ca 100644 --- a/ts_src/payments/embed.ts +++ b/ts_src/payments/embed.ts @@ -36,16 +36,15 @@ export function p2data(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'data', () => { if (!a.output) return; - return bscript.decompile(a.output)!.slice(1); + return bscript.decompile(a.output).slice(1); }); // extended validation if (opts.validate) { if (a.output) { const chunks = bscript.decompile(a.output); - if (chunks![0] !== OPS.OP_RETURN) - throw new TypeError('Output is invalid'); - if (!chunks!.slice(1).every(typef.Buffer)) + if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid'); + if (!chunks.slice(1).every(typef.Buffer)) throw new TypeError('Output is invalid'); if (a.data && !stacksEqual(a.data, o.data as Buffer[])) diff --git a/ts_src/payments/p2ms.ts b/ts_src/payments/p2ms.ts index ffbf0155b..c5d7d7ac3 100644 --- a/ts_src/payments/p2ms.ts +++ b/ts_src/payments/p2ms.ts @@ -55,7 +55,7 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment { function decode(output: Buffer | Stack): void { if (decoded) return; decoded = true; - chunks = bscript.decompile(output) as Stack; + chunks = bscript.decompile(output); o.m = (chunks[0] as number) - OP_INT_BASE; o.n = (chunks[chunks.length - 2] as number) - OP_INT_BASE; o.pubkeys = chunks.slice(1, -2) as Buffer[]; @@ -90,7 +90,7 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'signatures', () => { if (!a.input) return; - return bscript.decompile(a.input)!.slice(1); + return bscript.decompile(a.input).slice(1); }); lazy.prop(o, 'input', () => { if (!a.signatures) return; diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts index 2f5f936c6..fbb449450 100644 --- a/ts_src/payments/p2sh.ts +++ b/ts_src/payments/p2sh.ts @@ -105,10 +105,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { lazy.prop(o, 'input', () => { if (!a.redeem || !a.redeem.input || !a.redeem.output) return; return bscript.compile( - ([] as Stack).concat( - bscript.decompile(a.redeem.input) as Stack, - a.redeem.output, - ), + ([] as Stack).concat(bscript.decompile(a.redeem.input), a.redeem.output), ); }); lazy.prop(o, 'witness', () => { @@ -157,7 +154,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { // is the redeem output empty/invalid? if (redeem.output) { const decompile = bscript.decompile(redeem.output); - if (!decompile || decompile.length < 1) + if (decompile.length < 1) throw new TypeError('Redeem.output too short'); if (redeem.output.byteLength > 520) throw new TypeError( @@ -182,7 +179,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { if (hasInput && hasWitness) throw new TypeError('Input and witness provided'); if (hasInput) { - const richunks = bscript.decompile(redeem.input) as Stack; + const richunks = bscript.decompile(redeem.input); if (!bscript.isPushOnly(richunks)) throw new TypeError('Non push-only scriptSig'); } diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index c1140b715..ac7e8bb92 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -252,7 +252,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { } if (a.redeem.output) { - if (bscript.decompile(a.redeem.output)!.length === 0) + if (!bscript.decompile(a.redeem.output).length) throw new TypeError('Redeem.output is invalid'); // output redeem is constructed from the witness diff --git a/ts_src/payments/p2wsh.ts b/ts_src/payments/p2wsh.ts index bd9339277..1456a7008 100644 --- a/ts_src/payments/p2wsh.ts +++ b/ts_src/payments/p2wsh.ts @@ -183,8 +183,7 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { // is the redeem output non-empty/valid? if (a.redeem.output) { const decompile = bscript.decompile(a.redeem.output); - if (!decompile || decompile.length < 1) - throw new TypeError('Redeem.output is invalid'); + if (!decompile.length) throw new TypeError('Redeem.output is invalid'); if (a.redeem.output.byteLength > 3600) throw new TypeError( 'Redeem.output unspendable if larger than 3600 bytes', @@ -212,9 +211,7 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { if ( (a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) || (a.redeem.output && - (bscript.decompile(a.redeem.output) || []).some( - chunkHasUncompressedPubkey, - )) + bscript.decompile(a.redeem.output).some(chunkHasUncompressedPubkey)) ) { throw new TypeError( 'redeem.input or redeem.output contains uncompressed pubkey', @@ -228,7 +225,7 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { throw new TypeError('Witness and redeem.output mismatch'); if ( a.witness.some(chunkHasUncompressedPubkey) || - (bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey) + bscript.decompile(wScript).some(chunkHasUncompressedPubkey) ) throw new TypeError('Witness contains uncompressed pubkey'); } diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index b2aa36f1f..2ed52418f 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -2148,7 +2148,7 @@ function redeemFromFinalScriptSig( ): Buffer | undefined { if (!finalScript) return; const decomp = bscript.decompile(finalScript); - if (!decomp) return; + if (!decomp.length) return; const lastItem = decomp[decomp.length - 1]; if ( !Buffer.isBuffer(lastItem) || @@ -2157,7 +2157,7 @@ function redeemFromFinalScriptSig( ) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp) return; + if (!sDecomp.length) return; return lastItem; } @@ -2169,7 +2169,7 @@ function redeemFromFinalWitnessScript( const lastItem = decomp[decomp.length - 1]; if (isPubkeyLike(lastItem)) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp) return; + if (!sDecomp.length) return; return lastItem; } diff --git a/ts_src/psbt/psbtutils.ts b/ts_src/psbt/psbtutils.ts index 19cf33e5b..3b2fcedaf 100644 --- a/ts_src/psbt/psbtutils.ts +++ b/ts_src/psbt/psbtutils.ts @@ -75,7 +75,7 @@ export function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number { const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling? const decompiled = bscript.decompile(script); - if (decompiled === null) throw new Error('Unknown script error'); + if (!decompiled.length) throw new Error('Unknown script error'); return decompiled.findIndex(element => { if (typeof element === 'number') return false; @@ -178,10 +178,10 @@ function extractPartialSigs(input: PsbtInput): Buffer[] { function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] { const scriptItems = !input.finalScriptSig ? [] - : bscript.decompile(input.finalScriptSig) || []; + : bscript.decompile(input.finalScriptSig); const witnessItems = !input.finalScriptWitness ? [] - : bscript.decompile(input.finalScriptWitness) || []; + : bscript.decompile(input.finalScriptWitness); return scriptItems .concat(witnessItems) .filter(item => { diff --git a/ts_src/script.ts b/ts_src/script.ts index 54ee98fde..f858c467d 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -111,10 +111,7 @@ export function compile(chunks: Buffer | Stack): Buffer { return buffer; } -export function decompile( - buffer: Buffer | Array, -): Array | null { - // TODO: remove me +export function decompile(buffer: Buffer | Array): Stack { if (chunksIsArray(buffer)) return buffer; typeforce(types.Buffer, buffer); @@ -130,11 +127,11 @@ export function decompile( const d = pushdata.decode(buffer, i); // did reading a pushDataInt fail? - if (d === null) return null; + if (d === null) return []; i += d.size; // attempt to read too much data? - if (i + d.number > buffer.length) return null; + if (i + d.number > buffer.length) return []; const data = buffer.slice(i, i + d.number); i += d.number; @@ -166,7 +163,7 @@ export function decompile( */ export function toASM(chunks: Buffer | Array): string { if (chunksIsBuffer(chunks)) { - chunks = decompile(chunks) as Stack; + chunks = decompile(chunks); } return chunks @@ -211,7 +208,7 @@ export function fromASM(asm: string): Buffer { * @returns The stack of buffers. */ export function toStack(chunks: Buffer | Array): Buffer[] { - chunks = decompile(chunks) as Stack; + chunks = decompile(chunks); typeforce(isPushOnly, chunks); return chunks.map(op => { diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index 665583ec5..784f8b01c 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -284,7 +284,7 @@ export class Transaction { // ignore OP_CODESEPARATOR const ourScript = bscript.compile( - bscript.decompile(prevOutScript)!.filter(x => { + bscript.decompile(prevOutScript).filter(x => { return x !== opcodes.OP_CODESEPARATOR; }), ); From ac6a5b21b28c50d3d8d4af01011d915ca867ff8e Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 10 May 2024 19:11:01 +0800 Subject: [PATCH 2/4] Revert "perfs: optimize script.decomplie return type" This reverts commit 6ec2822357e48cad2c9b1a31c0b97af0d6baad89. --- src/payments/p2sh.js | 2 +- src/payments/p2tr.js | 2 +- src/payments/p2wsh.js | 9 ++++++--- src/psbt.js | 6 +++--- src/psbt/psbtutils.js | 6 +++--- src/script.d.ts | 2 +- src/script.js | 5 +++-- test/script.spec.ts | 2 +- ts_src/payments/embed.ts | 7 ++++--- ts_src/payments/p2ms.ts | 4 ++-- ts_src/payments/p2sh.ts | 9 ++++++--- ts_src/payments/p2tr.ts | 2 +- ts_src/payments/p2wsh.ts | 9 ++++++--- ts_src/psbt.ts | 6 +++--- ts_src/psbt/psbtutils.ts | 6 +++--- ts_src/script.ts | 13 ++++++++----- ts_src/transaction.ts | 2 +- 17 files changed, 53 insertions(+), 39 deletions(-) diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index 7a7bbd243..1386966be 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -138,7 +138,7 @@ function p2sh(a, opts) { // is the redeem output empty/invalid? if (redeem.output) { const decompile = bscript.decompile(redeem.output); - if (decompile.length < 1) + if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output too short'); if (redeem.output.byteLength > 520) throw new TypeError( diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js index ccf803aa8..33fedb464 100644 --- a/src/payments/p2tr.js +++ b/src/payments/p2tr.js @@ -230,7 +230,7 @@ function p2tr(a, opts) { throw new TypeError('Redeem.redeemVersion and witness mismatch'); } if (a.redeem.output) { - if (!bscript.decompile(a.redeem.output).length) + if (bscript.decompile(a.redeem.output).length === 0) throw new TypeError('Redeem.output is invalid'); // output redeem is constructed from the witness if (o.redeem.output && !a.redeem.output.equals(o.redeem.output)) diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js index b45e3e6e3..a3422e50a 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -171,7 +171,8 @@ function p2wsh(a, opts) { // is the redeem output non-empty/valid? if (a.redeem.output) { const decompile = bscript.decompile(a.redeem.output); - if (!decompile.length) throw new TypeError('Redeem.output is invalid'); + if (!decompile || decompile.length < 1) + throw new TypeError('Redeem.output is invalid'); if (a.redeem.output.byteLength > 3600) throw new TypeError( 'Redeem.output unspendable if larger than 3600 bytes', @@ -197,7 +198,9 @@ function p2wsh(a, opts) { if ( (a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) || (a.redeem.output && - bscript.decompile(a.redeem.output).some(chunkHasUncompressedPubkey)) + (bscript.decompile(a.redeem.output) || []).some( + chunkHasUncompressedPubkey, + )) ) { throw new TypeError( 'redeem.input or redeem.output contains uncompressed pubkey', @@ -210,7 +213,7 @@ function p2wsh(a, opts) { throw new TypeError('Witness and redeem.output mismatch'); if ( a.witness.some(chunkHasUncompressedPubkey) || - bscript.decompile(wScript).some(chunkHasUncompressedPubkey) + (bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey) ) throw new TypeError('Witness contains uncompressed pubkey'); } diff --git a/src/psbt.js b/src/psbt.js index b95bd8b9d..cc0e5c8ad 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1636,7 +1636,7 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) { function redeemFromFinalScriptSig(finalScript) { if (!finalScript) return; const decomp = bscript.decompile(finalScript); - if (!decomp.length) return; + if (!decomp) return; const lastItem = decomp[decomp.length - 1]; if ( !Buffer.isBuffer(lastItem) || @@ -1645,7 +1645,7 @@ function redeemFromFinalScriptSig(finalScript) { ) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp.length) return; + if (!sDecomp) return; return lastItem; } function redeemFromFinalWitnessScript(finalScript) { @@ -1654,7 +1654,7 @@ function redeemFromFinalWitnessScript(finalScript) { const lastItem = decomp[decomp.length - 1]; if (isPubkeyLike(lastItem)) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp.length) return; + if (!sDecomp) return; return lastItem; } function compressPubkey(pubkey) { diff --git a/src/psbt/psbtutils.js b/src/psbt/psbtutils.js index bf1cbd2d0..ea5f1d719 100644 --- a/src/psbt/psbtutils.js +++ b/src/psbt/psbtutils.js @@ -79,7 +79,7 @@ function pubkeyPositionInScript(pubkey, script) { const pubkeyHash = (0, crypto_1.hash160)(pubkey); const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling? const decompiled = bscript.decompile(script); - if (!decompiled.length) throw new Error('Unknown script error'); + if (decompiled === null) throw new Error('Unknown script error'); return decompiled.findIndex(element => { if (typeof element === 'number') return false; return ( @@ -173,10 +173,10 @@ function extractPartialSigs(input) { function getPsigsFromInputFinalScripts(input) { const scriptItems = !input.finalScriptSig ? [] - : bscript.decompile(input.finalScriptSig); + : bscript.decompile(input.finalScriptSig) || []; const witnessItems = !input.finalScriptWitness ? [] - : bscript.decompile(input.finalScriptWitness); + : bscript.decompile(input.finalScriptWitness) || []; return scriptItems .concat(witnessItems) .filter(item => { diff --git a/src/script.d.ts b/src/script.d.ts index e8563cf03..ffc8c89bb 100644 --- a/src/script.d.ts +++ b/src/script.d.ts @@ -14,7 +14,7 @@ export declare function countNonPushOnlyOPs(value: Stack): number; * @throws Error if the compilation fails. */ export declare function compile(chunks: Buffer | Stack): Buffer; -export declare function decompile(buffer: Buffer | Array): Stack; +export declare function decompile(buffer: Buffer | Array): Array | null; /** * Converts the given chunks into an ASM (Assembly) string representation. * If the chunks parameter is a Buffer, it will be decompiled into a Stack before conversion. diff --git a/src/script.js b/src/script.js index 996d640d0..be2805190 100644 --- a/src/script.js +++ b/src/script.js @@ -115,6 +115,7 @@ function compile(chunks) { } exports.compile = compile; function decompile(buffer) { + // TODO: remove me if (chunksIsArray(buffer)) return buffer; typeforce(types.Buffer, buffer); const chunks = []; @@ -125,10 +126,10 @@ function decompile(buffer) { if (opcode > ops_1.OPS.OP_0 && opcode <= ops_1.OPS.OP_PUSHDATA4) { const d = pushdata.decode(buffer, i); // did reading a pushDataInt fail? - if (d === null) return []; + if (d === null) return null; i += d.size; // attempt to read too much data? - if (i + d.number > buffer.length) return []; + if (i + d.number > buffer.length) return null; const data = buffer.slice(i, i + d.number); i += d.number; // decompile minimally diff --git a/test/script.spec.ts b/test/script.spec.ts index 570d1ccc0..d593ab17d 100644 --- a/test/script.spec.ts +++ b/test/script.spec.ts @@ -158,7 +158,7 @@ describe('script', () => { () => { const chunks = bscript.decompile(Buffer.from(f.script, 'hex')); - assert.deepStrictEqual(chunks, []); + assert.strictEqual(chunks, null); }, ); }); diff --git a/ts_src/payments/embed.ts b/ts_src/payments/embed.ts index 1acbab6ca..aef14e1b7 100644 --- a/ts_src/payments/embed.ts +++ b/ts_src/payments/embed.ts @@ -36,15 +36,16 @@ export function p2data(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'data', () => { if (!a.output) return; - return bscript.decompile(a.output).slice(1); + return bscript.decompile(a.output)!.slice(1); }); // extended validation if (opts.validate) { if (a.output) { const chunks = bscript.decompile(a.output); - if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid'); - if (!chunks.slice(1).every(typef.Buffer)) + if (chunks![0] !== OPS.OP_RETURN) + throw new TypeError('Output is invalid'); + if (!chunks!.slice(1).every(typef.Buffer)) throw new TypeError('Output is invalid'); if (a.data && !stacksEqual(a.data, o.data as Buffer[])) diff --git a/ts_src/payments/p2ms.ts b/ts_src/payments/p2ms.ts index c5d7d7ac3..ffbf0155b 100644 --- a/ts_src/payments/p2ms.ts +++ b/ts_src/payments/p2ms.ts @@ -55,7 +55,7 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment { function decode(output: Buffer | Stack): void { if (decoded) return; decoded = true; - chunks = bscript.decompile(output); + chunks = bscript.decompile(output) as Stack; o.m = (chunks[0] as number) - OP_INT_BASE; o.n = (chunks[chunks.length - 2] as number) - OP_INT_BASE; o.pubkeys = chunks.slice(1, -2) as Buffer[]; @@ -90,7 +90,7 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment { }); lazy.prop(o, 'signatures', () => { if (!a.input) return; - return bscript.decompile(a.input).slice(1); + return bscript.decompile(a.input)!.slice(1); }); lazy.prop(o, 'input', () => { if (!a.signatures) return; diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts index fbb449450..2f5f936c6 100644 --- a/ts_src/payments/p2sh.ts +++ b/ts_src/payments/p2sh.ts @@ -105,7 +105,10 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { lazy.prop(o, 'input', () => { if (!a.redeem || !a.redeem.input || !a.redeem.output) return; return bscript.compile( - ([] as Stack).concat(bscript.decompile(a.redeem.input), a.redeem.output), + ([] as Stack).concat( + bscript.decompile(a.redeem.input) as Stack, + a.redeem.output, + ), ); }); lazy.prop(o, 'witness', () => { @@ -154,7 +157,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { // is the redeem output empty/invalid? if (redeem.output) { const decompile = bscript.decompile(redeem.output); - if (decompile.length < 1) + if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output too short'); if (redeem.output.byteLength > 520) throw new TypeError( @@ -179,7 +182,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { if (hasInput && hasWitness) throw new TypeError('Input and witness provided'); if (hasInput) { - const richunks = bscript.decompile(redeem.input); + const richunks = bscript.decompile(redeem.input) as Stack; if (!bscript.isPushOnly(richunks)) throw new TypeError('Non push-only scriptSig'); } diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts index ac7e8bb92..c1140b715 100644 --- a/ts_src/payments/p2tr.ts +++ b/ts_src/payments/p2tr.ts @@ -252,7 +252,7 @@ export function p2tr(a: Payment, opts?: PaymentOpts): Payment { } if (a.redeem.output) { - if (!bscript.decompile(a.redeem.output).length) + if (bscript.decompile(a.redeem.output)!.length === 0) throw new TypeError('Redeem.output is invalid'); // output redeem is constructed from the witness diff --git a/ts_src/payments/p2wsh.ts b/ts_src/payments/p2wsh.ts index 1456a7008..bd9339277 100644 --- a/ts_src/payments/p2wsh.ts +++ b/ts_src/payments/p2wsh.ts @@ -183,7 +183,8 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { // is the redeem output non-empty/valid? if (a.redeem.output) { const decompile = bscript.decompile(a.redeem.output); - if (!decompile.length) throw new TypeError('Redeem.output is invalid'); + if (!decompile || decompile.length < 1) + throw new TypeError('Redeem.output is invalid'); if (a.redeem.output.byteLength > 3600) throw new TypeError( 'Redeem.output unspendable if larger than 3600 bytes', @@ -211,7 +212,9 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { if ( (a.redeem.input && _rchunks().some(chunkHasUncompressedPubkey)) || (a.redeem.output && - bscript.decompile(a.redeem.output).some(chunkHasUncompressedPubkey)) + (bscript.decompile(a.redeem.output) || []).some( + chunkHasUncompressedPubkey, + )) ) { throw new TypeError( 'redeem.input or redeem.output contains uncompressed pubkey', @@ -225,7 +228,7 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { throw new TypeError('Witness and redeem.output mismatch'); if ( a.witness.some(chunkHasUncompressedPubkey) || - bscript.decompile(wScript).some(chunkHasUncompressedPubkey) + (bscript.decompile(wScript) || []).some(chunkHasUncompressedPubkey) ) throw new TypeError('Witness contains uncompressed pubkey'); } diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 2ed52418f..b2aa36f1f 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -2148,7 +2148,7 @@ function redeemFromFinalScriptSig( ): Buffer | undefined { if (!finalScript) return; const decomp = bscript.decompile(finalScript); - if (!decomp.length) return; + if (!decomp) return; const lastItem = decomp[decomp.length - 1]; if ( !Buffer.isBuffer(lastItem) || @@ -2157,7 +2157,7 @@ function redeemFromFinalScriptSig( ) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp.length) return; + if (!sDecomp) return; return lastItem; } @@ -2169,7 +2169,7 @@ function redeemFromFinalWitnessScript( const lastItem = decomp[decomp.length - 1]; if (isPubkeyLike(lastItem)) return; const sDecomp = bscript.decompile(lastItem); - if (!sDecomp.length) return; + if (!sDecomp) return; return lastItem; } diff --git a/ts_src/psbt/psbtutils.ts b/ts_src/psbt/psbtutils.ts index 3b2fcedaf..19cf33e5b 100644 --- a/ts_src/psbt/psbtutils.ts +++ b/ts_src/psbt/psbtutils.ts @@ -75,7 +75,7 @@ export function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number { const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling? const decompiled = bscript.decompile(script); - if (!decompiled.length) throw new Error('Unknown script error'); + if (decompiled === null) throw new Error('Unknown script error'); return decompiled.findIndex(element => { if (typeof element === 'number') return false; @@ -178,10 +178,10 @@ function extractPartialSigs(input: PsbtInput): Buffer[] { function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] { const scriptItems = !input.finalScriptSig ? [] - : bscript.decompile(input.finalScriptSig); + : bscript.decompile(input.finalScriptSig) || []; const witnessItems = !input.finalScriptWitness ? [] - : bscript.decompile(input.finalScriptWitness); + : bscript.decompile(input.finalScriptWitness) || []; return scriptItems .concat(witnessItems) .filter(item => { diff --git a/ts_src/script.ts b/ts_src/script.ts index f858c467d..54ee98fde 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -111,7 +111,10 @@ export function compile(chunks: Buffer | Stack): Buffer { return buffer; } -export function decompile(buffer: Buffer | Array): Stack { +export function decompile( + buffer: Buffer | Array, +): Array | null { + // TODO: remove me if (chunksIsArray(buffer)) return buffer; typeforce(types.Buffer, buffer); @@ -127,11 +130,11 @@ export function decompile(buffer: Buffer | Array): Stack { const d = pushdata.decode(buffer, i); // did reading a pushDataInt fail? - if (d === null) return []; + if (d === null) return null; i += d.size; // attempt to read too much data? - if (i + d.number > buffer.length) return []; + if (i + d.number > buffer.length) return null; const data = buffer.slice(i, i + d.number); i += d.number; @@ -163,7 +166,7 @@ export function decompile(buffer: Buffer | Array): Stack { */ export function toASM(chunks: Buffer | Array): string { if (chunksIsBuffer(chunks)) { - chunks = decompile(chunks); + chunks = decompile(chunks) as Stack; } return chunks @@ -208,7 +211,7 @@ export function fromASM(asm: string): Buffer { * @returns The stack of buffers. */ export function toStack(chunks: Buffer | Array): Buffer[] { - chunks = decompile(chunks); + chunks = decompile(chunks) as Stack; typeforce(isPushOnly, chunks); return chunks.map(op => { diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index 784f8b01c..665583ec5 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -284,7 +284,7 @@ export class Transaction { // ignore OP_CODESEPARATOR const ourScript = bscript.compile( - bscript.decompile(prevOutScript).filter(x => { + bscript.decompile(prevOutScript)!.filter(x => { return x !== opcodes.OP_CODESEPARATOR; }), ); From bbd61bfd7afbdff2b860ad6548062240b562dfb2 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Mon, 13 May 2024 20:05:28 +0800 Subject: [PATCH 3/4] perfs: toASM throw error when receive invalid chunks --- src/script.js | 3 +++ ts_src/script.ts | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/script.js b/src/script.js index be2805190..0bc4475aa 100644 --- a/src/script.js +++ b/src/script.js @@ -158,6 +158,9 @@ function toASM(chunks) { if (chunksIsBuffer(chunks)) { chunks = decompile(chunks); } + if (!chunks) { + throw new Error('convert invalid chunks to ASM'); + } return chunks .map(chunk => { // data? diff --git a/ts_src/script.ts b/ts_src/script.ts index 54ee98fde..455dce3d3 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -168,7 +168,9 @@ export function toASM(chunks: Buffer | Array): string { if (chunksIsBuffer(chunks)) { chunks = decompile(chunks) as Stack; } - + if (!chunks) { + throw new Error('convert invalid chunks to ASM'); + } return chunks .map(chunk => { // data? From 7efa3f9d6ee6e2d9bb90c10211d5b14f99fba9a9 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 17 May 2024 12:07:46 +0800 Subject: [PATCH 4/4] fix: update toASM error tips --- src/script.js | 2 +- ts_src/script.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/script.js b/src/script.js index 0bc4475aa..c95f0cc67 100644 --- a/src/script.js +++ b/src/script.js @@ -159,7 +159,7 @@ function toASM(chunks) { chunks = decompile(chunks); } if (!chunks) { - throw new Error('convert invalid chunks to ASM'); + throw new Error('Could not convert invalid chunks to ASM'); } return chunks .map(chunk => { diff --git a/ts_src/script.ts b/ts_src/script.ts index 455dce3d3..c2a033569 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -169,7 +169,7 @@ export function toASM(chunks: Buffer | Array): string { chunks = decompile(chunks) as Stack; } if (!chunks) { - throw new Error('convert invalid chunks to ASM'); + throw new Error('Could not convert invalid chunks to ASM'); } return chunks .map(chunk => {