๐จ Miniscript Studio
Basic examples:
Complex examples:
Basic examples:
Complex examples:
๐ Key variables
Define key variables to use names like "Alice" and "Bob". Use public keys only!
Key type generation:
No key variables defined.
๐พ Saved policies
No saved policies yet.
๐ Saved miniscript
No saved expressions yet.
๐ Policy reference
๐ What is Policy Language?
Policy Language is a high-level, human-readable way to describe Bitcoin spending conditions. It's designed for non-technical users to express complex logic like "Alice can spend immediately, OR Bob can spend after 1 week, OR any 2 of 3 trustees can spend." The policy compiler automatically converts these intuitive expressions into optimized miniscript and Bitcoin Script.
Key benefits: Natural language syntax โข Automatic optimization โข Error prevention โข Supports all Bitcoin script features
๐ Authentication Functions
pk(key) - Public Key Signature
What it does: Requires a digital signature from the specified public key.
When to use: Single-user wallets, simple ownership, hot wallets.
Key formats: Variable names (Alice), hex pubkeys (02abc...), or xpub descriptors ([fingerprint/path]xpub...)
Example: pk(Alice) - Only Alice can spend
Why choose: Most efficient option (34 bytes), compiles directly to CHECKSIG.
pkh(key) - Public Key Hash (P2PKH Style)
What it does: Requires revealing the public key AND providing a signature.
When to use: Traditional Bitcoin addresses, privacy until spending, shorter scriptPubKeys.
Trade-offs: Slightly less efficient than pk() but hides the public key until spending.
Example: pkh(Alice) - Alice must reveal her key and sign
Why choose: Standard address format, privacy benefits, 20-byte hash storage.
โฐ Time-based Conditions
after(n) - Absolute Timelock
What it does: Can only spend after a specific block height or Unix timestamp.
When to use: Scheduled payments, vesting schedules, inheritance planning, time-delayed vaults.
Format: Block height (n < 500000000) or Unix timestamp (n โฅ 500000000)
Examples: after(800000) - after block 800k,
after(1767225600) - after Jan 1, 2026
Why choose: Enforces global time constraints, perfect for future-dated transactions.
older(n) - Relative Timelock
What it does: Must wait n blocks after UTXO creation before spending.
When to use: Recovery paths, Lightning channels, forced hodling, vault timeouts.
Common values: 144 (1 day), 1008 (1 week), 4320 (1 month)
Example: or(pk(Alice),and(pk(Bob),older(144))) - Bob can recover after 1 day
Why choose: Creates delays relative to UTXO age, ideal for timeout-based recovery.
๐ Hash-based Conditions
sha256(h) - SHA256 Preimage
What it does: Requires revealing data that produces the given SHA256 hash.
When to use: Atomic swaps, HTLCs, payment channels, proof of knowledge.
Format: 64 hex characters (32-byte hash)
Example: and(pk(Alice),sha256(abc123...)) - Alice + secret knowledge
Why choose: Most common hash function, widely supported, secure.
hash256(h) - Double SHA256
What it does: Requires preimage for SHA256(SHA256(data)).
When to use: Bitcoin-native hashing, compatibility with Bitcoin's block/transaction hashing.
Example: Cross-chain swaps with Bitcoin-style chains
Why choose: Standard Bitcoin hashing, resistance to length extension attacks.
hash160(h) - Bitcoin Address Hash
What it does: Requires preimage for RIPEMD160(SHA256(data)).
Format: 40 hex characters (20-byte hash)
When to use: Bitcoin address generation, compatibility with existing systems.
Why choose: Standard for Bitcoin addresses, compact 20-byte representation.
ripemd160(h) - RIPEMD160 Hash
What it does: Requires preimage for RIPEMD160 hash.
Format: 40 hex characters (20-byte hash)
When to use: Specialized use cases, rarely used alone.
Why choose: Part of Bitcoin's cryptographic suite, specific compatibility needs.
๐ Logic Operators
and(X,Y) - Both Conditions Required
What it does: Both X AND Y must be satisfied to spend.
When to use: Multi-signature wallets, complex authorization, dual requirements.
Optimization tip: Order matters - put the more likely to fail condition first.
Example: and(pk(Alice),pk(Bob)) - requires both signatures
Why choose: Clear intent, automatic optimization to and_v() miniscript.
or(X,Y) - Either Condition Works
What it does: Either X OR Y can satisfy (or both).
When to use: Backup paths, multiple spending methods, recovery options.
Auto-optimization: Compiler chooses optimal miniscript combinator (or_b, or_c, or_d, or_i).
Example: or(pk(Alice),and(pk(Bob),older(144))) - Alice immediately OR Bob after delay
Why choose: Flexible spending paths, compiler handles complexity.
thresh(k,X,Y,Z,...) - K-of-N Threshold
What it does: Exactly k out of n conditions must be satisfied.
When to use: Flexible multisig, mixed condition types, complex governance.
Advantage: More flexible than multi() - works with any policy fragments, not just keys.
Examples: thresh(2,pk(Alice),pk(Bob),pk(Charlie))
for 2-of-3, thresh(2,pk(Alice),older(100),sha256(...))
Why choose: Ultimate flexibility, can combine different condition types.
โก Optimization Features
n@ - Probability Weights
What it does: Indicates relative probability/frequency of use for optimization.
When to use: When you know which spending path will be used most often.
Benefit: Compiler optimizes for smaller, cheaper transactions on common paths.
Examples:
โข or(99@pk(Alice),pk(Bob)) - Alice spends 99% of the time
โข or(9@pk(Alice),pk(Bob)) - Alice 9x more likely than Bob
โข thresh(2,10@pk(Alice),10@pk(Bob),1@pk(Charlie)) - Charlie rarely participates
How it works: Only relative ratios matter, any scale works (1-10, percentages, weights).
Important Constraints
- Satisfiability: All policies must be satisfiable (no impossible conditions like
and(pk(A),0)
) - Script Limits: Maximum 520 bytes for P2SH, 10,000 bytes for P2WSH, no limit for Taproot
- Time Format: Block heights (small numbers like 144), Unix timestamps (large numbers like 1767225600)
- Key Format: Variable names (Alice), hex pubkeys (02abc...), or descriptors ([fingerprint/path]xpub...)
- Compilation: Policy โ Miniscript โ Bitcoin Script (automatic optimization at each stage)
Reference: https://bitcoin.sipa.be/miniscript/
๐ Miniscript reference
โ๏ธ What is Miniscript?
Miniscript is a structured representation of Bitcoin Script that's designed to be analyzable, composable, and optimizable. While Policy is human-readable, Miniscript is the precise, unambiguous intermediate language that compiles directly to Bitcoin Script. It explicitly defines how Bitcoin opcodes combine, ensuring scripts are secure, efficient, and predictable.
Key features: Precise script control โข Fragment composability โข Size/cost analysis โข Security guarantees โข Optimization opportunities
๐ Key-based Fragments
pk(key) - Simple Public Key Check
What it does: Requires a signature from the specified public key.
Script: <pubkey> CHECKSIG
When to use: Single-user wallets, simple ownership where one person controls funds.
Example: pk(Alice) - Only Alice can spend
Why choose this: Most efficient for single-key spending. Minimal script size (34 bytes for compressed key).
pkh(key) - Public Key Hash
What it does: Requires revealing the public key AND a signature. Key must hash to the given hash160.
Script: DUP HASH160 <hash> EQUALVERIFY CHECKSIG
When to use: Traditional Bitcoin addresses (P2PKH style), when you want shorter scriptPubKeys.
Example: pkh(Alice) - Alice must reveal her key and sign
Why choose this: Shorter scriptPubKey (only 20-byte hash stored), privacy until spending, standard address format.
pk_k(key) - Raw Key on Stack
What it does: Like pk() but expects the key already on stack (doesn't push it).
Script: CHECKSIG
(key must be on stack already)
When to use: Advanced scripts where key comes from elsewhere (e.g., from a hash preimage).
Example: Used internally by compiler for optimizations
Why choose this: Saves bytes when key is already available, used in complex nested structures.
pk_h(key) - Hashed Key Check
What it does: Like pkh() but doesn't duplicate the key (expects it on stack).
Script: HASH160 <hash> EQUALVERIFY CHECKSIG
When to use: Internal optimization when key is already available.
Example: Used in nested constructs by compiler
Why choose this: Saves a DUP operation when key is already on stack.
0 - Always False
What it does: Pushes false (0) onto the stack.
Script: 0
When to use: Creating provably unspendable branches or testing.
Example: or_i(pk(Alice),0) - Only Alice can spend
Why use: Explicit false conditions, often for disabled branches.
1 - Always True
What it does: Pushes true (1) onto the stack.
Script: 1
When to use: Creating always satisfiable conditions or testing.
Example: and_v(1,pk(Alice)) - Effectively just requires Alice
Why use: Placeholder conditions or always-satisfied requirements.
โฐ Time-based Fragments
after(n) - Absolute Timelock
What it does: Can only spend after block height or Unix timestamp n.
Script: <n> CHECKLOCKTIMEVERIFY
When to use: Scheduled payments, vesting, inheritance planning, time-delayed vaults.
Example: and_v(v:pk(Alice),after(1767225600)) - Alice can spend after Jan 1, 2026
Why choose this: Enforces spending cannot happen before specific time. Uses transaction nLockTime field.
older(n) - Relative Timelock
What it does: Must wait n blocks after UTXO creation before spending.
Script: <n> CHECKSEQUENCEVERIFY
When to use: Lightning channels, vault recovery paths, forced hodling, gradual release.
Example: or(pk(Alice),and(pk(Bob),older(144))) - Bob can spend after 1 day
Why choose this: Creates delay relative to when UTXO was created. Perfect for timeout-based recovery.
๐ Hash-based Fragments
sha256(hash) - SHA256 Preimage
What it does: Requires revealing data that hashes to the given SHA256 hash.
Script: SIZE 32 EQUALVERIFY SHA256 <hash> EQUAL
When to use: Atomic swaps, HTLCs, proof of knowledge, cross-chain bridges.
Example: and(pk(Alice),sha256(abc123...)) - Alice + secret
Why choose this: Most common hash function in Bitcoin, 32-byte preimages, widely supported.
hash256(hash) - Double SHA256
What it does: Requires preimage for SHA256(SHA256(data)).
Script: SIZE 32 EQUALVERIFY HASH256 <hash> EQUAL
When to use: Bitcoin-native hashing (used for block headers, txids).
Example: Cross-chain swaps with Bitcoin-style chains
Why choose this: Standard Bitcoin hashing, slightly more secure against length extension.
hash160(hash) - RIPEMD160(SHA256)
What it does: Requires preimage for RIPEMD160(SHA256(data)).
Script: SIZE 32 EQUALVERIFY HASH160 <hash> EQUAL
When to use: Bitcoin address-style hashing, 20-byte hashes for smaller scripts.
Example: and(pk(Alice),hash160(6c60f404...)) - Common in 2FA setups
Why choose this: Smaller hash (20 bytes), used in Bitcoin addresses, good for space optimization.
ripemd160(hash) - RIPEMD160 Hash
What it does: Requires preimage for RIPEMD160(data).
Script: SIZE 32 EQUALVERIFY RIPEMD160 <hash> EQUAL
When to use: Alternative hash function, legacy compatibility.
Example: Rarely used, mainly for specific protocols
Why choose this: Different hash function for diversity, 20-byte output.
๐ OR Combinators - Choosing the Right One
or_i(X,Y) - If/Else Branch Selection
What it does: Spender chooses branch with 1 (left) or 0 (right) on stack.
Script: IF <X> ELSE <Y> ENDIF
When to use: Two distinct spending paths, clean branch selection, vault tiers.
Example: or_i(pk(Alice),pk(Bob)) - Alice OR Bob can spend
Why choose or_i: Most readable, explicit branch choice, good for user-facing options. Spender decides upfront which path to take.
or_d(X,Y) - Try Left First, Fallback to Right
What it does: Tries X first; if X leaves 0, then tries Y.
Script: <X> IFDUP NOTIF <Y> ENDIF
When to use: Primary path with automatic fallback, signature-or-timeout patterns.
Example: or_d(pk(Alice),and(pk(Bob),older(144))) - Alice primary, Bob backup
Why choose or_d: Automatic fallback without explicit choice. Left branch "dissatisfies" gracefully to allow right branch.
or_c(X,Y) - Optimized for Signatures
What it does: Specialized OR for pk/pkh combinations.
Script: <X> NOTIF <Y> ENDIF
When to use: When left side is a signature check that can be "dissatisfied".
Example: Used internally for optimizing signature-based ORs
Why choose or_c: Most efficient for signature-based options. Compiler often chooses this automatically.
or_b(X,Y) - Boolean OR Logic
What it does: Evaluates both branches, combines with boolean OR.
Script: <X> <Y> BOOLOR
When to use: When you need both conditions evaluated (rare).
Example: Complex boolean logic, both sides must execute
Why choose or_b: Both branches always execute. Usually inefficient but sometimes necessary for specific logic.
๐ AND Combinators - Combining Conditions
and_v(X,Y) - Verify Then Execute
What it does: X must be true (verified), then Y is evaluated.
Script: <X> VERIFY <Y>
When to use: Most common AND. Signature + timelock, key + hash, any two conditions.
Example: and_v(v:pk(Alice),older(144)) - Alice after 1 day
Why choose and_v: Clean, efficient, X doesn't leave anything on stack. Use when X is a "check" condition.
and_b(X,Y) - Boolean AND
What it does: Both return boolean values, combined with AND.
Script: <X> <Y> BOOLAND
When to use: When both sides produce boolean (0/1) results.
Example: Combining hash checks or boolean conditions
Why choose and_b: Both sides leave values on stack to be combined. Less common than and_v.
and_n(X,Y) - Conditional AND
What it does: Uses NOTIF pattern for conditional execution.
Script: <X> NOTIF 0 ELSE <Y> ENDIF
When to use: Internal optimization, rarely hand-written.
Example: Compiler-generated for specific patterns
Why choose and_n: Special cases where this pattern is more efficient.
๐ฏ Threshold & Multisig
thresh(k,Xโ,...,Xโ) - K-of-N Threshold
What it does: Exactly k of the n conditions must be satisfied.
Script: Complex combination of ADD and EQUAL checks
When to use: Flexible multisig, mixed conditions (keys + timelocks + hashes).
Example: thresh(2,pk(A),pk(B),pk(C)) - Any 2 of 3 signers
Why choose thresh: Most flexible threshold. Can mix different condition types, not just keys.
multi(k,keyโ,...) - Classic Multisig
What it does: Traditional Bitcoin multisig with CHECKMULTISIG.
Script: <k> <key1> ... <keyn> <n> CHECKMULTISIG
When to use: Legacy compatibility, up to 3-of-3 in P2SH.
Example: multi(2,Alice,Bob,Charlie)
Why choose multi: Most efficient for pure key-based multisig. Limited to 20 keys max.
multi_a(k,keyโ,...) - Taproot Multisig
What it does: Modern multisig using CHECKSIGADD (Taproot only).
Script: Series of CHECKSIGADD
operations
When to use: Taproot scripts, more than 20 keys, batch validation.
Example: multi_a(11,key1,...,key20) - 11-of-20
Why choose multi_a: No key limit, more efficient in Taproot, better for large multisigs.
๐ญ Wrappers - Modifying Behavior
v: - Verify Wrapper
What it does: Adds VERIFY, expression must be true or script fails.
Script: <expr> VERIFY
When to use: Converting expressions for use in and_v, ensuring conditions are met.
Example: and_v(v:pk(Alice),older(144))
Why use: Makes expression "unit type" - doesn't leave value on stack.
a: - Alt Stack Wrapper
What it does: Temporarily stores value on alt stack.
Script: TOALTSTACK ... FROMALTSTACK
When to use: Complex scripts needing temporary storage, threshold operations.
Example: thresh(2,pk(A),a:pk(B),a:pk(C))
Why use: Prevents stack interference in complex evaluations.
s: - Swap Wrapper
What it does: Swaps top two stack elements.
Script: SWAP <expr>
When to use: Reordering stack for specific operations.
Example: Internal compiler optimization
Why use: Stack management in complex scripts.
c: - Checksig Wrapper
What it does: Adds CHECKSIG to a key.
Script: <key> CHECKSIG
When to use: Converting raw keys to signature checks.
Example: or_c(pk(A),v:pk(B))
uses this internally
Why use: Type conversion for certain combinators.
d: - Dup-If Wrapper
What it does: Duplicates if non-zero.
Script: DUP IF ... ENDIF
When to use: or_d constructions, conditional duplication.
Example: Used in or_d
patterns
Why use: Enables dissatisfaction in OR branches.
t: - True/False Wrapper
What it does: Converts to 1 if true, 0 if false.
Script: IF 1 ELSE 0 ENDIF
When to use: Normalizing boolean values.
Example: t:or_i(pk(A),pk(B))
Why use: Ensures consistent 0/1 output.
j: - Size Check Wrapper
What it does: Adds SIZE 0NOTEQUAL check (non-zero size).
Script: SIZE 0NOTEQUAL IF <expr> ENDIF
When to use: Ensuring input has non-zero size before evaluation.
Example: j:pk(Alice) - Requires non-empty signature
Why use: Malleability protection, ensuring witness data present.
n: - Nonzero Wrapper
What it does: Converts any non-zero value to 1.
Script: DUP 0NOTEQUAL
When to use: Normalizing non-zero values to boolean true.
Example: n:older(100) - Ensures boolean output
Why use: Type normalization for certain operations.
๐ Advanced Patterns
andor(X,Y,Z) - Optimized (X AND Y) OR Z
What it does: If X is satisfied, require Y; otherwise require Z.
Script: Optimized combination avoiding redundant checks
When to use: "If condition then require Y, else require Z" patterns.
Example: andor(pk(Alice),pk(Bob),and(pk(Charlie),older(144)))
Why choose andor: More efficient than or(and(X,Y),Z)
for this specific pattern.
๐ก Design Patterns & Best Practices
Common Patterns
- Hot Wallet + Cold Backup:
or(pk(Hot),and(pk(Cold),older(1008)))
- 2FA with Timeout:
and(pk(User),or(pk(Service),older(52560)))
- Escrow:
or(and(pk(Buyer),pk(Seller)),pk(Arbiter))
- HTLC:
or(and(pk(Recipient),sha256(H)),and(pk(Sender),older(144)))
- Inheritance:
or(pk(Owner),and(pk(Heir),after(1798761600)))
- Vault:
or(and(pk(Hot),pk(Cold)),and(pk(Recovery),older(4320)))
Choosing Fragments
- Keys: Use
pk()
for simplicity,pkh()
for privacy/compatibility - Time: Use
after()
for absolute dates,older()
for relative delays - OR Logic: Use
or_i
for clear choices,or_d
for primary/backup - AND Logic: Use
and_v
for most cases, others for optimization - Threshold: Use
thresh
for mixed conditions,multi
for pure multisig - Wrappers: Let compiler add them, use
v:
when needed for and_v
Note: Public keys must be 33-byte compressed hex strings (66 characters) for Legacy and Segwit v0, or 32-byte X-only keys (64 characters) for Taproot. The compiler supports Legacy (p2SH), Segwit v0 (p2WSH), and Taproot contexts.
Reference: https://bitcoin.sipa.be/miniscript/
๐ก Tips & Quick Help
Use the ๐ท๏ธ button to toggle between showing full public keys and variable names (Alice, Bob, etc.) in your expressions. Makes complex policies much easier to read and understand.
If one of the examples fails to complie with "pubkey string should be 66 or 130 digits" (or similar), or ๐ท๏ธ show keys doesn't work properly, go to "Key variables" section and use "Clear all" then "Restore defaults" to reset Alice, Bob, Charlie, etc. You can delete only the problematic key and "Restore defaults".
Use the ๐ button to automatically detect keys in your expression and convert them to reusable variables. Detects public keys, xpubs, tpubs, and x-only keys. For each key, choose the appropriate type (compressed, x-only, xpub, tpub). Suggests meaningful names (Alice, Bob, Charlie) and prevents duplicates.
Use the ๐ button to format long policy, miniscript expressions and scripts with proper indentation. Makes complex nested expressions easier to read and understand. Note: formatting is automatically cleaned during compilation to reflect the actual expression being compiled.
The ๐งน button removes extra whitespace and formatting from pasted expressions that prevent them from compiling successfully.
Save frequently used policies/expressions with the ๐พ Save button. Saved items appear in their respective sections for quick reloading.
Use the ๐ฒ Generate button in Key variables to create random test keys appropriate for your selected script context (xpub, compressed for Legacy/Segwit, X-only for Taproot).
Script context automatically switches based on key types: compressed keys (66 chars) โ Legacy/Segwit, X-only keys (64 chars) โ Taproot.
Paste Bitcoin Script hex/ASM into the "Script HEX" or "Script ASM" fields to automatically lift them back to miniscript. Every lift also shows the policy equivalent. You can also lift miniscript expression to policy.
Use the ๐ button to toggle between mainnet and testnet address generation. Testnet keys (tpub) automatically generate testnet addresses, mainnet keys (xpub) generate mainnet addresses.
All data (keys, saved expressions) persists locally in your browser. No server - everything stays private. Use public keys only!
Use the ๐ button above any expression to generate a shareable URL that includes your policy/miniscript and all key variables. Perfect for collaboration or saving your work. Choose between URL encoding and compact JSON format in Settings below.
When loading an example or a saved expression, it can be automatically compiled to show results immediately. This behavior can be toggled on/off in the Settings section below. When off, expressions load but wait for manual compilation.
โ๏ธ Settings
Choose between dark and light theme. Overrides system preference.
Automatically compile policies and miniscripts when loading from examples or saved expressions. When disabled, expressions load but require manual compilation.
Display helpful descriptions when clicking example buttons. Turn off if you're a miniscript master who doesn't like explanations. ๐งโโ๏ธ
Show helpful tips and instructions in empty editors. Turn off for a cleaner interface with just "Enter your policy/miniscript here..." instead of detailed guidance.
Choose how shared URLs are formatted. URL encoding preserves special characters in readable format and is better for simple expressions. JSON creates base64-encoded URLs that include all state.
Hide the GitHub and beer donation buttons in the top corners for a cleaner interface. Corner buttons are automatically hidden on mobile devices.
Choose how to display the tree structure in compilation results. Script Compilation shows opcodes and detailed structure, Visual Hierarchy shows a clean graphical tree, or hide it completely.