The BitHyve team gets very excited especially when it comes to technical or nerdy things relating to Bitcoin. So Miniscript had our attention from the get go. When we looked at it carefully we realised that it actually aligns with what BitHyve is set out to achieve i.e. ‘Simplifying Sound Money’. Let me explain. One of the barriers to entry in the Bitcoin space, even for developers, is it’s scripting language. Compared to say Ethereum, it is not as easy as Solidity which is akin to modern programming languages. Miniscript is a step in the right direction (along with Simplicity and Tapscript) when it comes to *Simplifying* this aspect of Sound Money. Miniscript encodes to a subset of Bitcoin Script and makes programming on Bitcoin script way simpler. So we decided to take a deep dive which is the goal of this article.
At the recent Boston BitDevs meetup, multiple things were discussed — Utreexo, Minisketch, Miniscript, etc. This tweetstorm by Rhys Lindmark lists everything that happened. We found Miniscript fascinating since Andrew Poelstra explained in detail regarding its capabilities, and it seemed to be ready for use right away without requiring any external changes.
As an example, imagine you want a 2/2 multisig
1 pubkey_1 pubkey_2 OP_CHECKMULTISIG. The standard way would be to get a pen and write down script yourself. However, as the size and complexity of the script go larger, it is exponentially more difficult to roll these scripts by hand (not to mention verification). This is where Miniscript comes in. It takes a standard expression syntax with commands supporting a subset of Bitcoin Script and encodes it to Bitcoin Script. For the above example, the Miniscript version would be
multi(1,?,?) which then encodes to
1 [pk] [pk] 2 CHECKMULTISIG. In order to understand this better, let us construct a few decision trees (trees which have different decisions as nodes) and see how Miniscript encodes them to Bitcoin Script:
- Simple decision tree: The following is a simple decision tree which we would like to represent in Bitcoin Script. It requires a pubkey signature AND a pubkey signature or a timeout of 1000 blocks.
The above decision tree requires both branches (a pubkey signature OR a timeout of 1000 blocks AND a pubkey signature) to be satisfied.
Before we dive in, two things going on under the hood when you use Miniscript: The first is something called a “policy language”: We can consider this decision tree to be a “policy language” describing the conditions under which coins can be spent. We can represent it in text form as
The Miniscript (which the policy language compiles to) for the above policy which looks like
and_v(vc:pk(?),or_i(c:pk(?),after(1000))). For the most part, Miniscript and the policy language look the same (the Miniscript is the uglier brother of policy language) but Miniscript is what gets encoded to bitcoin script and the ugly parts (in Miniscript compared to the policy language) help the encoder generate Bitcoin Script. For example,
vc:pk()above encodes to
c:pk() encodes to
[pk] CHECKSIG. In short,
you write Policy Language:
which compiles to Miniscript:
which encodes to Bitcoin Script:
[pk] CHECKSIGVERIFY [pk] CHECKSIG IFDUP NOTIF 0xe803 CHECKSEQUENCEVERIFY ENDIF
Verification — The example script we have here is
and_v(vc:pk(?),or_c(c:pk(?),after(1000))). When encoding to script, we first see
and_v which means encode both branches, one after the other. Then we have two conditions under
vc:pk() encodes directly as
or_c encodes as
[left] IFDUP NOTIF [right] ENDIF,
c:pk() encodes as
[pk] CHECKSIG and
after(1000) encodes to
1000 CHECKSEQUENCEVERIFY. Lets move on to a more complex construction.
2. Decision Tree v2:
The above decision tree needs one of the two branches (one requiring two pubkey signatures and the other requiring a signature AND a timeout of 1000 blocks) to be satisfied. In policy language terms, there are two branches
and(pk(C),after(1000)) OR’d together to give
IF [pk] CHECKSIGVERIFY [pk] ELSE 0xe803 CHECKSEQUENCEVERIFY VERIFY [pk] ENDIF CHECKSIG
Verification — We first see
c:or_i which encodes to
IF [left] ELSE [right] ENDIF CHECKSIG. The left branch under that is
and_v(vc:pk(),pk()) which gets encoded to
[pk] CHECKSIGVERIFY [pk] and the right branch gets encoded to
1000 CHECKSEQUENCEVERIFY VERIFY [pk]. We don’t have a checksig on the trailing pubkey in both brances since we have a final
If the left branch is instead a 2/2 multisig ie in policy language
or(multi(2,C,C),and(pk(C),after(1000))), the compiled bitcoin script becomes
2 [pk] [pk] 2 CHECKMULTISIG IFDUP NOTIF [pk] CHECKSIGVERIFY 0xe803 CHECKSEQUENCEVERIFY ENDIF.
We start to get a taste of what Miniscript has to offer.
3. Threshold Decision Tree:
This is a simple 2 of 3 threshold scheme which is satisfied if two of the three clauses(two pubkey signatures, one signature AND a timeout of 1000 blocks and one of two pubkey signatures) are satisfied. We could construct this in multiple ways — starting from any of three branches and checking if they satisfy in sequential order. This certainly takes a non-trivial amount of time.
This is where we can see Miniscript’s power on display.
[pk] CHECKSIG SWAP [pk] CHECKSIG BOOLAND TOALTSTACK [pk] CHECKSIG NOTIF 0 ELSE 0xe803 CHECKSEQUENCEVERIFY 0NOTEQUAL ENDIF FROMALTSTACK ADD TOALTSTACK [pk] CHECKSIG SWAP [pk] CHECKSIG BOOLOR FROMALTSTACK ADD 2 EQUAL
Verification — In this example, the encoder takes the first branch, checks the signatures from the two pubkeys and then pushes the result to the altstack. Then it moves to the middle branch, where it checks for the pubkey signature and if false returns 0 (since we need both the signature and the timeout). If true, it checks for the timeout and returns the result. Then we take the result from the left branch (which is now on the altstack), push it onto the main stack, add the result from the middle branch and push the result (ie left branch + middle branch) onto the alt stack again. We then evaluate the right branch, retrieve the result of the other two branches from the altstack, add and check if it equals two. Doing this by hand is going to take a while and so is verification. Having Miniscript evaluate the best approach and present it immediately is a boon.
4. Threshold Decision Tree v2:
[pk] CHECKSIG NOTIF [pk] CHECKSIGVERIFY ENDIF [pk] CHECKSIG SWAP [pk] CHECKSIG BOOLAND SWAP DUP IF 0xe803 CHECKSEQUENCEVERIFY DROP ENDIF ADD TOALTSTACK [pk] CHECKSIG SWAP [pk] CHECKSIG BOOLAND FROMALTSTACK ADD 1 EQUALVERIFY [pk] CHECKSIG SWAP [pk] CHECKSIG BOOLOR TOALTSTACK [pk] CHECKSIG SWAP [pk] CHECKSIG BOOLOR FROMALTSTACK ADD TOALTSTACK [pk] CHECKSIG NOTIF 0 ELSE 0xe803 CHECKSEQUENCEVERIFY 0NOTEQUAL ENDIF FROMALTSTACK ADD 2 EQUAL
We won’t go into detail on how this script works (its somewhat similar to the 2/3 threshold example in 3) but needless to say that this is going to take a long time to script by hand.
cargo new <repo_name> and cargo will generate a tree for you. Open Cargo.toml and add the following lines:
bitcoin = “0.18.0”
miniscript = “0.5.7”
secp256k1 = “0.12.0”
NOTE: rust-miniscript is under active development with new changes being pushed every day. So if there is (and there most likely will be) a newer version out, please upgrade
0.5.7 to the latest version.
You can think of Cargo.toml as similar to package.json in some ways, so you would have to add in dependencies. Now you’re all set. Open
src/main.rs and you can import Miniscript and have fun with it. We have a demo program (implemented using the tests at https://docs.rs/miniscript/0.5.7/src/miniscript/miniscript/mod.rs.html) over at https://github.com/bithyve/code-snippets/tree/master/miniscript-test. This tries to compile a pretty basic policy language
pk(C) and checks it against the equivalent Bitcoin Script representation. You can also have more complex conditions depending on your level of Rust l33tness.
Miniscript is a powerful construction that enables easy structuring of Bitcoin Scripts. It simplifies the task of having to hand roll application specific Scripts and enables natural expression of complex conditions. While being a subset of Bitcoin Script, Miniscript should suffice for most practical purposes. Onwards to Simplifying Sound Money.
Thanks to Andrew Poelstra for the presentation at Boston Bitdevs, help in understanding Miniscript and getting started with rust-miniscript. Thanks to Pieter Wuille, Andrew Poelstra and Sanket Kanjulkar for inventing, implementing and optimizing Miniscript. Thanks to Andrew Poelstra, Rhys and Gert-Jaap for their valuable inputs and review of this article.