Miniscript Policy
A high-level language for expressing Bitcoin spending conditions that compiles into optimized Miniscript and then into Bitcoin Script.
Key Takeaways
- Miniscript policy is a human-readable language for describing Bitcoin spending conditions using simple primitives like
pk,and,or, andthresh, sitting atop the three-layer stack of policy, Miniscript, and Bitcoin Script. - A compiler automatically translates policy expressions into the most efficient Miniscript representation, which then maps deterministically to raw Bitcoin Script opcodes, removing the need for manual Script optimization.
- Policy integrates with output descriptors and descriptor wallets, enabling portable, analyzable wallet configurations that multiple software implementations can import and verify.
What Is Miniscript Policy?
Miniscript policy is a high-level language for expressing Bitcoin spending conditions in a way that humans can read and reason about. Instead of writing raw Bitcoin Script opcodes directly, developers describe their intent using composable primitives: "require two of these three keys," "allow a backup key after 90 days," or "need both a signature and a hash preimage." A compiler then translates this intent into optimized Script automatically.
The policy language was developed alongside Miniscript by Pieter Wuille, Andrew Poelstra, and Sanket Kanjalkar. While Miniscript itself is a structured, well-specified subset of Bitcoin Script designed for analysis and composition, the policy language sits one layer above it as a developer-facing tool. Policy captures what you want to enforce; Miniscript and Script handle how it gets enforced on-chain.
How It Works
Miniscript policy operates through a three-layer compilation stack. Each layer serves a distinct purpose, and the boundaries between them are what make the system powerful.
The Three-Layer Stack
- Policy (human-readable intent): the developer writes a spending condition using high-level primitives. For example,
thresh(2, pk(Alice), pk(Bob), pk(Carol))expresses a 2-of-3 multisig requirement. - Miniscript (structured representation): the compiler converts the policy into a Miniscript expression tree. This intermediate form is well-specified: the same Miniscript expression always produces the same Bitcoin Script. It enables static analysis of spending conditions, including which keys are required, what timelocks apply, and how large the witness will be.
- Bitcoin Script (raw opcodes): Miniscript maps deterministically to the actual opcodes that Bitcoin nodes execute. This is the on-chain representation that gets embedded in transaction outputs and evaluated during spending.
Policy Primitives
The policy language provides a small set of composable building blocks:
pk(key): require a signature from the specified public keyolder(n): enforce a relative timelock of n blocks (uses OP_CHECKSEQUENCEVERIFY)after(n): enforce an absolute timelock at block height or time n (uses OP_CHECKLOCKTIMEVERIFY)sha256(hash): require a preimage whose SHA-256 matches the given hashand(X, Y): both sub-policies must be satisfiedor(X, Y): either sub-policy must be satisfiedthresh(k, X1, X2, ..., Xn): at least k of the n sub-policies must be satisfied
These primitives compose freely. Any sub-policy can be nested inside any combinator, allowing arbitrarily complex spending conditions to be built from simple, well-understood parts.
The Compilation Process
A single policy can often be expressed as multiple valid Miniscript representations, each producing different Bitcoin Script with different size and cost characteristics. The compiler's job is to find the most efficient encoding automatically.
Consider a vault policy where two partners normally sign together, but either can spend alone after a 90-day timeout:
// Policy: 2-of-2 normally, either key after 90 days
or(
and(pk(Alice), pk(Bob)),
and(
or(pk(Alice), pk(Bob)),
older(12960)
)
)The compiler evaluates different Miniscript representations for this policy, considering factors like script size, witness size, and which spending paths are more likely. Developers can supply probability hints using a weight prefix:
// The 9@ prefix tells the compiler that the first branch
// (both keys signing) is ~9x more likely than the fallback
or(9@and(pk(Alice), pk(Bob)), and(or(pk(Alice), pk(Bob)), older(12960)))These weights guide the compiler to place the more likely spending path in the cheaper position within the resulting Script. The compiler handles details like choosing between OP_IF/OP_ELSE orderings, selecting optimal Miniscript wrapper types, and minimizing witness sizes for common cases.
Policy Is Not Deterministic
An important distinction: policy-to-Miniscript compilation is not guaranteed to be deterministic. The same policy expression may produce different Miniscript across compiler versions, implementations, or even platforms. This means policy should not be used as a canonical representation for sharing wallet configurations.
By contrast, Miniscript-to-Script mapping is fully deterministic. One Miniscript expression always produces exactly one Bitcoin Script. This is why output descriptors reference the compiled Miniscript (or Script), not the original policy.
Descriptor and Wallet Integration
Miniscript policy connects to the broader Bitcoin wallet ecosystem through output descriptors. After compiling a policy to Miniscript, the result can be wrapped in a descriptor like wsh(...) for P2WSH outputs or used inside a Tapscript leaf for P2TR outputs.
For example, a compiled 2-of-3 policy might produce this descriptor:
wsh(thresh(2, pk(Alice), s:pk(Bob), s:pk(Carol)))Bitcoin Core has progressively added Miniscript support in its descriptor wallet implementation: watch-only support arrived in version 24.0, signing support for P2WSH descriptors in 25.0, and Tapscript support in version 26.0. Hardware wallets including Ledger have also added Miniscript signing support, enabling users to import complex spending policies onto signing devices.
Use Cases
Multi-Party Custody
Policy makes it straightforward to express complex custody arrangements that go beyond simple k-of-n multisig. A corporate treasury might use:
// 3-of-5 normally, 2-of-5 after 6 months (key recovery fallback)
or(
thresh(3, pk(CFO), pk(CEO), pk(CTO), pk(Legal), pk(Board)),
and(
thresh(2, pk(CFO), pk(CEO), pk(CTO), pk(Legal), pk(Board)),
older(26280)
)
)Without policy language, encoding this in raw Script would require deep knowledge of opcode interactions, stack manipulation, and witness structure. The policy compiler handles all of that.
Inheritance Planning
Time-decaying security policies are natural fits for the policy language. An inheritance scheme might start with full owner control and progressively relax requirements as time passes:
// Owner can always spend; after 1 year, heir + lawyer can spend
or(
pk(Owner),
and(
and(pk(Heir), pk(Lawyer)),
after(840000)
)
)Escrow and Conditional Payments
Combining key requirements with hash locks enables trustless escrow patterns. A buyer and seller can agree on a policy where either both sign to release funds, or an arbitrator resolves disputes:
// Buyer + Seller agree, OR Arbitrator + either party
or(
and(pk(Buyer), pk(Seller)),
and(pk(Arbitrator), or(pk(Buyer), pk(Seller)))
)Collaborative Wallet Recovery
Policy integrates naturally with social recovery schemes. A wallet might require the owner's key normally, but allow a threshold of trusted contacts to recover funds after a delay, all expressed in a single policy expression that compiles to a single Script.
Available Tools
Several tools and libraries support Miniscript policy compilation:
- The Miniscript playground by Pieter Wuille: a web-based compiler that lets you type a policy and see the resulting Miniscript, Script, and analysis
- The rust-miniscript library: provides policy compilation, Miniscript parsing, semantic analysis, and witness generation
- Bitcoin Core (v24.0+): native support for importing and spending from Miniscript-based descriptor wallets
Risks and Considerations
Compiler Output Variability
Because the policy-to-Miniscript step is not deterministic, the same policy may compile to different Scripts across tools or versions. Wallet developers should store and share the compiled Miniscript or descriptor, not the source policy. Relying on policy as the canonical representation risks generating incompatible addresses across different software.
Script Size Limits
Complex policies can produce large Scripts that approach or exceed Bitcoin's consensus limits. For P2WSH outputs, the witness script cannot exceed 10,000 bytes, and the overall number of opcodes is capped at 201. Taproot relaxes some of these limits through Tapscript, which removes the opcode limit and allows splitting complex policies across multiple Taptree leaves.
Analysis Assumptions
The policy compiler optimizes based on probability weights that developers provide. If the actual spending patterns differ significantly from these weights, the resulting Script may place the most-used spending path in a more expensive position. While this does not affect correctness, it can increase transaction fees for common operations.
Limited Expressiveness
The policy language deliberately limits what can be expressed. It cannot encode every possible Bitcoin Script program: only those that fall within Miniscript's structured subset. Features like introspecting transaction amounts or accessing stack values in arbitrary ways are outside its scope. This is a deliberate tradeoff: the constraints enable the static analysis and optimization that make the system valuable.
For a deeper technical exploration of how Miniscript enables composable spending conditions, see the research article on Miniscript spending policies and the Bitcoin Script programmability overview.
This glossary entry is for informational purposes only and does not constitute financial or investment advice. Always do your own research before using any protocol or technology.