Table of Contents
SPEECH TITLE: The future of Bitcoin smart contracts (Simplicity of Liquid)
SPEAKER: Christian Lewe
CONFERENCE: Baltic honeybadger 2023 (layer 2 day)
At Blockstream we work on the future of Bitcoin. We’re dreamers, so we dream about the future of Bitcoin. We dream in terms of decades, not the next few months but the next few decades. Simplicity is one of these things that we think are necessary for Bitcoin. It is a new scripting language that will solve many problems. Today, I’ll talk about that. Why are we here? Where do we want to be? What is the problem we’re trying to solve?
Where do we want Bitcoin to be?
Bitcoin as a sending mechanism
We all have an idea of Bitcoin, the digital money and the features it should have, the problems it should solve. What we want to do is to send money to a person that we call the recipient. This can be a single person; this can be multiple people, which we call multisig. This could be a Lightning channel or HTLC. This could be a company, or things like a DAO, which we don’t have right now on Bitcoin, any kinds of things. When I send my money, it should arrive at the right location.
Bitcoin as a store of value
We want to store value on the blockchain. Bitcoin is famously deflationary. There is a fixed supply and there will be no more coins after the block subsidy runs out in 2140. There are some proposals that would make it easier to manage your coins, such as vaults. Obviously, any improvement in blockchain security also helps to keep coins with the rightful owner.
Bitcoin as a layered onion
We want to scale Bitcoin. Bitcoin layer one is limited. The idea is to use Bitcoin on layer twos, which are anchored into layer one somehow. A layer two has a small layer-one footprint, so using it increases overall transaction throughput.
Bitcoin as an everyday tool
Using Bitcoin should be cheap and efficient. I should not pay excessive fees to make a transaction and I should be able to run a Bitcoin full node to secure the network. This is what we can all agree on. I think we already solved a lot of these problems, but we’re not quite there yet.
Where is Bitcoin right now?
Sending coins is limited
We can send coins to public keys, we can do HTLCs, we can do Lightning, we can do slightly more complicated things, but there are many applications, many ways how we could make our money available to a use case, such as a DAO or a zero-knowledge rollup, that we cannot do right now. We’re limited in how we can move our coins and do interesting things with this money.
Miniscript makes Bitcoin Script less scary
Miniscript helps. There is Bitcoin Script which makes Bitcoin Bitcoin. If I send coins somewhere then I encode the recipient in a script. There are many features of Bitcoin Script that almost no one uses. Most features are not used because they are dangerous; they are scary: you could lose your money. Miniscript helps with that because it enables you to write scripts in a structured way. It gives you some guarantees. It basically tells you, if you write the script this way, then your money will arrive at the right location, and you will not lose any funds in the future. If you use a timelock, then it tells you, in N years when the timelock expires, the money will arrive. The recipient will have the money; they will not lose the money in N years. There are applications like Liana wallet that use this feature.
Coin security could be improved with vaults
Vaults are an interesting proposal. This is the store of money idea. It would be great to have a cold storage vault where you can withdraw only a certain percentage of its content. If a hacker tries to get the money out, then you can detect that and stop the hack. Unfortunately, we don’t have vaults yet.
Bitcoin scaling is limited
Bitcoin has layer twos and many layer twos propose new opcodes. Bitcoin Script is not enough to support the layer twos that people want to build. Even Lightning proposes things such as SIGHASH_ANYPREVOUT. These layer twos need extensions of Bitcoin Script, either to work at all or to become better.
Bitcoin Script receives small updates
Bitcoin Script is not enough right now. There is a huge demand for extending it. We have been extending Bitcoin Script step by step, opcode by opcode, to solve small problems that come our way. An alternative that we propose here is one big update that solves all problems in one go.
Simplicity to the rescue
How can Simplicity help? If I want to send coins to a recipient, if I want to make my coins available in some kind of way, then Simplicity solves this problem. I write a Simplicity program that encodes the spending condition. Simplicity is able to cover all imaginable use cases. Miniscript makes Bitcoin Script less scary, because it gives you formal guarantees, and Simplicity is just like that but better. It’s formally verifiable, so you know that when you send coins to a Simplicity UTXO, then the locking program works in the way it is intended.
Building layer twos with Simplicity
We want to build all possible layer twos. We can mathematically show that all imaginable layer twos are supported by Simplicity. That’s powerful. That said, if there are certain ways that we do not want to use Simplicity, then we can limit that. In principle, everything is supported or it’s possible to support anything we want to support.
Simplicity as Bitcoin’s last soft fork
There is the idea of the last soft fork. Maybe you’ve heard about this idea. Simplicity will not be the (absolutely) last soft fork, but instead of having a lot of small upgrades to Bitcoin, we do one big upgrade that covers a lot of use cases. That’s one soft fork. For many future use cases, we won’t need a soft fork, because this use case is already supported. I will talk about what this means in more detail.
Running Simplicity on the main chain
Running Simplicity should be fast. To prevent DDoS, if you do something very complicated on layer one, then you should pay for the complicated computation. You shouldn’t be able to DDoS the main chain. There should be a smart fee function that accurately predicts the cost of running a program.
What is Simplicity?
Simplicity data and functions
How do you do Simplicity? First of all, you always encode some data. There is a Simplicity data format. Second, you represent the functions that work on the data in Simplicity. I call them Simplicity functions. For instance, there is the function “add one” and the corresponding Simplicity function “increment”. In Simplicity, you also have access to witness data and to introspection. That’s all you need: You write a program to lock a UTXO. Later, you want to spend the UTXO. Based on the witness data that you provided, based on the introspection, the program tells you, “you can spend the UTXO” or “you cannot spend the UTXO”. This is the same system as for Bitcoin Script, but Simplicity is much more general in what it can compute. That’s the execution model of Simplicity.
With Simplicity, you can do any kind of computation off-chain. For instance, a zero-knowledge proof verifier. You can run a layer-two rollup. You can write proof that says a state transition of a rollup block is valid, which is a very hard computation.
You can verify this computation on-chain, which is easy. It’s relatively easy, which is important, because everybody has to do this verification. You can put the computation on-chain very easily if you just verify it. This is the dichotomy between computation and verification. Computation is always hard and verification is always easy. Verifying is much, much easier than computing. Computing is like solving a Sudoku puzzle, which is hard, and verifying is like checking the solution of a Sudoku puzzle, which is easy. Computation is like mining a Bitcoin block, and verifying is like verifying a block. The latter is much easier. Simplicity can do every kind of verification. Any computation you can do off-chain, you can verify on a main chain with Simplicity support.
There are consensus limits. Simplicity is powerful, but we don’t want to DDoS the main chain. We have hard limits of what you can put on the main chain, which is reasonable. There is a way how Simplicity deals with this: There are certain things we might want to do, like signature verification, and we do these things quite a lot. I would say, every UTXO has a signature check. The corresponding Simplicity program should be small and fast. For this, we have so called jets, which are shortcuts for common functions: You have the Simplicity program which does a signature check, and we can compress it to a small bit, which we call a jet. You can compress anything, such as addition, subtraction, hashing, signatures, maybe a zero-knowledge verifier. You compress it into one atom, which is what we call a jet. When we see this jet we have optimized C code to execute it, so the jet is small and fast. Simplicity comes with a fixed catalog of jets. We say, these are the use cases that we are interested in, this is the introspection that we are interested in, this is what we want to optimize. You can combine jets as well to do more complicated computations. This ties into the last soft fork idea.
No need for soft forks
Why might Simplicity be the last soft fork? Simplicity is general enough to cover a lot of use cases. We have this rich catalog of jets; it’s already quite long and it’s getting longer. In the end, the community decides what they want, obviously. We do not force anything upon you, but we give you the opportunity to choose. We give you basically the horn of plenty and you choose what you want. One of the things we do is to expose libsecp internals. You are able to work with elliptic curves in a very low-level way. You can do point additions and all kinds of things to build cryptographic primitives. Each jet solves a specific problem and you can combine multiple jets to solve new problems. You can combine further to solve even more problems. With this, you can do a lot within consensus limits: A jet is very small, the combination of multiple jets is very small, and small programs are within consensus limits. This is a general paradigm that enables a lot of use cases. Simplicity is a huge update. Small soft forks like OP_CTV and SIGHASH_ANYPREVOUT you can do immediately in Simplicity; you don’t need a soft fork. They are already included. You don’t need new opcodes for a lot of use cases, because you can just do those things already in Simplicity. That’s why it is the last soft fork.
Soft fork prototyping
If you ever need a new soft fork, then Simplicity helps, because Simplicity is a prototyping language. The idea is, you want to make a new opcode, like OP_CTV. In Simplicity speak this is a new jet. You can already do OP_CTV in Simplicity, because Simplicity is strong enough, but it’s a very large program. You want to make OP_CTV more compact by defining a jet as a shortcut. Because you can already do OP_CTV in an unoptimized way, you can deploy it on a test net where you disabled consensus limits. There you can try all possible opcodes. There are no code changes required; you just run the unoptimized programs. So, you deploy your OP_CTV program on the test net. You can test the program with your friends and with your community and see how and if it works. Now you want to compress the large program into a jet. You write C code for OP_CTV, which is optimized. If you want, you can formally verify that this C code is equivalent to the OP_CTV program that you deployed on the test net. Once that’s done, you add your new jet to the catalog of the next Simplicity version. Simplicity is versioned and enables updates. You can always add more jets through a new version. That said, our goal is to make the initial catalog rich enough so you don’t need to extend it for a while.
Reality check: Simplicity today
Let’s see where we are right now. Simplicity sounds cool, but what has been done and what do we actually have? Liquid is especially interesting, because we want to deploy Simplicity on Liquid. First, the Simplicity language is defined. This work is by Russell O’Connor. He knows what he’s doing and the formal details are done. Second, the core language is implemented. We have C and Rust implementations. The C implementation is consensus critical and that’s done. We are working on the jets. They are mostly done and can do all kinds of arithmetic and elliptic primitives already. We also have a lot of jets for Elements internals. Anything you can do with Elements Script, like all the introspection, all the covenants that Elements can already do, you can do with Simplicity. What we are still working on is the deployment itself. Where can you use Simplicity right now? We have a Simplicity testnet, which is public; you can join it. It’s separate from the Liquid testnet. To join you need a patched Elements Core version. There is a Simplicity PR for Elements Core. Finally, we are working on tools. We have a Simplicity wallet, which I am working on. We have a file encoding and we have a higher-level language to write Simplicity contracts. I will talk about all of these things. All in all, the theory is done and in practice, as always, there are a few things that need to be ironed out.
The Simplicity wallet is a descriptor wallet which uses Simplicity descriptors.
What is a descriptor? Unfortunately, descriptors didn’t exist at the beginning of Bitcoin, but they are really useful. Bitcoin Core is currently migrating to descriptor support. It has a descriptor wallet while other wallets are now called legacy wallets. Descriptors help you locate and spend UTXOs. They have information that’s not necessarily included in the UTXO and that helps you spend the UTXO later.
Taproot descriptors with Simplicity support
There’s a thing called the Taproot descriptor. It consists of an internal key and a tap tree. The internal key is for the cooperative spend, and the tap tree includes the different spending conditions you can use to otherwise spend the UTXO. The tap tree consists of tap leaves which hold the actual scripts. Right now, the scripts are written in Bitcoin Script. We can extend Taproot to Simplicity: We introduce a new kind of leaf: a Simplicity tap leaf. The leaf contains a thing called the Simplicity policy, which is basically the Simplicity program. This gives the leaf Simplicity support. To spend this leaf, we have to satisfy this so-called policy. I will talk about what that is.
This is what a Simplicity descriptor looks like. It’s an Elements descriptor with an internal key at the front and a Simplicity part at the back. The latter includes policies. In this example, you need to provide signatures to the public keys B and C. In Elements Miniscript we recently added Simplicity support. There are tap trees which can include Miniscript or Simplicity tap leaves.
and(X,Y) or(X, Y)
thresh(k; X, …, Y)
This is an overview of Simplicity policy. A policy encodes a spending condition, which defines what you need to spend a UTXO. Basically, the policy encodes a recipient; “where does my money go?” There are public keys, hash blocks, timelocks and thresholds. You can combine these fragments into more complex policies. Policy gets interesting with what we call the asm fragment. You can put any Simplicity program into the asm fragment and say the recipient of these coins is whoever can satisfy the program. This is interesting, because Simplicity is very general. The program could be a zero-knowledge rollup, a DAO, or something very complex.
The Simplicity wallet has support for Simplicity descriptors and Policy. It works with the latest Elements Core, which has Simplicity patched into it via a separate branch. When you open the wallet for the first time, you generate an xpub descriptor. You can import more complex descriptors, which includes the aforementioned asm descriptors. You can generate addresses and send coins around. This is what we currently have. The wallet lets you interact with Simplicity in this descriptor context, which is currently the only way you can interact with Simplicity on an Elements blockchain. You can see how Elements accepts or rejects your spends.
Using asm descriptors
Let’s spend an asm descriptor. If I want to spend a public key descriptor, I just provide a signature for the public key. Easy, we all know this. The asm descriptor is interesting because I have a Simplicity program and I need to satisfy this program to spend my coins and to move my coins. I can do things like key delegation where I own a UTXO but I give the UTXO to someone else without a transaction. This delegation is built into the UTXO like a second condition where I say “this other person can also have the UTXO”. I define this at spending time. First, I create the UTXO, and then much later, I decide to give the UTXO to some person. I don’t need to decide on that person when I create UTXO, which is why delegation is powerful. You might have heard of graftroot, which also enables delegation. Apart from delegation, I could use Simplicity for a universal sighash. SIGHASH_ANYPREVOUT is a sighash which enables eltoo channels. Sighashes are interesting, because they allow you to work with presigned transactions in novel ways. Simplicity supports every single sighash. In fact, we can put all sighashes into the same UTXO and say “I will decide on the sighash when I spend the coins. I won’t decide on the sighash before”. Finally, you can use Simplicity for elliptic curve cryptography and whatever comes out of that. That’s powerful.
Using the wallet
You write a Simplicity program and put it into an asm descriptor. You send money to fund the descriptor and get a UTXO. Then you write a satisfying witness for the program that you wrote. Right now, you have to write a JSON file, which is a manual process. You give this file to the wallet, which constructs and broadcasts a transaction. The coins are moved to wherever you wanted to move the coins. I’m working on a way to make the wallet easier to use. It would be good to specify what a satisfying witness looks like when you create the UTXO. Then you could generate the witness automatically instead of having to write everything by hand. Say, you do a zero-knowledge proof rollup. There is a Simplicity program. To do a state transition, you have to spend the UTXO and create a new UTXO. You have to provide a very complicated witness which satisfies the zero-knowledge proof verifier. This witness is basically a zero-knowledge proof that proves that this state transition is valid. This is complicated, but you can make it easier with some higher satisfaction logic.
Simplicity for the masses
We are also working on making it easier to write Simplicity itself. To me it was a little surprising how useful this is, but it’s of course important to write Simplicity in some text editor and to get useful error messages. You can open the text editor of your choice and start writing Simplicity in the so-called human encoding. Then you send this text to a parser that will give you type errors and syntax highlighting. I think we have some language server bindings, which you can use in VS Code and other editors. The parser will give you a base64 encoding of the Simplicity program you wrote. We use base64 as an export format in the wallet and in all our other tools. The text is readable and the base64 is not so readable. The former is for humans, the latter is for machines. This is why the text is called the human encoding.
This is what the human encoding looks like. Here we have a key lock. There is the public key that is encoded into the UTXO itself. There is a message which is the sighash, in this case SIGHASH_ALL. Finally, there is a signature, which is the witness data that needs to be filled in. We bundle key, message and signature into the variable “input”. The “output” is a Schnorr (BIP340) verifier which takes the inputs and verifies the Schnorr signature. The program takes witness data and tells you “you can spend the UTXO” if the signature matches or “you cannot spend the UTXO” otherwise. This is a simple example, but I think it helps with understanding.
Something that Sanket is working on is a domain-specific language (DSL), which is high level. The DSL is more like Rust: It has data types. Simplicity just has data encoded in the Simplicity encoding. In the DSL, you have u8, u32 and so on. You have variables and types. You have control flow through if statements, loops and functions. You write a DSL program in a file and you can compile it into the human encoding, which is lower level. The DSL is high level and the human encoding is low level. The DSL is still work in progress, but many people are looking forward to an easier way of using Simplicity.
Your next steps
You have seen Simplicity. What will you do after this talk?
You will install Elements with Simplicity support. You will install the wallet. And you will join the testnet. The steps on how to join are provided in the wallet repo.
Then you write your first program. You will install the human encoding parser, which lives in the rust-simplicity repo. There, you can look at some example programs like the key lock. We have documentation on GitHub, we have blog posts on the Blockstream blog, and we have the tech report on the main repo. Then you start using the asm descriptor in the wallet and play around with that.
I think rust-simplicity is the most important repo. This is where most of the activity happens and it is the most user-friendly code base.
Disclaimer: Transcripts provided on bitlyrics.co represents solely the opinion of the speaker and is not by any means financial/legal advice or an opinion of the website. The content has been transcribed with maximum accuracy. Repetitions and fill words have been amended in order to enhance the reading experience. The full text may not be confirmed by the speaker. Please, refer back to the above-provided source of content for more certainty. If you are a speaker and wish to confirm/amend your speech please contact us.