From 829842aaf950dff3e3b4f196d6862dd9b02cdc5a Mon Sep 17 00:00:00 2001 From: Otto Allmendinger Date: Mon, 7 Oct 2024 17:16:54 +0200 Subject: [PATCH 1/2] feat(utxo-bin): add nodeCatchError Wrapper around `node()` and `handleParseError()` to catch errors and return a parse error node instead of throwing an error. Issue: BTC-1533 --- modules/utxo-bin/src/Parser.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modules/utxo-bin/src/Parser.ts b/modules/utxo-bin/src/Parser.ts index e05f01cf4c..b60fe20901 100644 --- a/modules/utxo-bin/src/Parser.ts +++ b/modules/utxo-bin/src/Parser.ts @@ -23,9 +23,11 @@ export type ParserNode = { export class Parser { parseError: 'throw' | 'continue'; + constructor(params: { parseError?: 'throw' | 'continue' } = {}) { this.parseError = params.parseError ?? 'continue'; } + node(label: string | number, value: ParserNodeValue, nodes: ParserNode[] = []): ParserNode { if (!isParserNodeValue(value)) { throw new Error(`invalid node value ${typeof value}`); @@ -38,6 +40,18 @@ export class Parser { }; } + nodeCatchError( + label: string | number, + buildValue: () => ParserNodeValue | undefined, + buildNodes: () => ParserNode[] = () => [] + ): ParserNode { + try { + return this.node(label, buildValue?.(), buildNodes()); + } catch (e) { + return this.handleParseError(e); + } + } + handleParseError(e: unknown): ParserNode { if (this.parseError === 'throw') { throw e; From 018dcf52c48b7f1d1129aace8a78590f70e82e97 Mon Sep 17 00:00:00 2001 From: Otto Allmendinger Date: Mon, 7 Oct 2024 17:17:54 +0200 Subject: [PATCH 2/2] feat(utxo-bin): parse script as miniscript Issue: BTC-1533 --- modules/utxo-bin/src/ScriptParser.ts | 14 ++++++++++++++ .../utxo-bin/test/fixtures/formatScript/p2sh.txt | 1 + .../test/fixtures/formatScript/p2shP2wsh.txt | 2 ++ .../utxo-bin/test/fixtures/formatScript/p2wsh.txt | 1 + 4 files changed, 18 insertions(+) diff --git a/modules/utxo-bin/src/ScriptParser.ts b/modules/utxo-bin/src/ScriptParser.ts index 78bf4844b2..8d2523cfe0 100644 --- a/modules/utxo-bin/src/ScriptParser.ts +++ b/modules/utxo-bin/src/ScriptParser.ts @@ -1,4 +1,5 @@ import * as utxolib from '@bitgo/utxo-lib'; +import { Miniscript } from '@bitgo/wasm-miniscript'; import { Parser, ParserNode } from './Parser'; import { parseUnknown } from './parseUnknown'; @@ -21,6 +22,18 @@ function parsePaymentWithType(script: Buffer, type: PaymentType, network?: utxol } } +function asMiniscript(script: Buffer): Miniscript | undefined { + const contexts = ['tap', 'segwitv0', 'legacy'] as const; + for (const ctx of contexts) { + try { + return Miniscript.fromBitcoinScript(script, ctx); + } catch (e) { + continue; + } + } + throw new Error('failed to parse as miniscript'); +} + export class ScriptParser extends Parser { network?: utxolib.Network; constructor({ network }: { network?: utxolib.Network } = {}) { @@ -76,6 +89,7 @@ export class ScriptParser extends Parser { const classification = ScriptParser.classify(script, undefined); const decompiled = utxolib.script.decompile(script); return this.node('script', `length ${script.length} bytes`, [ + this.nodeCatchError('miniscript', () => asMiniscript(script)?.toString()), this.node('classification', undefined, [ this.node('input', classification.input), this.node('output', classification.output), diff --git a/modules/utxo-bin/test/fixtures/formatScript/p2sh.txt b/modules/utxo-bin/test/fixtures/formatScript/p2sh.txt index cf1e3b8864..f1578e9d4f 100644 --- a/modules/utxo-bin/test/fixtures/formatScript/p2sh.txt +++ b/modules/utxo-bin/test/fixtures/formatScript/p2sh.txt @@ -1,6 +1,7 @@ inputScripts └─┬ redeemScript └─┬ script: length 105 bytes + ├── miniscript: multi(2,03f6f40764bd5d63f200a2778883acf75e96f15095c998263c087270d0c97e7e7f,035ffb7abc70159e0469f4b989a6d5e1785a2904169ff050b2f468fe5d3d5dbbf2,03e1524d7f6fc57ab3eacbb659b787106780a475d1db483952c2310b7e9a38975b) ├─┬ classification │ ├── input: nonstandard │ ├── output: multisig diff --git a/modules/utxo-bin/test/fixtures/formatScript/p2shP2wsh.txt b/modules/utxo-bin/test/fixtures/formatScript/p2shP2wsh.txt index 07338fcb63..f62c4d88e1 100644 --- a/modules/utxo-bin/test/fixtures/formatScript/p2shP2wsh.txt +++ b/modules/utxo-bin/test/fixtures/formatScript/p2shP2wsh.txt @@ -1,6 +1,7 @@ inputScripts ├─┬ redeemScript │ └─┬ script: length 34 bytes +│ ├── error: Error: failed to parse as miniscript │ ├─┬ classification │ │ ├── input: nonstandard │ │ ├── output: witnessscripthash @@ -20,6 +21,7 @@ inputScripts │ └── witness: undefined └─┬ witnessScript └─┬ script: length 105 bytes + ├── miniscript: multi(2,03393e3c784cf42a09c32b33b3ee2879ab092fd381c2187deafe6b69e0dc5bee4e,02089e4bad05bf0f0a67f5055c6518d5ae05de86db8081e9d5d2e654c710280507,03c154061b8a77902eeebf5c39429515b572161af576610f66f0d4269940f0f81b) ├─┬ classification │ ├── input: nonstandard │ ├── output: multisig diff --git a/modules/utxo-bin/test/fixtures/formatScript/p2wsh.txt b/modules/utxo-bin/test/fixtures/formatScript/p2wsh.txt index 5704c990a0..88a2cbd41f 100644 --- a/modules/utxo-bin/test/fixtures/formatScript/p2wsh.txt +++ b/modules/utxo-bin/test/fixtures/formatScript/p2wsh.txt @@ -1,6 +1,7 @@ inputScripts └─┬ witnessScript └─┬ script: length 105 bytes + ├── miniscript: multi(2,03199dc3912467b37ff890cc1740c8ef4ac7a50bf3741d99590e905ca3a79c7670,02396d5d0c79d779f9cf019cea9e81d2576bad476ea87ed634416205605a55ce8f,0286de50435e3680740715bfa3f7d41c9620d80d93b2c02672f53efa0c6e68d182) ├─┬ classification │ ├── input: nonstandard │ ├── output: multisig