Simplicity: Taproot and Universal Sighashes
By Andrew Poelstra and Russell O’Connor
Our last blog post about Simplicity was nearly ten months ago, and since then we have been hard at work pushing Simplicity toward a production-ready deployment. The previous update described the disconnect combinator, a low-level component of the Simplicity language that can be used to implement delegation and looping, and our implementation of the combinator in our Simplicity branch of the Elements codebase. Today, we are excited to talk about significant improvements to that branch, which bring Simplicity much closer to production-readiness and becoming more accessible to users familiar with other programming environments.
Our changes include building out an expansive set of jets, which support many arithmetic and cryptographic operations, including the BIP-340 “Schnorr” signatures and the key-tweaking mechanism used by Taproot. Users can even write their own hash-to-curve function. We have also rewritten the jet encoding, added more static analysis, and improved our anti-denial-of-service protection.
We’ve also started working on new tools to work with Simplicity, including rust-simplicity and elements-miniscript. Our integration into Elements now uses Taproot’s tapleaf versioning system rather than a new Segwit version, which improves privacy for Simplicity users and simplifies integration with existing scripts by allowing users to mix Simplicity with ordinary Script.
With all these improvements in hand, we’ve put together a demonstration of how to create a pair of transactions, one sending coins into a Simplicity program and the other moving them back out, and made a short video illustrating how users can replicate this. Our Simplicity program of choice, the universal sighash program, is also interesting in its own right.
As described in our last post, Bitcoin has a feature known as “sighash modes” which allow users to choose which parts of their transactions are covered by their signatures. There are six possible modes, each describing different sets of transaction data to cover. One (contrived) way to think of them is as a programming language in which you are only able to express six programs. By extending this language to express any possible program, we get some intuition as to what the “universal sighash” program does and why it is exciting.
Over the years, whenever changes have been made to the transaction data covered by Bitcoin’s signatures, such as when Segwit and Taproot introduced additional data to be signed, developers have proposed extending the set of sighash modes to give users more freedom to sign (or not sign) individual parts of the transaction. To keep the design space small, these proposals never made it into larger upgrades, and independently they never gained much traction.
The most popular sighash-extension proposal, and one that seems to have a very high chance of making it into Bitcoin, is ANYPREVOUT― an extension that would greatly improve the scalability of the Lightning network. With Simplicity, we get all these, and more, for free.
The key insight is that sighash modes, unlike any other aspect of Bitcoin’s Script, allow the user to decide what gets signed at signing time rather than at address generation time. In Bitcoin, this signing-time ability is limited to setting the sighash mode, but with careful use of Simplicity’s disconnect combinator, we can go much further. We can enable the signer to do much more than fixing various parts of the transaction data. He could fix arbitrary transaction parts not only to specific values, but to certain ranges or subsets, and conditional these restrictions on timelocks being satisfied, external data being signed — or any arbitrary computation! Further, he could delegate these decisions to alternate (sets of) public keys.
Some specific examples of functionality this enables are:
- After a certain date, delegating funds controlled by a specific key (“emergency backup”)
- Signing the inputs and change of a transaction, but not outputs (“blank check”)
- Signing the ratio of one asset to another, but not the amounts (“open order”)
- Signing the transaction fee rate as a function of locktime (“noninteractive fee-bumping”)
In other words, Simplicity’s “universal sighash” is actually much more universal than just a sighash. See our previous blog post for more details.
A New Demo
In September of 2019, we created a demo transaction on a development branch of Elements to show that Simplicity could be used. At the time, Simplicity lacked many essential features, including an extensible jet encoding (or more than a couple jets), the disconnect combinator, or any tooling to create transactions. Further, Elements itself lacked any tools to create non-standard or experimental transactions, even without adding Simplicity to the mix. The Simplicity integration used for that demo was also a “proof-of-concept” integration, which had many rough edges and which we did not intend to bring to production.
Since then, we have made many improvements, especially to the tooling and library support for Elements. We have defined a new Partially Signed Elements Transaction format, based on Bitcoin’s PSBT2, and implemented it as part of the Elements RPC interface. We have a command-line tool, hal-elements, which can edit PSETs and other aspects of Elements transactions. hal-elements is a fork of hal, a very useful tool which exposes the same functionality for Bitcoin.
Using these tools and our Haskell implementation of Simplicity, we have created a new demo transaction which consists of generating a Taproot-Simplicity address, sending coins to it, and moving the coins back out using the universal sighash construction. You can replicate this transaction using the text of our ASCIICinema video which illustrates the whole process.
As highlighted in the video, there are still a couple of missing pieces which we hope to fill in over the coming months, but for developers not afraid to get their hands dirty, real non-trivial Simplicity transactions are at their fingertips!
This transaction is only the beginning of our push to bring Simplicity to Elements, and shortly thereafter, to the production Liquid Network. There are several more steps that need to be taken.
First, there are still some missing pieces in Simplicity itself. We need to add some more primitives to cover Elements transaction data, such as the Taproot annex, which are needed to create non-malleable Simplicity transactions. There are also anti-denial-of-service measures yet to be implemented, such as static analysis of resource usage and defining a mapping from these resources to a fee schedule. We need to define exact integration parameters and a deployment timeline.
More excitingly, we also intend to develop more and better tooling for Simplicity program development, based on rust-miniscript, hal-elements and minsc, which will allow users to quickly start working with Simplicity. This will give users a simple on-ramp to move from Bitcoin Script to Elements Script to Simplicity and back — without knowing a lick of Haskell! We will cover this in more detail in an upcoming blog post.
Our next immediate steps are to extend our existing Script system with new opcodes to complete our Taproot implementation for Elements, to integrate Simplicity with this implementation, and to complete the supporting tooling and libraries. Stay tuned!
To keep up with the latest Simplicity developments, make sure you sign up for our official newsletter or subscribe to the simplicity-dev mailing list. Also, be sure to join the Liquid Community group on Telegram to discuss all things Liquid.