From ad140dc60a32a9ced5d2772d355731f60f2553c6 Mon Sep 17 00:00:00 2001 From: natsoni Date: Wed, 5 Mar 2025 15:29:54 +0100 Subject: [PATCH] Tapscript multisig parsing --- frontend/src/app/shared/script.utils.ts | 61 +++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/frontend/src/app/shared/script.utils.ts b/frontend/src/app/shared/script.utils.ts index 731e0051b..62a7a5845 100644 --- a/frontend/src/app/shared/script.utils.ts +++ b/frontend/src/app/shared/script.utils.ts @@ -251,6 +251,11 @@ export function detectScriptTemplate(type: ScriptType, script_asm: string, witne return ScriptTemplates.multisig(multisig.m, multisig.n); } + const tapscriptMultisig = parseTapscriptMultisig(script_asm); + if (tapscriptMultisig) { + return ScriptTemplates.multisig(tapscriptMultisig.m, tapscriptMultisig.n); + } + return; } @@ -299,6 +304,62 @@ export function parseMultisigScript(script: string): undefined | { m: number, n: return { m, n }; } +export function parseTapscriptMultisig(script: string): undefined | { m: number, n: number } { + if (!script) { + return; + } + + const ops = script.split(' '); + // At minimum, one pubkey group (3 tokens) + m push + final opcode = 5 tokens + if (ops.length < 5) return; + + const finalOp = ops.pop(); + if (finalOp !== 'OP_NUMEQUAL' && finalOp !== 'OP_GREATERTHANOREQUAL') { + return; + } + + let m: number; + if (['OP_PUSHBYTES_1', 'OP_PUSHBYTES_2'].includes(ops[ops.length - 2])) { + const data = ops.pop(); + ops.pop(); + m = parseInt(data.match(/../g).reverse().join(''), 16); + } else if (ops[ops.length - 1].startsWith('OP_PUSHNUM_') || ops[ops.length - 1] === 'OP_0') { + m = parseInt(ops.pop().match(/[0-9]+/)?.[0], 10); + } else { + return; + } + + if (ops.length % 3 !== 0) { + return; + } + const n = ops.length / 3; + if (n < 1) { + return; + } + + for (let i = 0; i < n; i++) { + const push = ops.shift(); + const pubkey = ops.shift(); + const sigOp = ops.shift(); + + if (push !== 'OP_PUSHBYTES_32') { + return; + } + if (!/^[0-9a-fA-F]{64}$/.test(pubkey)) { + return; + } + if (sigOp !== (i === 0 ? 'OP_CHECKSIG' : 'OP_CHECKSIGADD')) { + return; + } + } + + if (ops.length) { + return; + } + + return { m, n }; +} + export function getVarIntLength(n: number): number { if (n < 0xfd) { return 1;