Skip to content

Malformed multi-colon wrapper syntax such as v:j:multi(...) is accepted and silently canonicalized #13

Description

@Nuhiat-Arefin

Body

Summary

bitcoinerlab/miniscript accepts malformed wrapper syntax containing more than one : separator, for example:

v:j:multi(...)

This malformed form compiles successfully instead of being rejected as a parse error. It is silently treated the same as the valid wrapper chain:

vj:multi(...)

Minimal repro

const { analyzeMiniscript, compileMiniscript } = require("@bitcoinerlab/miniscript");

const invalid =
  "j:and_v(v:j:multi(1,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798),multi(1,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5))";

const valid =
  "j:and_v(vj:multi(1,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798),multi(1,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5))";

console.log(analyzeMiniscript(invalid, { tapscript: false }));
console.log(compileMiniscript(invalid, { tapscript: false }).asm);
console.log(compileMiniscript(valid, { tapscript: false }).asm);

Observed locally

  • analyzeMiniscript(invalid, { tapscript: false }) reports valid: true, issane: true
  • compileMiniscript(invalid, { tapscript: false }) succeeds
  • the compiled ASM for invalid is identical to the compiled ASM for valid

Expected behavior

The malformed descriptor should be rejected during parsing, because v:j:multi(...) contains multiple : separators in what is effectively being interpreted as a wrapper prefix.

The accepted / canonical form should be:

vj:multi(...)

not:

v:j:multi(...)

Cross-check against maintained peers

The same malformed invalid string is rejected by other implementations:

  • rust-miniscript: separator ':' occurred multiple times
  • elements-miniscript: «v:j:multi» has multiple instances of «:»
  • bitcoin/bitcoin: parse failed
  • embit: rejects during parse
  • NBitcoin: rejects during parse

Root cause

The issue appears to be in parseExpression, specifically the wrapper-stripping logic in src/compiler/parse.ts around lines 216–232.

That code strips wrapper prefixes in a while (true) loop, matching:

/^([a-z]+):/

and then slicing off one <letters>: group per iteration whenever every letter is a valid wrapper.

As a result, a malformed multi-colon chain such as:

v:j:multi(...)

is consumed as two successive wrapper groups:

v:
j:

leaving:

multi(...)

as the base expression.

This produces the same AST as the canonical single-colon form:

vj:multi(...)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions