DEV Community

Cover image for Introduction to Miniscript
Oghenovo Usiwoma
Oghenovo Usiwoma

Posted on

Introduction to Miniscript

Miniscript is a representation for Bitcoin scripts that allows composition, analysis, and compilation from spending policies. It has a structure that enables various properties such as correctness, security, and malleability. It translates into Bitcoin Script, the language used to specify spending conditions on the Bitcoin network.

Miniscript offers benefits over the traditional Bitcoin scripting language:

  • It allows developers to create complex Bitcoin transactions without having to write custom scripts from scratch. This can save developers a lot of time and effort, and also reduce the risk of errors in the code.

  • It uses a simpler and more predictable syntax that makes it easier to audit and verify the code. This can help reduce the risk of bugs and vulnerabilities in the code, which can help improve the overall security of Bitcoin transactions.

This article assumes that you have an understanding of the Bitcoin Scripting language, that you understand how it uses the Stack and that you are familiar with the major opcodes.

Policies and Fragments

Miniscript policies are written in the Miniscript Policy language, they specify spending conditions for Bitcoin Transaction Outputs. Miniscript policies are made up of fragments, which are building blocks that can be combined to create more complex policies.
These polices are compiled into Miniscript which is then compiled into Bitcoin Script.

To create a simple pay-to-pubkey script in Miniscript policy language, you can use the pk(key) fragment. This fragment allows you to create an output that can only be spent by the owner of the specified public key (pk). The pk(key) policy compiles into pk(key) in Miniscript which compiles into <key> OP_CHECKSIG in Bitcoin Script. This was a simple scenario where the policy language and miniscript script are the same but as we will see, this is not always the case.

Some of the most commonly used Miniscript fragments include:

  • 0: This fragment resolves to 0 in Bitcoin script.
  • 1: This fragment resolves to 1 in Bitcoin script.
  • pk(key): This fragment allows you to create an output that can only be spent by the owner of the specified public key (pk).
  • pkh(key): This fragment allows you to create an output that can only be spent by the owner of the private key associated with the specified public key hash (pkh).
  • older(n): This fragment allows you to create an output that can only be spent after a certain number of blocks have been mined (n).
  • after(n): This fragment allows you to create an output that can only be spent after a certain date and time (n).
  • hash160(h): This fragment allows you to create an output that can only be spent by providing a signature that hashes to the specified hash value (h).
  • and_b(x, y): This fragment allows you to create an output that can only be spent if both x and y are satisfied.
  • or_b(x, y): This fragment allows you to create an output that can be spent if either x or y is satisfied.
  • thresh(k, pol_1,…, pol_n): This fragment allows you to create an output that can only be spent if k of the n policies are satisfied.
  • multi(k, key_1, …, key_n): This fragment allows you to create an output that can only be spent if k of the n specified public keys (keys) sign the transaction.

These fragments can be combined in various ways to create more complex spending conditions for Bitcoin transactions. See the full list of fragments here

Let's create a policy that allows an output to be spent by key1 or key2. We use the or_b and pk(key) fragments to achieve this:

or_b(pk(key1),pk(key2))

This Miniscript policy compiles to
or_b(pk(key_1),s:pk(key_2))
in Miniscript which is
<key_1> OP_CHECKSIG OP_SWAP <key_2> OP_CHECKSIG OP_BOOLOR
in Bitcoin Script.

This will create an output that can be spent by providing a signature that matches either of the specified public keys (key1 or key2). The signature would be verified against the public key using the OP_CHECKSIG opcode.

Let's take a look at a slightly more complicated policy: thresh(3,pk(key_1),pk(key_2),pk(key_3),older(12960))

It compiles into:
thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sln:older(12960))
in Miniscript.

This policy specifies a spending condition that requires 3 signatures before 90 days but only require 2 signatures subsequently. It is a 3-of-3 multisig that turns into a 2-of-3 multisig after 90 days.

Here’s a breakdown of how this policy works:

  • The “thresh” fragment specifies that at least 3 of the specified conditions must be satisfied to spend the output.
  • The “pk” fragments specify the public keys that can be used to spend the output.
  • The “older” fragment specifies that after 12960 blocks (about 90 days), the script will change to a 2-of-3 multisig.
  • Before 90 days, the older(12960) policy cannot be satisfied, hence, you need the signatures for key_1, key_2 and key_3 to meet the thresh requirement.
  • After 90 days, the older(12960) policy is satisfied and you only need two signatures to meet the thresh requirement.

https://bitcoin.sipa.be/miniscript/ has a Miniscript policy to Miniscript compiler. It also shows the resulting Bitcoin Script.

The Miniscript Type System

Miniscript uses a type system to ensure that the scripts are safe and secure to use. The type system enables static analysis and runtime checks that ensure the scripts are well-formed and that they do not contain any errors or vulnerabilities.

There are four basic types:

  • "B" B-type expressions are "Base Expressions". They push a nonzero value onto the stack when satisfied and exact zero when dissatisfied.
  • "V" V-type expressions are "Verify Expressions". They abort script execution when dissatisfied but do no push any value to the stack when satisfied.
  • "K" K-type expressions are "Key Expressions". They take inputs from the top of the stack, and they push a key onto the stack. A signature is still required to satisfy the expression.
  • "W" W-type expressions are "Wrapped Expressions". They take inputs from one below the top of the stack. They push their result either on top of the stack or one below. In case of satisfaction, they push a nonzero value and a zero value in case of dissatisfaction.

Apart from the basic types, there are also type modifiers: z, o, n, d and u. Each of them guarantees additional properties, for example:

Type Bz describes an expression that pushes a nonzero value to the stack when satisfied and a zero otherwise because it is a Base expression but it also consumes exactly zero elements because it has the "z" modifier.

An expression can be converted from one type to another by applying wrappers. Wrappers look like this v: and they are applied to expressions like this:
v:<Expression>

There are rules that specify what wrappers can be applied to what expression types and what the type of the resulting expression will be. For example;

v: can only be applied to an expression of type "B" and the resulting expression will have a type "V". Observe:

v:older(n) is valid because older(n) is a "B" expression.
older(n) compiles to <n> OP_CHECKSEQUENCEVERIFY but
v:older(n) compiles to <n> OP_CHECKSEQUENCEVERIFY OP_VERIFY

You can also combine wrappers like this:
dv:older(n)
The d: wrapper is applied to the v: wrapper which is applied to the older(n) fragment.

  • The v: wrapper converts older(n) to a "V" type
  • The d: wrapper converts v:older(n) to a "B" type Hence the type of the resulting expression is "B".

This page also has the complete list of wrappers, their requirements, types and properties.

Conclusion

In conclusion, Miniscript is a powerful scripting language that allows for complex Bitcoin transactions to be created with ease. It's simple syntax and modular design make it easy to use and understand, even for those who are new to Bitcoin scripting. Additionally, Miniscript’s flexibility and extensibility make it a great choice for developers who want to create custom Bitcoin transactions that meet their specific needs.

Top comments (0)