Miniscript - Custody, Computable, Composable
Speakers: Andrew Poelstra
Date: November 6, 2021
Transcript By: Michael Folkson
Tags: Miniscript
Media: https://www.youtube.com/watch?v=mVihRFrbsbc&t=6470s
Slides: https://download.wpsoftware.net/bitcoin/wizardry/2021-11-tabconf/slides.pdf
Intro
I am here to talk about Miniscript. This is something that I have talked about a few times before but usually in a much more technical way where we get into the weeds of Bitcoin Script and what Miniscript is and how to use it and stuff. I want to talk today about Miniscript from the perspective of someone actually trying to use Bitcoin and how this solves problems that I think are very important in the space regarding how to custody your coins, how to keep track of your keys, stuff like that.
Before I jump into that I’ll introduce the structure of the talk. I am going to talk a little bit about Bitcoin script for a couple of slides. Bitcoin script is a system that is built into Bitcoin that is used to define spending conditions for coins. Typically what that means is that for an ordinary wallet you’ll have a script that basically says “To move this coins a transaction needs to have a signature with a specific public key”. Your script is really just a thin wrapper around your public key. But you can do more interesting things using script. You can say “Here’s a list of 3 keys and any 2 of them need to sign”. You can do an escrow 2-of-3 thing like that. You can check that a certain amount of time has passed before certain keys can activate. You can check that hash preimages are revealed and that is how Lightning HTLCs work. There are all sorts of neat technical features that are accessible in Bitcoin script. But there are a number of problems that make this in practice quite difficult to do. As a result the situation with Bitcoin wallets is that typically they don’t really use script in any interesting way at all. And when they do they do so in ways that don’t really interoperate with each other. What I am going to demonstrate here is that there is another way to write script for Bitcoin where rather directly using Bitcoin Script you can use this thing called Miniscript that gets you a framework for writing scripts which is easy to reason about. I have these three words here: custody, computable and composable. Custody is what I am going to focus on, how do you hold your coins? Computable means with Miniscript you can reason about what your script is doing. You can figure out how much it is going to cost, you can do accurate fee estimations. You can determine semantic properties of your script. You can ask questions like “Given some arbitrary complicated script could somebody use that script to move my coins without my signature?” That is a question that you should ask for any complicated script. Prior to Miniscript the answer was basically “I don’t know, don’t use script. Stick with something you know, stick with templates, stick with something you’ve analyzed.” But with Miniscript you can do arbitrarily complicated things and still be able to answer questions like that. The third word is composable. This is interesting, it is a bit of a technical word but the premise is pretty straightforward. If you have a high level spending policy and you want to replace some components of your spending policy with something complicated with Bitcoin script you can’t do that easily, verifiably. With Miniscript you can. An example of that would be imagine you’re on the board of directors of some company that is holding a bunch of Bitcoin. You agree to do some sort of multisignature policy. You need like 4-of-5 board members to sign to move the coins. You are one of these board members. The rest of the team ask you for a public key. But you don’t really want to give them a single key where there is some secret key somewhere that you could lose. For the same reason that you wouldn’t want to keep your Bitcoin in an output controlled by a single key. You want some redundancy, you want some resilience. So ideally you could say “My part of this 4-of-5 is itself going to be a 2-of-3. Three keys that I have custodied in different parts of the world under different rules or whatever.” With Bitcoin script you can’t really do that. If somebody asked you for a key and you reply with this complicated set of keys and rules for how they behave the person receiving that and the other participants in this script are going to have a hard time verifying that what you are doing is ok. Basically what you are doing is providing a fragment of script, a fragment of computer code and asking to insert that into the middle of a larger computer program in a model that doesn’t really match the way that people think about spending conditions.
Issues with Bitcoin Script
I have a couple of slides here listing problems with script. Some of these are fairly technical and things you only really care about if you are a wallet developer. Some of these are things that everyone should care about. In particular the first two, what do you care about when you are creating a Bitcoin script, when you are defining spending policy? You care that it is correct. This means that the coins will move if they are supposed to move. And you care that it is secure. Which means that the coins will not move if they are not supposed to move. Those two things are kind of difficult to express in Bitcoin script because Script is a low level stack based programming language. It is a set of small instructions to the computer and all the nodes in the network that says things like “Move this data here and then swap this data with that data, then add this data to that data. Interpret this blob as a signature and check that the signature is correct with this other blob that you should interpret as a public key.” And so on. Going step by step by step rather than thinking what are the set of conditions under which the coins should move. This is the way that people think about this. People want a set of spending conditions and they want to verify that the script somehow represents exactly that set. No more and no less. The bottom two points that I’ve got here are ones that you care about as a wallet developer. The reason that I list them is that this is basically the reason that you can’t download an arbitrary Bitcoin wallet and do interesting things with it. Stick multisignatures onto it or attach 5 different hardware wallets to it or have a second co-signer or whatever. You need a wallet that specifically supports those things and if you choose one you can’t really move your coins from one to the other without actually sending them across the network. You can’t export a policy and import it somewhere else. The reason is that as a wallet developer, when you are trying to implement these things in Script, you need to figure out how to actually produce transactions on the network that use this script. It turns out there is a whole list of technical problems that you have to solve. You have to solve it independently for every single script that you create. If you want to make an interesting wallet you need to hire a team of Bitcoin experts to define a script that makes your wallet interesting. They need to do all this analysis, they need to solve a bunch of questions about how big will the transaction be and how do you estimate fees? If you have the signatures how do you put them in the right order? All these annoying detailed questions that you need to answer for your stuff to work but you don’t care about. It is not part of your business logic. It would be better if this was somehow a solved problem.
Zooming out you get these two problems that I have been hinting at. Interoperability and composability. Interoperability, if you are using an interesting wallet of one form and you want to work with an interesting wallet of another form you have to pick one or the other. And to switch between them you have to send the coins across the network. If you want to do something like do a coinjoin where you have two wallets, maybe you have a BitGo wallet on one side and a Blockstream Green wallet and then a Bitcoin Core wallet and you want the 3 of them together to sign a transaction, that is pretty difficult to do. Those 3 different wallets have different script templates and they don’t understand each other’s scripts. Relatedly we have this problem of composability where suppose we are back to this board of directors example. I am one of 5 directors and we want 4 of those 5 to be able to move the coins. I say “I don’t want to give you a public key. I use BitGo for custody so I am going to give you this BitGo script that has a 2-of-3 signature where only 1 of the keys is mine. I want you to fit that into this higher level policy”. In Bitcoin Script you basically can’t do it, more or less.
Miniscript
The technical idea behind Miniscript is that we can take all of the little things you could do, all the individual functionalities of Script that people use in practice, signature checks, hash checks and time checks basically, and the different primitive ways that you could compose these. You can use ANDs, all of these conditions need to be satisfied, or ORs where only one of them does, or thresholds which are kind of in between. You say “3 out of the 5 of these things need to be satisfied for the coins to move”. We can create these little short script templates that represent these. Rather than directly working in Bitcoin Script we are going to work in this higher level language where the primitives that you are working with are things that have more meaning. That’s not a novel idea, people have tried to make high level languages for Script a number of times. There are two cool features of this. The big one here is that this isn’t a separate language from Script where you compile it down and you have a compiler that tries to interpret what your code means and output a script that has the same meaning. With Miniscript it is actually a direct one-to-one correspondence here. You are just encoding your Miniscript directly in Script and you can decode it as well. You can see that what is on the blockchain is exactly your policy. You are secretly directly working with Bitcoin Script. The other difference between this and what you might think of as a higher level language, is that it isn’t just a friendlier way to write an imperative program. This isn’t a friendlier way to tell all of the nodes “Do this, do that, do that, do that and if everything is ok let the coins move”. The way that Miniscript works is that you really are directly representing the spending conditions that you want to express.
Here I have got a picture. On the left I’ve got a Bitcoin script where I’ve added some indentations and colors to try to separate it. But it really isn’t a structured language so I made some arbitrary choices there. And on the right I have a Miniscript which represents the same thing. These are exactly the same thing. The Miniscript typically if you are actually using you wouldn’t have this graphical representation, you would have a text string, but you could essentially read this tree structure here. You can see what is happening here. I have got 4 public keys in parallel and at the top level I have this OR. I have these two halves of the script and I need one of them to be satisfied in order for my coins to move. On the left side here I have got this multisignature, what I call a thresh_m
which says “Out of the set of keys some number of them need to sign off to move the coins”. On the left I’ve got this 2-of-3 policy. On the right I’ve got this AND, two things need to be satisfied if you want to take the right branch. The first is is a signature with an alternate emergency key and the final thing is after(1000)
. after(1000)
is pretty cool, what that is saying is this branch can only be used after 1000 blocks have gone by if the coins have not moved in 1000 blocks. The idea here is that I have this normal spending policy, this 2-of-3 policy that under normal conditions I want to use to allow the coins to move. But if something goes wrong there, if 2 of the signers have dropped off or lost their keys or whatever and their coins are unable to move then after some amount of time, it doesn’t have to be 1000 blocks, it could be a year, it could be until some fixed date in the future, if the coins haven’t moved in a long time then this alternate emergency key should come online. It can’t be used until then. The point is that it is an emergency key and it possibly has a different trust model that you don’t want to be active under normal circumstances. You can sort of see, the code on the left, it would be very difficult for me to express that, describe how the code on the left maps to the actual policy that I want. But with the code on the right, if I want to zoom out and say what this is doing, what I can do is just delete a lot of the extraneous stuff here, in the Miniscript representation I’ve got these b
s and v
s, I’ve got some subscripts and so on. Those are just details, those are not so important to a user, they are important to a wallet developer. If I drop that data now you can see I have this high level policy here. You can see I have got an OR, I’ve got an AND, I am directly representing my spending conditions. If you are an auditor or you are just somebody trying to participate in a script like this you can look at this and visually see that the script policy matches what you intuitively expect it to be. If you are trying to ask questions like “Is it possible to spend these coins without my key?” you can look at this. Let’s suppose my key is pk1
. I can see there are actually a couple of ways these coins can move without my key. The first is obviously if pk2
and pk3
decide to sign, you only need 2 of those 3 keys. My key doesn’t matter, that is good to know. Then the second is this emergency clause. I can see on the emergency clause that it only activates after a 1000 blocks. As long as things are going normally is my key necessary? I have a much simpler question to ask. I can hide that entire timelock section. What is even cooler is that I can do that automatically. I can ask the computer or ask a program design to analyze these Miniscripts. “What if the coins are only 10 blocks old?” The computer can drop everything that’s contingent on timelocks that are further than 10 blocks out. I can say “What if my key is definitely present? What would the script look like? What remaining conditions need to be satisfied?” “What if my key is definitely not present? What conditions need to be satisfied?” You can sort of see that I am going to get very precise answers here. If my goal is to assure myself that the script makes sense that’s pretty cool. In fact I can do that with an arbitrary script. I don’t need to have a team of Bitcoin experts build this tree on the right. This is something you could do with a fairly surface level understanding of how Bitcoin works but maybe a detailed understanding of what you want your threat model to be and what you want the security policy for your coins to be. Which is what you want, Bitcoin technical experts are not necessarily experts in your security model and vice versa. Ideally you don’t need to find people who are experts in both.
This also leads to solutions for my two problems here: interoperability and composability. Interoperability kind of comes for free. As I said you can ask these questions in an automated way, go through any script and get answers to them. Which means we can write general purpose libraries, we’ve written a couple Pieter and I, that can answer questions like this. Wallet developers can just use these and build wallets that will work each other even when they are doing interesting stuff. The major wallet that I’m aware of that is using this is the Specter wallet. There is a wallet library built on top of Miniscript called BDK. I think there is a workshop about today. It uses Minsicript under the hood to give your wallet all sorts of cool features. Then the second problem of course is composability. You can almost see visually how this solves the composability problem. If I am supposed to be a public key, one of those red squares was just a public key representing Andrew. I say “I don’t want a public key, I want a 2-of-3 script. I want some complicated thing.” I can replace my red public key square with an arbitrary subtree of other conditions. Everyone can verify that I’ve just replaced my own little part of the tree with my weird conditions.
To give a final example of how this is useful before I move on, you can imagine someone like BitGo whose business model is that they are a countersigner, or Blockstream Green which kind of does the same thing but for retail customers. Their model is that they just sign off on every transaction that you ask them to sign off unless you call them out of band and tell them to stop. Or unless they detect funny behavior on the network. Or unless they see a double spend or whatever. They have some basic rules that they follow. From their perspective they care that your coins are stored in a script that requires they sign. The way that both of these wallets do their thing is that right now they have fixed scripts that you have to use. A simple 2-of-3 or 2-of-2 multisignature script. But the only thing that BitGo cares about and the only thing that Blockstream cares about is that their key is necessary. So you can imagine one of these Miniscripts where at the top level you’ve got an AND and then one branch of the AND is their key. The other branch is completely arbitrary, they don’t even have to look at it. Unlike Bitcoin script where they’d have to read the whole thing, “Are there bugs here? Can you override these other conditions?” In Miniscript when you see an AND then you know that both conditions have to be satisfied. If you only care about one of them or you’re in control of one of them you really don’t care. You know my signature needs to be present. This would allow BitGo or Blockstream Green to both do much more interesting and generic flexible things and allow their users a lot more freedom to do interesting things with their own keys. Splitting up their keys, doing multisignatures, things of this nature.
Future Work
As a final point, this is a little bit speculative but I want to talk from a technical future looking perspective. On a technical level I have been talking about Miniscript as a set of script templates. You have these red and blue boxes and each one of them represents a few opcodes or whatever. But really Minsicript is a different paradigm of script. It is a way of representing your spending conditions directly as these trees of spending conditions. The way that I’ve summarized, Miniscript describes the conditions to satisfy rather than instructions to execute. There are direct benefits for users of Bitcoin script but another cool thing is that in principle we don’t need Bitcoin script to be what we translate Miniscript into, we could have any language really that can express spending conditions. As long as you can extract fragments of that language that represent these different ORs and ANDs and thresholds and so forth you can translate that language or some subset of that language into Miniscript and back. The cool thing here is that if you have two systems that have completely different scripts systems, you can imagine Bitcoin script versus Ethereum’s EVM, in principle you could write a Miniscript backend for EVM and then you could take a Bitcoin script, decode it as a Miniscript and then reencode that as an Ethereum program. Now you have this set of spending conditions that you can express in the same way on Bitcoin and Ethereum. And you can verify that they are the same and the same going in the other direction. This is a cool trick, that by itself is maybe interesting to people who need to work with both of those blockchains. But the other interesting thing is that if we were working on extensions to Bitcoin script or replacements for Bitcoin script, which I am, I spend a lot of time thinking about blockchain programming languages, then I can use Miniscript as a guide for what users are trying to do. There are two directions I can extend this. The first is thinking about replacing Bitcoin script in which case I want to obtain compatibility with Miniscript. In doing so I allow existing users on script and Miniscript to directly translate their stuff into my new language. I can look at how efficient is that, what does that do to their fee rates, what does that do to their computational efficiency of answering various questions about your script. That is kind of cool. That means I can start working on improvements to the underlying Bitcoin script system. Rather than thinking script was already a nightmare for people to use, now I am going to ask them to learn this whole new thing, I can say “Miniscript provides a direct migration path”. Not only as a developer, I care about people using my stuff, I care about there being a migration path. For users what they care about is they can try my thing and then go back. They are not committed to learning a whole bunch of stuff. Miniscript, because it is a not compiled language, it is really a reinterpretation of the underlying script, it lets you do that. You can take a Bitcoin script, interpret it as Miniscript, reinterpret that as EVM or reinterpret that as Simplicity or reinterpret that as whatever future thing and then go back. You not only have an easy way to back off but you can [recording cuts out]…..