Body
Summary
bitcoinerlab/miniscript accepts malformed wrapper syntax containing more than one : separator, for example:
This malformed form compiles successfully instead of being rejected as a parse error. It is silently treated the same as the valid wrapper chain:
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:
not:
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:
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:
is consumed as two successive wrapper groups:
leaving:
as the base expression.
This produces the same AST as the canonical single-colon form:
Body
Summary
bitcoinerlab/miniscriptaccepts malformed wrapper syntax containing more than one:separator, for example:This malformed form compiles successfully instead of being rejected as a parse error. It is silently treated the same as the valid wrapper chain:
Minimal repro
Observed locally
analyzeMiniscript(invalid, { tapscript: false })reportsvalid: true,issane: truecompileMiniscript(invalid, { tapscript: false })succeedsinvalidis identical to the compiled ASM forvalidExpected 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:
not:
Cross-check against maintained peers
The same malformed
invalidstring is rejected by other implementations:rust-miniscript:separator ':' occurred multiple timeselements-miniscript:«v:j:multi» has multiple instances of «:»bitcoin/bitcoin: parse failedembit: rejects during parseNBitcoin: rejects during parseRoot cause
The issue appears to be in
parseExpression, specifically the wrapper-stripping logic insrc/compiler/parse.tsaround 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:
is consumed as two successive wrapper groups:
leaving:
as the base expression.
This produces the same AST as the canonical single-colon form: