// Any two of A, B, and C
multi(2, A, B, C)A Lean 4 model connecting typed Miniscript to the Bitcoin Script it compiles.
Introduction
We use MiniscriptA typed structure for composing and analyzing spending conditions. to describe Bitcoin spending conditions in a structured form. Miniscript tools can analyze this structure and compile it into the lower-level Bitcoin ScriptThe stack program that Bitcoin nodes execute. that nodes execute.
Plain language
The spending condition stated without implementation details.
Two of the three signers must approve the spend.
Structured Miniscript
A readable form that software can analyze and compose.
multi(2,A,B,C)Modeled Bitcoin Script
Execution consumes the input stack and leaves a Boolean result on top.
2ABC3CHECKMULTISIG0sigAsigCtrueCompilation
Bitcoin nodes do not execute Miniscript directly. A compiler, usually part of wallet or library software, translates it into the ordered Bitcoin Script instructions they execute.
1 · Miniscript input
or_i(primary, recovery)// Any two of A, B, and C
multi(2, A, B, C)// After DELAY, use key R
and_v(v:older(DELAY), pk(R))2 · Compiler rules
or_i(X, Z)==>IF X ELSE Z ENDIFmulti(k, keys)==>k keys n CHECKMULTISIGv:older(n)==>n CHECKSEQUENCEVERIFY VERIFYpk(key)==>key CHECKSIG3 · Bitcoin Script output
IF
// Primary path
2 A B C 3 CHECKMULTISIG
ELSE
// Recovery path
DELAY CHECKSEQUENCEVERIFY
VERIFY R CHECKSIG
ENDIFThe verification problem
The compiler has produced a Script. However, can we trust its translation? It is easy to assume that the output means the same thing as the Miniscript input. If the translation is wrong, the compiled Script could allow an extra spending case or produce different stack behavior.
Miniscript expects
truetruerejectedMust match
expected(m)
=
execute(compile(m))
same input · same transactionCompiled Script produces
truetruerejectedCorrect compilation keeps the two behavior sets equal. Faulty compilation can add a result that Miniscript never accounted for.
Concrete example A 2022 rust-miniscript security advisory documented this kind of mismatch: a satisfied d: child was typed as leaving exactly 1, while some compiled Scripts could leave another true value where MINIMALIF was not a consensus rule.
Lean for formal verification
Expression
m : CoreFragment
Represent expressions, then assign each well-typed expression its expected stack behavior.Translation
compile m
Specify the Script instructions emitted for each constructor.Execution semantics
Eval (compile m) … result
Relate the initial stacks and transaction context to the resulting stacks—or failure.Theorem target
expected = actual
The general proof for every supported expression is still in progress.The central goal is a theorem about every supported Miniscript expression. If Lean assigns an expression a type, evaluating its compiled Script must have the stack behavior recorded by that type. Proving this constructor by constructor rules out compilation steps that introduce results the type system did not account for.
Why it matters
Lean Miniscript gives wallet and library developers a way to check what a Miniscript compiler does instead of simply trusting its output. It is verification infrastructure, not a wallet feature by itself.
Follow each Miniscript constructor into the Bitcoin Script instructions it produces. This makes the compiler easier to review and debug.
For supported constructors, Lean checks that the generated Script follows the BIP 379 scheme. This can reveal translation mistakes before compiled policies reach users.
As implementations add constructors, optimizations, or Script contexts, their rules and proofs can be added to the same model. A general type-soundness proof is still in progress.