Miniscript is a language for writing (a subset of) Bitcoin Scripts in a structured way, enabling analysis, composition, generic signing and more.
Bitcoin Script is an unusual stack-based language with many edge cases, designed for implementing spending conditions consisting of various combinations of signatures, hash locks, and time locks. Yet despite being limited in functionality it is still highly nontrivial to:
Miniscript functions as a representation for scripts that makes these sort of operations possible. It has a structure that allows composition. It is very easy to statically analyze for various properties (spending conditions, correctness, security properties, malleability, ...). It can be targeted by spending policy compilers (see below). Finally, compatible scripts can easily be converted to Miniscript form - avoiding the need for additional metadata for e.g. signing devices that support it.
Miniscript is designed for (P2SH-)P2WSH and Tapscript (as defined in BIP342) embedded scripts. Most of its constructions work fine in P2SH as well, but some of the (optional) security properties rely on Segwit-specific rules. Furthermore, the implemented policy compilers assume a Segwit-specific cost model.
Miniscript was designed and implemented by Pieter Wuille, Andrew Poelstra, and Sanket Kanjalkar at Blockstream Research, but is the result of discussions with several other people.
Links:
Here you can see a demonstration of the P2WSH Miniscript compiler. Write a spending policy according to the instructions below, and observe how it affects the constructed Miniscript.
Here you can analyze the structure of a Miniscript expression and more.
The Miniscript specifications vary only slightly between P2WSH and Tapscript embedded scripts. Differences are pointed inline. If no information specific to the context is given you can assume it is valid for both P2WSH and Tapscript.
This table shows all Miniscript fragments and their associated semantics and Bitcoin Script.
Fragments that do not change the semantics of their subexpressions are called wrappers. Normal fragments use
a "fragment(arguments,...)" notation, while wrappers are written using prefixes separated from other fragments
by a colon. The colon is dropped between subsequent wrappers; e.g. dv:older(144)
is the d:
wrapper applied to the v:
wrapper applied to the older
fragment for 144 blocks. Note how
key expressions in this table and the followings are implicitly 32 bytes "x-only" public key in Tapscript
and 33 bytes compressed public keys in P2WSH.
Semantics | Miniscript fragment | Bitcoin Script |
---|---|---|
false | 0 |
0 |
true | 1 |
1 |
check(key) | pk_k(key) |
<key> |
pk_h(key) |
DUP HASH160 <HASH160(key)> EQUALVERIFY | |
pk(key) = c:pk_k(key) |
<key> CHECKSIG | |
pkh(key) = c:pk_h(key) |
DUP HASH160 <HASH160(key)> EQUALVERIFY CHECKSIG | |
nSequence ≥ n (and compatible) | older(n) |
<n> CHECKSEQUENCEVERIFY |
nLockTime ≥ n (and compatible) | after(n) |
<n> CHECKLOCKTIMEVERIFY |
len(x) = 32 and SHA256(x) = h | sha256(h) |
SIZE <20> EQUALVERIFY SHA256 <h> EQUAL |
len(x) = 32 and HASH256(x) = h | hash256(h) |
SIZE <20> EQUALVERIFY HASH256 <h> EQUAL |
len(x) = 32 and RIPEMD160(x) = h | ripemd160(h) |
SIZE <20> EQUALVERIFY RIPEMD160 <h> EQUAL |
len(x) = 32 and HASH160(x) = h | hash160(h) |
SIZE <20> EQUALVERIFY HASH160 <h> EQUAL |
(X and Y) or Z | andor(X,Y,Z) |
[X] NOTIF [Z] ELSE [Y] ENDIF |
X and Y | and_v(X,Y) |
[X] [Y] |
and_b(X,Y) |
[X] [Y] BOOLAND | |
and_n(X,Y) = andor(X,Y,0) |
[X] NOTIF 0 ELSE [Y] ENDIF | |
X or Z | or_b(X,Z) |
[X] [Z] BOOLOR |
or_c(X,Z) |
[X] NOTIF [Z] ENDIF | |
or_d(X,Z) |
[X] IFDUP NOTIF [Z] ENDIF | |
or_i(X,Z) |
IF [X] ELSE [Z] ENDIF | |
X1 + ... + Xn = k | thresh(k,X1,...,Xn) |
[X1] [X2] ADD ... [Xn] ADD ... <k> EQUAL |
check(key1) + ... + check(keyn) = k (P2WSH only) | multi(k,key1,...,keyn) |
<k> <key1> ... <keyn> <n> CHECKMULTISIG |
check(key1) + ... + check(keyn) = k (Tapscript only) | multi_a(k,key1,...,keyn) |
<key1> CHECKSIG <key2> CHECKSIGADD ... <keyn> CHECKSIGADD <k> NUMEQUAL |
X (identities) | a:X |
TOALTSTACK [X] FROMALTSTACK |
s:X |
SWAP [X] | |
c:X |
[X] CHECKSIG | |
t:X = and_v(X,1) |
[X] 1 | |
d:X |
DUP IF [X] ENDIF | |
v:X |
[X] VERIFY (or VERIFY version of last opcode in [X]) | |
j:X |
SIZE 0NOTEQUAL IF [X] ENDIF | |
n:X |
[X] 0NOTEQUAL | |
l:X = or_i(0,X) |
IF 0 ELSE [X] ENDIF | |
u:X = or_i(X,0) |
IF [X] ELSE 0 ENDIF |
pk
, pkh
, and and_n
fragments and t:
, l:
, and u:
wrappers are syntactic sugar for other Miniscripts, as listed in the table above.
In what follows, they will not be included anymore, as their properties can be derived by looking at their expansion.
Hash preimages are constrained to 32 bytes to disallow various forms of griefing, including making non-standard(un-relayable) transactions,
consensus-invalid swaps across blockchains, as well as ensure that satisfaction cost can be accurately calculated. Finally, note that <20>
are in hex representation in this document.
Not every Miniscript expression can be composed with every other. Some return their result by putting true or false on the stack; others can only abort or continue. Some require subexpressions that consume an exactly known number of arguments, while others need a subexpression that has a nonzero top stack element to satisfy. To model all these properties, we define a correctness type system for Miniscript.
Every miniscript expression has one of four basic types:
older(n)
= <n> CHECKSEQUENCEVERIFY.v:
wrapper on a "B" expression, or by combining other "V" expressions using and_v
, or_i
, or_c
, or andor
.
An example is v:pk(key)
= <key> CHECKSIGVERIFY.c:
wrapper (CHECKSIG).
An example is pk_h(key)
= DUP HASH160 <Hash160(key)> EQUALVERIFYs:B
(SWAP B) or a:B
(TOALTSTACK B FROMALTSTACK).
An example is s:pk(key)
= SWAP <key> CHECKSIG.Then there are 5 type modifiers, which guarantee additional properties:
This tables lists the correctness requirements for each of the Miniscript expressions, and its type properties in function of those of their subexpressions:
Miniscript | Requires | Type | Properties |
---|---|---|---|
0 | B | z; u; d | |
1 | B | z; u | |
pk_k(key) | K | o; n; d; u | |
pk_h(key) | K | n; d; u | |
older(n) , after(n) | 1 ≤ n < 231 | B | z |
sha256(h) | B | o; n; d; u | |
ripemd160(h) | B | o; n; d; u | |
hash256(h) | B | o; n; d; u | |
hash160(h) | B | o; n; d; u | |
andor(X,Y,Z) | X is Bdu; Y and Z are both B, K, or V | same as Y/Z | z=zXzYzZ; o=zXoYoZ or oXzYzZ; u=uYuZ; d=dZ |
and_v(X,Y) | X is V; Y is B, K, or V | same as Y | z=zXzY; o=zXoY or zYoX; n=nX or zXnY; u=uY |
and_b(X,Y) | X is B; Y is W | B | z=zXzY; o=zXoY or zYoX; n=nX or zXnY; d=dXdY; u |
or_b(X,Z) | X is Bd; Z is Wd | B | z=zXzZ; o=zXoZ or zZoX; d; u |
or_c(X,Z) | X is Bdu; Z is V | V | z=zXzZ; o=oXzZ |
or_d(X,Z) | X is Bdu; Z is B | B | z=zXzZ; o=oXzZ; d=dZ; u=uZ |
or_i(X,Z) | both are B, K, or V | same as X/Z | o=zXzZ; u=uXuZ; d=dX or dZ |
thresh(k,X1,...,Xn) | 1 ≤ k ≤ n; X1 is Bdu; others are Wdu | B | z=all are z; o=all are z except one is o; d; u |
multi(k,key1,...,keyn) | 1 ≤ k ≤ n | B | n; d; u |
multi_a(k,key1,...,keyn) | 1 ≤ k ≤ n | B | d; u |
a:X | X is B | W | d=dX; u=uX |
s:X | X is Bo | W | d=dX; u=uX |
c:X | X is K | B | o=oX; n=nX; d=dX; u |
d:X | X is Vz | B | o; n; d; (Tapscript only) u |
v:X | X is B | V | z=zX; o=oX; n=nX |
j:X | X is Bn | B | o=oX; n; d; u=uX |
n:X | X is B | B | z=zX; o=oX; n=nX; d=dX; u |
The nSequence field in a transaction input, or the nLockTime field in transaction can be specified either as a time or height but not both. Therefore it is not possible to spend scripts that require satisfaction of both, height based timelock and time based timelock of the same type.
Various types of Bitcoin Scripts have different resource limitations, either through consensus or standardness. Some of them affect otherwise valid Miniscripts:
multi
s, is above 201, are invalid by consensus (bare, P2SH, P2WSH, P2SH-P2WSH).pk(key)
(P2PK), pkh(key)
(P2PKH), and multi(k,...)
up to n=3 is invalid by standardness (bare).or_b(X,Y)
where both X and Y require a number of large multi
s
to be executed to satisfy. It may be the case that satisfying just one of X or Y does not exceed the ops limit, while satisfying both does. As it's never required to
satisfy both, the limit does not prevent satisfaction.
The type system above guarantees that the corresponding Bitcoin Scripts are:
The completeness property has been extensively tested for P2WSH by verifying large numbers of random satisfactions for random Miniscript expressions against Bitcoin Core's consensus and standardness implementation. The soundness can be reasoned about by considering all possible execution paths through each of the fragments' scripts.
In order for these properties to not just apply to script, but to an entire transaction, it's important that the witness commits to all data relevant for verification. In practice
this means that scripts whose conditions can be met without any digital signature are insecure. For example, if an output can be spent by simply passing a certain nLockTime
(an after(n)
fragment in Miniscript) but without any digital signatures, an attacker can modify the nLockTime field in the spending transaction.
The following table shows all valid satisfactions and dissatisfactions for every Miniscript, using satisfactions and dissatisfactions of its subexpressions. Multiple possibilities are separated by semicolons. Some options are not actually necessary to produce correct witnesses, and are called non-canonical options. They are listed for completeness, but marked in [grey] below.
Miniscript | Dissatisfactions (dsat) | Satisfactions (sat) |
---|---|---|
0 | - | |
1 | - | |
pk_k(key) | 0 | sig |
pk_h(key) | 0 key | sig key |
older(n) | - | |
after(n) | - | |
sha256(h) | any 32-byte vector except the preimage | preimage |
ripemd160(h) | any 32-byte vector except the preimage | preimage |
hash256(h) | any 32-byte vector except the preimage | preimage |
hash160(h) | any 32-byte vector except the preimage | preimage |
andor(X,Y,Z) | dsat(Z) dsat(X); [dsat(Y) sat(X)] | sat(Y) sat(X); sat(Z) dsat(X) |
and_v(X,Y) | [dsat(Y) sat(X)] | sat(Y) sat(X) |
and_b(X,Y) | dsat(Y) dsat(X); [sat(Y) dsat(X)]; [dsat(Y) sat(X)] | sat(Y) sat(X) |
or_b(X,Z) | dsat(Z) dsat(X) | dsat(Z) sat(X); sat(Z) dsat(X); [sat(Z) sat(X)] |
or_c(X,Z) | - | sat(X); sat(Z) dsat(X) |
or_d(X,Z) | dsat(Z) dsat(X) | sat(X); sat(Z) dsat(X) |
or_i(X,Z) | dsat(X) 1; dsat(Z) 0 | sat(X) 1; sat(Z) 0 |
thresh(k,X1,...,Xn) | All dsats [Sats/dsats with 1 ≤ #(sats) ≠ k] | Sats/dsats with #(sats) = k |
multi(k,key1,...,keyn) | 0 0 ... 0 (k+1 times) | 0 sig ... sig |
multi_a(k,key1,...,keyn) | 0 ... 0 (n times) | sig/0 with #(sig) = k and #(sigs/0) = n |
a:X | dsat(X) | sat(X) |
s:X | dsat(X) | sat(X) |
c:X | dsat(X) | sat(X) |
d:X | 0 | sat(X) 1 |
v:X | - | sat(X) |
j:X | 0; [dsat(X) (if nonzero top stack)] | sat(X) |
n:X | dsat(X) | sat(X) |
The correctness properties in the previous section are based on the availability of satisfactions and dissatisfactions listed above. The requirements include a "d" for every subexpression whose dissatisfaction may be needed in building a satisfaction for the parent expression. The "d" properties themselves rely on the "d" properties of subexpressions. An interesting property is that in a well-typed Miniscript, dissatisfying a non-"d" subexpression always causes the overall script to fail, propagating the failure upwards to a point where it either causes the script to abort, or reach the top level.
A conservative simplification is made here, ignoring the existence of always-true expressions like 1
. This makes the typing rules incorrect in the following cases:
or_b(X,1)
and or_b(1,X)
are complete when X is dissatisfiable ("d"). In that case, the condition is equivalent to 1 and is always met, and a satisfaction of the form "dsat(X)" exists.
or_b(1,1)
is complete, as it is equivalent to 1 and "" is a valid satisfaction.and_b(X,1)
and and_b(1,X)
are dissatisfiable ("d") when X is.
andor(1,1,Z)
is complete, as it is equivalent to 1 and "" is a valid satisfaction.andor(1,Y,0)
is complete when Y is; it also has the same type as Y, despite 0
always being a "B". This is also dissatisfiable when Y is.
1
s items listed above can be replaced with any other always-true expression.
and_v(X,1)
code), the typing rules match the definitions above.
While following the table above to construct satisfactions is sufficient for meeting the completeness and soundness properties, it does not guarantee non-malleability.
Malleability is the ability for a third party (not a participant in the script) to modify an existing satisfaction into another valid satisfaction. Since Segwit, malleating a transaction no longer breaks the validity of unconfirmed descendant transactions. However, unintentional malleability may still have a number of much weaker undesirable effects. If a witness can be stuffed with additional data, the transaction's feerate will go down, potentially to the point where its ability to propagate and get confirmed is impacted. Additionally, malleability can be exploited to add roundtrips to BIP152 block propagation, by trying to get different miners to mine different versions of the same transaction. Finally, malleability may interfere with the usage of hash locks as a mechanism for publishing preimages.
Because of these reasons, Miniscript is actually designed to permit non-malleable signing. As this guarantee is restricted in non-obvious ways, let's first state the assumptions:
Malleable satisfactions or dissatisfactions appear whenever options are available to attackers distinct from the one taken by honest users. This can happen for multiple reasons:
Because we assume attackers never have access to private keys, we can treat any solution that includes a signature as one that is unavailable to attackers. For others, the worst case is that the attacker has access to every solution the honest users have, but no others: for preimages this is an explicit assumption, while timelock availability is determined by the nLockTime and nSequence fields in the transaction. As long as the overall satisfaction includes at least one signature, those values are fixed, and timelock availability is identical for attackers and honest users.
In order to produce non-malleable satisfactions we make use of a function that returns the optimal satisfaction and dissatisfaction for a given expression (if any exist), or a special DONTUSE value, together with an optional HASSIG marker that tracks whether the solution contains at least one signature. To implement the function:
sha256
, ripemd160
, hash256
, and hash160
are always malleable (reason 1), so instead use DONTUSE there.and_b
, or_b
, and thresh
are always overcomplete (reason 3), so instead use DONTUSE there as well (with HASSIG flag if the original non-canonical solution had one).
pk_k
, pk_h
, and multi
can be marked HASSIG.The above can be used to show that no non-canonical solutions listed in the satisfaction table can occur inside non-malleable satisfactions.
or_b
, and_b
, and thresh
ones) are overcomplete, and thus can clearly not appear in non-malleable satisfactions.and_v
dissatisfaction.j:X
is always "d", its non-HASSIG dissatisfaction "0" always exists, and thus rules out any usage of "dsat(X)".
andor(X,Y,Z)
is "d", a non-HASSIG dissatisfaction "dsat(Z) dsat(X)" must exist, and thus rules out any usage of "dsat(Y) sat(X)".and_b(X,Y)
is "d", a non-HASSIG dissatisfaction "dsat(Y) dsat(X)" must exist, and thus rules out any usage of "dsat(Y) sat(X)" and "sat(Y) dsat(X)". Those are also overcomplete. thresh(k,...)
is always "d", a non-HASSIG dissatisfaction with just dissatisfactions must exist due to typing rules, and thus rules out usage of the other dissatisfactions. They are also overcomplete.Now we have an algorithm that can find the optimal non-malleable satisfaction for any Miniscript, if it exists. But can we statically prove that such a solution always exists? In order to do that, we add additional properties to the type system:
The following table lists these additional properties, and the requirements for non-malleability.
Miniscript | Requires | Properties |
---|---|---|
0 | s, e | |
1 | f | |
pk_k(key) | s, e | |
pk_h(key) | s, e | |
older(n) | f | |
after(n) | f | |
sha256(h) | ||
ripemd160(h) | ||
hash256(h) | ||
hash160(h) | ||
andor(X,Y,Z) | eX and (sX or sY or sZ) | s=sZ and (sX or sY); f=fZ and (sX or fY); e=eZ and (sX or fY) |
and_v(X,Y) | s=sX or sY; f=sX or fY | |
and_b(X,Y) | s=sX or sY; f=fXfY or sXfX or sYfY; e=eXeYsXsY | |
or_b(X,Z) | eXeZ and (sX or sZ) | s=sXsZ; e |
or_c(X,Z) | eX and (sX or sZ) | s=sXsZ; f |
or_d(X,Z) | eX and (sX or sZ) | s=sXsZ; f=fZ; e=eZ |
or_i(X,Z) | sX or sZ | s=sXsZ; f=fXfZ; e=eXfZ or eZfX |
thresh(k,X1,...,Xn) | all are e; at most k are non-s | s=at most k-1 are non-s; e=all are s |
multi(k,key1,...,keyn) | s; e | |
multi_a(k,key1,...,keyn) | s; e | |
a:X | s=sX; f=fX; e=eX | |
s:X | s=sX; f=fX; e=eX | |
c:X | s; f=fX; e=eX | |
d:X | s=sX; e | |
v:X | s=sX; f | |
j:X | s=sX; e=fX | |
n:X | s=sX; f=fX; e=eX |
Using the malleability type properties it is possible to determine statically whether a script can be nonmalleably satisfied under all circumstances. In many cases it is reasonable to only accept such guaranteed-nonmalleable scripts, as unexpected behavior can occur when using other scripts.
For example, when running the non-malleable satisfaction algorithm above, adding available preimages, or increasing the nLockTime/nSequence values actually may make it fail where it succeeded before. This is because a larger set of met conditions may mean an existing satisfaction goes from nonmalleable to malleable. Restricting things to scripts that are guaranteed to be satisfiable in a non-malleable way avoids this problem.
When analysing Miniscripts for resource limits, restricting yourself to just non-malleable solutions (or even non-malleable scripts) also leads to tighter bounds, as all non-canonical satisfactions and dissatisfactions can be left out of consideration.