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 further). Finally, compatible scripts easily can be converted to miniscript - avoiding the need for additional metadata for e.g. signing devices that support it.
For now, Miniscript is really only designed for P2WSH and P2SH-P2WSH embedded scripts. Most of its constructions works 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.
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. vc:pk(key)
is the v:
wrapper applied to the c:
wrapper applied to the pk
fragment for a given key.
Semantics | Miniscript fragment | Bitcoin Script |
---|---|---|
true | 1 |
OP_1 |
false | 0 |
OP_0 |
check(key) | pk(key) |
<key> |
pk_h(key) |
OP_DUP OP_HASH160 <HASH160(key)> OP_EQUALVERFIFY | |
nSequence ≥ n (and compatible) | older(n) |
<n> OP_CHECKSEQUENCEVERIFY |
nLockTime ≥ n (and compaitlbe) | after(n) |
<n> OP_CHECKLOCKTIMEVERIFY |
len(x) = 32 and SHA256(x) = h | sha256(h) |
OP_SIZE <32> OP_EQUALVERIFY OP_SHA256 <h> OP_EQUAL |
len(x) = 32 and HASH256(x) = h | hash256(h) |
OP_SIZE <32> OP_EQUALVERIFY OP_HASH256 <h> OP_EQUAL |
len(x) = 32 and RIPEMD160(x) = h | ripemd160(h) |
OP_SIZE <32> OP_EQUALVERIFY OP_RIPEMD160 <h> OP_EQUAL |
len(x) = 32 and HASH160(x) = h | hash160(h) |
OP_SIZE <32> OP_EQUALVERIFY OP_HASH160 <h> OP_EQUAL |
(X and Y) or Z | andor(X,Y,Z) |
[X] OP_NOTIF [Z] OP_ELSE [Y] OP_ENDIF |
X and Y | and_v(X,Y) |
[X] [Y] |
and_b(X,Y) |
[X] [Y] OP_BOOLAND | |
and_n(X,Y) = andor(X,Y,0) |
[X] OP_NOTIF OP_0 OP_ELSE [Y] OP_ENDIF | |
X or Z | or_b(X,Z) |
[X] [Z] OP_BOOLOR |
or_d(X,Z) |
[X] OP_IFDUP OP_NOTIF [Z] OP_ENDIF | |
or_c(X,Z) |
[X] OP_NOTIF [Z] OP_ENDIF | |
or_i(X,Z) |
OP_IF [X] OP_ELSE [Z] OP_ENDIF | |
X1 + ... + Xn = k | thresh(k,X1,...,Xn) |
[X1] [X2] OP_ADD ... [Xn] OP_ADD ... <k> OP_EQUAL |
check(key1) + ... + check(keyn) = k | thresh_m(k,key1,...,keyn) |
<k> <key1> ... <keyn> <n> OP_CHECKMULTISIG |
X (identities) | a:X |
OP_TOALTSTACK [X] OP_FROMALTSTACK |
s:X |
OP_SWAP [X] | |
c:X |
[X] OP_CHECKSIG | |
t:X = and_v(X,1) |
[X] OP_1 | |
d:X |
OP_DUP OP_IF [X] OP_ENDIF | |
v:X |
[X] OP_VERIFY (or VERIFY version of last opcode in [X]) | |
j:X |
OP_SIZE OP_0NOTEQUAL OP_IF [X] OP_ENDIF | |
n:X |
[X] OP_0NOTEQUAL | |
l:X = or_i(0,X) |
OP_IF OP_0 OP_ELSE [X] OP_ENDIF | |
u:X = or_i(X,0) |
OP_IF [X] OP_ELSE 0 OP_ENDIF |
and_n
fragment 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.
Not every Miniscript expression can be composed with every other. Some return their result by putting zero or false on the stack; others distinguish by aborting or continuing. 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> OP_CHECKSEQUENCEVERIFY.v:
wrapper on a "B" expression, or by combining other "V" expressions using and_v
, or_i
, or_c
, or andor
.vc:pk(key)
= <key> OP_CHECKSIGVERIFY.c:
wrapper (OP_CHECKSIG).pk_h(key)
= OP_DUP OP_HASH160 <Hash160(key)> OP_EQUALVERIFYs:B
(OP_SWAP B) or a:B
(OP_TOALTSTACK B OP_FROMALTSTACK).sc:pk(key)
= OP_SWAP <key> OP_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(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 | |
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=zXoY or zYoX; 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 |
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 |
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 |
thresh_m(k,key1,...,keyn) | 1 ≤ k ≤ n | B | n; 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=zX; n; u; d |
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 |
Various types of Bitcoin Scripts have different resource limitations, either through consensus or standardness. Some of them affect otherwise valid Miniscripts:
thresh_m
s, is above 201, are invalid by consensus (bare, P2SH, P2WSH, P2SH-P2WSH).c:pk(key)
(P2PK), c:pk_h(key)
(P2PKH), and thresh_m(k,...)
up to n=3 is invalid by standardness (bare).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.
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 how to construct valid satisfactions and dissatisfactions for every Miniscript, using satisfactions and dissatisfactions of its subexpressions.
Miniscript | Dissatisfaction (dsat) | Satisfaction (sat) | |
---|---|---|---|
0 | - | ||
1 | - | ||
pk(key) | 0 | sig | |
pk_h(key) | 0 | sig key | |
older(n) | - | ||
after(n) | - | ||
sha256(h) | any 32-byte vector | preimage | |
ripemd160(h) | any 32-byte vector | preimage | |
hash256(h) | any 32-byte vector | preimage | |
hash160(h) | any 32-byte vector | preimage | |
thresh_m(k,key1,...,keyn) | 0 0 ... 0 (n+1 times) | 0 sig ... sig | |
a:X | dsat(X) | sat(X) | |
s:X | dsat(X) | sat(X) | |
c:X | dsat(X) | sat(X) | |
n:X | dsat(X) | sat(X) | |
v:X | - | sat(X) | |
d:X | 0 | sat(X) 1 | |
j:X | 0 | sat(X) | |
and_v(X,Y) | - | sat(Y) sat(X) | |
and_b(X,Y) | dsat(Y) dsat(X) | sat(Y) sat(X) | |
thresh(k,X1,...,Xn) | dsat(Xn) ... dsat(X1) | [d]sat(Xn) ... [d]sat(X1) (with k sats and n-k dsats) | |
Left | Right | ||
or_b(X,Z) | dsat(Z) dsat(X) | dsat(Z) sat(X) | sat(Z) dsat(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 or dsat(Z) 0 | sat(X) 1 | sat(Z) 0 |
andor(X,Y,Z) | dsat(Z) dsat(X) | sat(Y) sat(X) | sat(Z) dsat(X) |
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.
Because of these reasons, Miniscript is actually designed to permit non-malleable signing. As this guarantee is restricted in nob-obvious ways, let's first state the assumptions:
Several ways to introduce malleability under the assumptions above exist:
sha256(h)
and other hash locks are always malleable, as any 32-byte input suffices. An attacker can modify any such dissatisfaction into another one. Because of this, inside a non-malleable satisfaction, a hash lock can never be directly dissatisfied. Instead if must be protected by an OP_IF (e.g. u:
) that skip execution of the hash lock check entirely when its satsifaction is not required.and_b
, andor
, and thresh
) a dissatisfaction could be constructed that involves subexpressions that are actually satisfied. To prevent this, we must require that satisfying those subexpressions requires a digital signature. In that case, a malleability attacker cannot construct a satisfaction as we assume he has no access to private keys.or_i
two possible ways for dissatisfying the expression exist: one involving the left subexpression and one involving the right one. To prevent the dissatisfaction from being malleable, we must make sure that at most one of the two is dissatisfiable.
or_*
, andor
, and thresh
) multiple ways of satisfaction exist. We must prevent malleability attackers from changing a satsifaction from one branch to another. Whenever a satisfaction exists for an expression that does not involve any signatures, we must take it. Otherwise the attacker could change the satisfaction to the non-signature requiring one. Whenever multiple ways exist that all require no signature, the result is inevitably malleable.Type | Shorthand | Behavior under honest inputs | Behavior under adverserial inputs |
---|---|---|---|
Base | B | Takes its inputs from the top of the stack. Pushes a nonzero value of up to 4 bytes if the condition is satisfied, exactly zero otherwise. | Pushes zero onto the stack, or aborts. |
Key | K | Takes its inputs from the top of the stack. Pushes a public key with which a checksig is to be done onto the stack. | Aborts. |
Verify | V | Takes its inputs from the top of the stack, which must satisfy the condition. Does not push anything onto the stack. | Aborts. |
Wrapped | W | Takes from the stack its inputs + element X at the top. If the inputs satisfy the condition, [t X] or [X t] is pushed, where t is a nonzero value of up to 4 bytes. If not, [0 X] or [X 0] is pushed. | Pushes [0 X] or [X 0] onto the stack, or aborts. |
Semantics | Node | Script | nsat | sat (X,Y) | sat Z | Types | Req. | Corr. prop. | Mall. prop. |
---|---|---|---|---|---|---|---|---|---|
check(key) | pk(key) | key | 0 | sig | - | K | o, n, u, d | e, m, s | |
pk_h(keyhash) | DUP HASH160 keyhash EQUALVERFIFY | 0 key | sig key | - | K | n, u, d | e, m, s | ||
nSequence ≥ n (and compatible) | older(n) | n CHECKSEQUENCEVERIFY | - | [] | - | B | n ≥ 1 | z | f, m |
nLockTime ≥ n (and compaitlbe) | after(n) | n CHECKLOCKTIMEVERIFY | - | [] | - | B | 231 > n ≥ 1 | z | f, m |
len(x) = 32 and SHA256(x) = h | sha256(h) | SIZE 32 EQUALVERIFY SHA256 h EQUAL | 000... | x | - | B | o, n, u, d | m | |
len(x) = 32 and HASH256(x) = h | hash256(h) | SIZE 32 EQUALVERIFY HASH256 h EQUAL | 000... | x | - | B | o, n, u, d | m | |
len(x) = 32 and RIPEMD160(x) = h | ripemd160(h) | SIZE 32 EQUALVERIFY RIPEMD160 h EQUAL | 000... | x | - | B | o, n, u, d | m | |
len(x) = 32 and HASH160(x) = h | hash160(h) | SIZE 32 EQUALVERIFY HASH160 h EQUAL | 000... | x | - | B | o, n, u, d | m | |
X and Y | and_v(X,Y) | [X] [Y] | - | satY satX | - | B=VXBY K=VXKY V=VXVY |
u=uY n=nX+zXnY z=zXzY o=zXoY+oXzY |
f=fXfY[=fY] m=mXmY s=sX+sY |
|
and_b(X,Y) | [X] [Y] BOOLAND | nsatY nsatX | satY satX | - | B=BXWY |
z=zXzY o=zXoY+oXzY n=nX+zXnY d=dXdY u |
f=fXfY e=eXeYsXsY m=mXmY s=sX+sY |
||
and_n(X,Y) | [X] NOTIF 0 ELSE [Y] ENDIF (=andor(X,Y,0)) | nsatX | satY satX | - | B=BXBY | dXuX |
z=zXzY o=oXzY u=uY d=dX[=1] |
f=fYfX[=0] e=eX(sX+fY) m=mXmYeX s=sX+sY |
|
X or Z | or_b(X,Z) | [X] [Z] BOOLOR | nsatZ nsatX | nsatZ satX | satZ nsatX | B=BXWZ | dXdZ |
z=zXzZ o=zXoZ+oXzZ d=dXdZ[=1] u |
f=fX+fZ[=0] e=eXeZ m=mXmZeXeZ(sX+sZ) s=sXsZ |
or_d(X,Z) | [X] IFDUP NOTIF [Z] ENDIF | nsatZ nsatX | satX | satZ nsatX | B=BXBZ | dXuX |
z=zXzZ o=oXzZ u=uX(fX+uZ)[=uZ] d=dXdZ[=dZ] |
f=fX+fZ[=fZ] e=eXeZ m=mXmZeX(sX+sZ) s=sXsZ |
|
or_c(X,Z) | [X] NOTIF [Z] ENDIF | - | satX | satZ nsatX | V=BXVZ | dXuX |
z=zXzZ o=oXzZ |
f=fX+fZ[=1] m=mXmZeX(sX+sZ) s=sXsZ |
|
or_i(X,Z) | IF [X] ELSE [Z] ENDIF | nsatX 1 nsatZ 0 |
satX 1 | satZ 0 | V=VXVZ B=BXBZ K=KXKZ |
o=zXzZ u=uXuZ d=dX+dZ |
f=fXfZ e=eXfZ+eZfX m=mXmZ(sX+sZ) s=sXsZ |
||
(X and Y) or Z | andor(X,Y,Z) | [X] NOTIF [Z] ELSE [Y] ENDIF | nsatZ nsatX | satY satX | satZ nsatX | B=BXBYBZ K=BXKYKZ V=BXVYVZ |
dXuX |
z=zXzYzZ o=zXoYoZ+oXzYzZ u=uYuZ d=dXdZ[=dZ] |
f=fY(fX+fZ)[=fYfZ] e=eXeZ(sX+fY) m=mXmYmZeX(sX+sY+sZ) s=sZ(sX+sY) |
X1 + ... + Xn = k | thresh(k,...) | [X1] ([Xi ADD)*(n-1) k EQUAL | nsat... | (sat|nsat)... | - | B=X1 is B others are W |
n > k > 1 all are d and u |
z=all are z o=all are z, except one is o d u |
e=all are e and s m=all are e and m, ≥(n-k) are s s=≥(n-k+1) are s |
check(key_1) + ... = k | thresh_m(k,...) | k key_1 ... key_n n CHECKMULTISIG | 0 0 ... 0 | 0 sig... | - | B | n ≥ k ≥ 1 | n, u, d | e, m, s |
X | a:X | TOALTSTACK [X] FROMALTSTACK | nsatX | satX | - | W=BX | u=uX, d=dX | f=fX, e=eX, m=mX, s=sX | |
s:X | SWAP [X] | nsatX | satX | oX | W=BX | u=ux, d=dX | f=fX, e=eX, m=mX, s=sX | ||
c:X | [X] CHECKSIG | nsatX | satX | - | B=KX | o=oX, n=nX, u, d=dX | e=eX, m=mX, s=sX[=1] | ||
t:X | [X] 1 (=and_v(X,1)) | - | satX | - | B=VX | u, n=nX, z=zX, o=oX | f, m=mX, s=sX | ||
d:X | DUP IF [X] ENDIF | 0 | satX 1 | - | B=VX | zX | o=zX, n, u, d | e=fX, m=mX, s=sX | |
v:X | [X] VERIFY | - | satX | - | V=BX | z=zX, o=oX, n=nX | f, m=mX, s=sX | ||
j:X | SIZE 0NOTEQUAL IF [X] ENDIF | 0 | satX | - | B=BX | nX | o=oX, n, u=uX, d | e=fX, m=mX, s=sX | |
n:X | [X] 0NOTEQUAL | nsatX | satX | - | B=BX | z=zX, o=oX, n=nX, u, d=dX | f=fX, e=eX, m=mX, s=sX | ||
l:X | IF 0 ELSE [X] ENDIF (=or_i(0,X)) | 1 | satX 0 | - | B=BX | o=zX, u=uX, d | e=fX, m=mX, s=sX | ||
u:X | IF [X] ELSE 0 ENDIF (=or_i(X,0)) | 0 | satX 1 | - | B=BX | o=zX, u=uX, d | e=fX, m=mX, s=sX | ||
true | 1 | 1 | - | [] | - | B | z, u | f, m | |
false | 0 | 0 | [] | - | - | B | z, u, d | e, m, s |