Not-mini Miniscript

Varunram Ganesh
Jul 1 · 7 min read

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:

  1. 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.
Small Minions

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 and(pk(C),or(pk(C),after(1000))).

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 [pk]CHECKSIGVERIFYwhile c:pk() encodes to [pk] CHECKSIG. In short,

you write Policy Language:

and(pk(C),or(pk(C),after(1000)))

which compiles to Miniscript:

and_v(vc:pk(?),or_c(c:pk(?),after(1000)))

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 and_vvc:pk() and or_c(c:pk(?),after(1000)). vc:pk() encodes directly as [pk] CHECKSIGVERIFY. 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:

Bigger minions

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),pk(C)) and
and(pk(C),after(1000)) OR’d together to give

Policy Language:

or(and(pk(C),pk(C)),and(pk(C),after(1000))) 

Miniscript:

c:or_i(and_v(vc:pk(?),pk(?)),and_v(v:after(1000),pk(?)))

Bitcoin Script:

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 CHECKSIG.

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:

Mini Boss Battle

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.

Policy Language:

thresh(2,and(pk(C),pk(C)),and(pk(C),after(1000)),or(pk(C),pk(C)))

Miniscript:

thresh(2,and_bool(pk(?),pk_w(?)),wrap(and_c(pk(?),time_f(1000))),wrap(or_bool(pk(?),pk_w(?))))

Bitcoin Script:

[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:

Boss Battle

Policy Language:

and(and(thres(1,and(pk(C),pk(C)),after(1000),and(pk(C),pk(C))),thres(2,or(pk(C),pk(C)),or(pk(C),pk(C)),and(pk(C),after(1000)))),or(pk(C),pk(C)))

Miniscript:

and_cat(or_cont(pk(?),check_v(pk(?))),and_cat(thres_v(1,and_bool(pk(?),pk_w(?)),time_w(1000),wrap(and_bool(pk(?),pk_w(?)))),thres(2,or_bool(pk(?),pk_w(?)),wrap(or_bool(pk(?),pk_w(?))),wrap(and_c(pk(?),time_f(1000))))))

Bitcoin Script:

[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.

Running Miniscript

Nothing’s complete without trying out the source code. The website is powered by a javascript version of Miniscript we couldn’t seem to find, but we found the rust library for Miniscript: https://github.com/apoelstra/rust-Miniscript. To get started, you’d need to have Rust and Cargo installed. To save you a click, you can head out to https://www.rust-lang.org/tools/install if you haven’t done that already. Once you’re done with installing rust, you’re ready to hack on rust-miniscript. Create a new cargo repository using 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.

Conclusion

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.

Acknowledgements

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.

Resources

  1. http://bitcoin.sipa.be/miniscript/
  2. https://www.youtube.com/watch?v=XM1lzN4Zfks
  3. https://github.com/apoelstra/rust-miniscript
  4. https://docs.rs/miniscript/0.5.7/miniscript/miniscript/struct.Miniscript.html

Thanks to Rhys Lindmark

Varunram Ganesh

Written by

Opensolar @mitDCI, R&D @Bithyve_, Climate Change @Yale Openlab

BitBees

BitBees

BitHyve’s comunity conversations: All about Bitcoin — technology, economics and liberal arts

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade