Ever wondered what makes Bitcoin tick? This article aims to get you started on this journey even if you are not a Bitcoin Core dev!
No need to fret about technical details; we will break down the key concepts in bite-sized pieces, using clear language and relatable examples. You will get a taste of how covenants work, how OP codes enable them, and what this means for the future of Bitcoin. The aim here is not to tell you everything about covenants but to prepare you for the journey ahead. Hopefully this piece will serve as your parachute as you dive down. However, it cannot prevent you from screaming.
By the end, you will be equipped to start your own exploration. You might even find yourself contributing to exciting upgrades and shaping the future of this revolutionary technology! So, buckle up, put on your learning hat, and let’s dive in!
Treasure Chests
Imagine bitcoin like gleaming digital coins stashed away in treasure chests in a giant public vault called the blockchain. Each treasure chest in this vault has a unique id, like a numbered lockbox. Each treasure chest has a lock that can only unlocked by the correct key. Your wallet has the keys to the locks for all the treasure chests that contain your bitcoin. As long as you are the sole owner of the keys, only you can unlock the treasure chests.
When you send bitcoin to someone else, you typically do the following two steps:
- π Unlock: You unlock some treasure chest(s) you own using the key that only you have (scriptSig, the unlocking script)
- π Lock: You then create a new treasure chests, put some bitcoin in it that you need to transfer to someone else, say, Bob. You then put a new lock on this treasure chest, a lock that only Bob can open (scriptPubKey, the locking script)
- If you do not want to send all the bitcoin from the treasure chest you opened to someone else, you also create a new treasure chest (with the leftover balance) and put your own lock on it
These treasure chests are formally known as UTXOs (Unspent Transaction Outputs) in Bitcoin. If you want to learn about UTXOs without losing (more) hair (Udi excluded), please check out my piece Degenβs Guide to Bitcoin Transactions, Part 1.
Every Bitcoin transaction uses some existing treasure chests as “inputs” and creates new treasure chests from them called “outputs” that might potentially be owned by other people. We transfer bitcoin by unlocking the bitcoin from the sender address and re-locking them to the receiver address.
The Robots
Suppose, you couldn’t just directly lock or unlock the treasure chests. Instead, there are robots and they only understand a certain language. You need to instruct the robots in their language to unlock some coins from an address and lock them to a new address. The language used by these robots is called Script. Notice, the capital ‘S’, yes, not the generic word “script”, but “Script”. Wondering who the robots are? The Bitcoin miners! All changes to the Bitcoin ledger must happen through the robots who only honor instructions written in Script.
In essence, each lock on a treasure chest is programmable. Bitcoin’s Script language is like a toolkit for crafting custom locks and keys. It allows you to create different rules for how bitcoins can be spent. Here are some examples of different locking and unlocking scripts, illustrating their flexibility:
- Simple lock and key
- π Locking script: This chest has one lock that can only be opened with a matching key
- π Unlocking script: Here is the key (digital signature) that proves I own the matching lock
- Safe deposit box with multiple owners
- π Locking script: This chest requires 2 out of 3 keys to open, like a safe deposit box with multiple owners
- π Unlocking script: Here are 2 valid keys (digital signatures)
- Time capsule
- π Locking script: This chest can only be opened after a certain date, like a time capsule
- π Unlocking script: The specified date has now passed, so the condition is met
We can see that the locking and unlocking scripts are not fixed rules. The locking script sets the conditions under which the lock will open. The unlocking script tries to to fulfill those conditions. They can be customized to create different conditions. This flexibility enables a wide range of use cases beyond simple transactions.
Example
Now, lets revisit a Bitcoin transaction in a bit more detail. Suppose you want to spend coins from two of the treasure chests that you own. You want to send those coins to someone else, say Bob. Here are the steps involved in spending coins from the two treasure chests:
- Gathering the input treasure chests
- You might have more than two treasure chests and since you decided to spend from two of them you will have to choose two from the many you have
- Unlocking the first chest
- You take the unlocking script (π) for the first treasure chest and combine it with with the locking script (new π with Bob’s key) of the new treasure chest that you want to create. This creates a mini-program that checks:
- “Does the key for the first chest match the lock? If so, allow the coins to move to the new treasure chest”
- You take the unlocking script (π) for the first treasure chest and combine it with with the locking script (new π with Bob’s key) of the new treasure chest that you want to create. This creates a mini-program that checks:
- Unlocking the second chest
- You repeat the same process with the second treasure chest which creates a second mini-program:
- “Does the key for the second chest match the lock? If so, allow the coins to move to the new treasure chest”
- You repeat the same process with the second treasure chest which creates a second mini-program:
- Executing the transaction
- Notice that the transaction now has two mini-programs that were created above
- When this transaction is broadcast to the network, miners verify the transaction by running the mini-programs we mentioned above
- If everything checks out, the transaction is included in a block at which point all the nodes in the network run those two mini-programs thus indicating the transfer of coins from the two chests that were owned by you to a new treasure chest that is owned by Bob
OP codes (commands for robots)
Just like addition, subtraction, multiplication and division operations on a calculator allow you to put together complex calculations, the op codes in Script allow you to customize the conditions under which bitcoin in some treasure chest can be unlocked and locked.
Continuing our analogy, OP codes are like verbs in the Script language used by robots. Since they are verbs, they specify an action to be performed. Lets take a look at few examples:
- OP_ADD
- inputs: Two numbers x and y
- output: x + y
- OP_MIN
- inputs: Two numbers x and y
- output: Returns the smaller of x and y
You can see a list of Script’s op codes here.
Simplicity is Security
Remember our robot analogy? How they need to be given instructions in Script (using some op codes) to tell them what to do? Also, remember that these instructions are executed by all the nodes in the Bitcoin network. Now, what if, someone had an evil idea to make all the robots get stuck. How? They could just tell the robot to keep doing nothing repeatedly. That would be really bad. It would mean that all robots would get stuck and therefore no robots would be available to lock or unlock treasure chests. Especially, for something that aspires to replace fiat money, it would be outright diabolical!
Should the language even allow something like that or should we leave it to the good faith of people? Well, clearly the Script language needs to make sure that really bad scenarios can be avoided. Ideally, it would be impossible for someone to give instructions to robots that result in “bad” things. Well, one obvious one is that if the Script language does not allow loops at all, then no one can make a robot loop indefinitely. You get the idea.
While allowing loops would have added even more flexibility to the whole system because you could write more complex rules, it also would have meant that we would have to deal with all the pathological cases involving loops which would affect the overall design of the system and make things more complex.
You can’t secure what you don’t understand.
Bruce Schneier
Robot Workbench
Now, back to our robot analogy. When you give instructions to the robots, they do not just take the instructions and execute them. There is a very specific way in which they execute the instructions. All of them have a workbench called a “stack”. Let’s look at an example of what is it like to use a stack. Everyone has used a calculator before. When you want to multiply 2 by 3 and add 1 to the result, you do 2 * 3 which gives you 6, and then 6 + 1 gives you 7. Suppose you have a calculator that uses a stack. To do the same calculation, you would instead write 1 2 3 * +
Let’s see what the stack-based calculator does. Lets go step by step:
- We enter 1 2 3 * +
- The calculator starts reading from the left, it sees 1 and sees there is a space after 1, so it thinks 1 stands alone and therefore pushes it onto the stack
- Now, the calculator sees 2 and since there is a space after 2, it thinks 2 stands alone and pushes it on to the stack as well
- Same with 3. At this point, 3 is at the “top” of the stack and 1 is at the “bottom”
- Now, the calculator sees * and it knows that its an operator that takes two inputs. So it takes the top two elements from the stack, 3 and 2 respectively, and applies * to them, resulting in 6. It then replaces 3 and 2 with 6. Now 6 is on the top of the stack
- The calculator continues reading the input and sees + which is also an operator. Now, the stack has 1 and 6, so it applies + to them resulting in 7
- 7 is the only number left on the stack and there are no more characters to read in the input, so 7 must be the result!
We do not have any direct quotes from Satoshi stating why he chose Script but there are a few obvious reasons especially in the light of what we have discussed so far:
- Script is simple which means its easy to argue about what a program written in Script would do
- Script does not support loops which means a program written in Script can never get stuck forever
- Two separate programs in Script can be put together — without any changes — to create a new program. This is very handy because every Bitcoin transaction involves combining unlocking and locking scripts which are two separate mini-programs
Crossroads
Bitcoin has conquered its initial battle β establishing itself as a legitimate and valuable digital currency. Now, we face a new challenge: expanding its capabilities while maintaining its core strengths.
Two main approaches emerge:
- General-purpose extension: Introduce a new, powerful tool that can handle various tasks, but potentially with trade-offs in efficiency and security. Imagine it as a Swiss army knife β versatile but not optimized for every job.
- Targeted improvements: Add specific features designed for particular use cases, leading to increased efficiency and security for those specific tasks. Think of it as specialized tools β wrenches for bolts, screwdrivers for screws β each excels in its domain.
Let’s delve deeper into the nuances of each approach:
General-purpose extension:
- Pros:
- Opens up a wider range of potential applications for Bitcoin beyond simply payments.
- Provides developers with more flexibility and creativity in building on top of the network.
- Cons:
- Increased complexity could introduce new vulnerabilities or unexpected behavior.
- Implementing additional functionalities might come at the cost of transaction speed or energy consumption.
Targeted improvements:
- Pros:
- Enhances performance and security for specific use cases without altering the core functionality.
- Addresses specific needs without introducing unnecessary complexity or potential risks.
- Cons:
- Limits the scope of new applications and might not cater to every desired future use case.
- Requires careful selection of which specific features to add, potentially missing out on unforeseen valuable functionalities.
The choice between these approaches depends on the community’s vision for Bitcoin’s future. Do we prioritize versatility and adaptability, even if it comes with some inherent risks? Or do we focus on optimizing for specific, well-defined needs, ensuring optimal performance and security within those boundaries?
This is a crucial discussion that will shape the next chapter of Bitcoin’s evolution.
How is Bitcoin Upgraded?
You cannot just update Bitcoin by pressing an upgrade button or installing a new version of the software. Bitcoin is decentralized which means it works because a lot of people decide to run software which implements the same rules. To upgrade Bitcoin, you would have to convince an overwhelming majority to run a newer version of software. Since Bitcoin is money and is very well established reputationally, this process of trying to update Bitcoin is not straightforward and neither should it be.
Bitcoin protocol is a set of rules that governs how Bitcoin transactions are processed. When developers envision improvements, they propose changes to these rules, aiming to enhance scalability, security, or functionality. Since Bitcoin is decentralized, there is a peculiar way in which this an upgrade can happen.
If anyone has a new idea, they first discuss it with multiple people in places such as the bitcoin-devs mailing list or with the Bitcoin Core devs. This is just to get some initial feedback about how some people in the community feel about that idea. As a follow up to that, the person with the idea normally writes a proposal called a Bitcoin Improvement Proposal (BIP). This mentions the proposed changes in detail, why they need to be done, what new features will they bring and how can they be implemented safely, etc.
The BIP is shared with the Bitcoin community for discussion on various forums, mailing lists, developer meeting etc. Feedback is sought for developers, miners, users, etc. Based on the community feedback, the BIP might undergo several revisions to address concerns, incorporate suggestions or improve the overall design.
Once there is rough consensus within the Bitcoin community, miners may signal their intention to support the proposed change. This is often done by including a specific piece of data in the block they mine, indicating their readiness to adopt the new feature. For example, in case of Taproot, when 90% of the mining hashrate signaled support for it, activation was locked in and would happen automatically at a particular block height.
As part of their implementation, the developers specify the activation logic and when all the conditions such as miner signalling and any others are met, the soft fork is activated. This means that the new code path introduced by the soft fork can now be exercised by Bitcoin nodes.
Soft forks happen quite rarely. Generally speaking, there has only been one soft fork every 4 or so years in Bitcoin’s history. A lot of work is required to build a compelling case for a soft fork, develop consensus and then implement the actual change and finally its activation and deployment.
Covenants
Lets take a look at one of the popular proposed improvements to Bitcoin and see what could it mean to go with a general-purpose extension or a targeted improvement all the while staying open to both options.
Covenants can be used to create smart contracts on Bitcoin.
But, how do they offer such flexibility? Before we can dive into that, lets quickly go over the concept of a “hash” (not the food kind).
Hash
Hashes are like fingerprints for information. Hashes are created using a hashing function. It takes any sort of data, like a photo, document or even a song and transforms it into a unique, short code of fixed length. This code acts like a fingerprint because any change to the original data, even tiny edits, would completely change the resulting hash. So, its like a quick and reliable way to check if something has been altered or tampered with. An important property of hashes is that you cannot get the original data from the hash alone.
Promises, promises
Covenants allow you to embed a promise within a transaction, dictating specific conditions that must be met in future spending transactions. So, you can attach promises to some bitcoins and when you give those bitcoin to someone else, somehow, when they try to spend those bitcoin, the bitcoin network will make sure that the spending complies with the promises you specified.
Immediately, two things come to mind:
- Where are these promises stored?
- Remember the treasure chests? Consider the promises as scrolls attached to the locks of those treasure chests
- When are these promises enforced and by whom?
- Whenever a robot (a miner or a full node) tries to unlock a treasure chest, it notices the scroll and has to make sure that the spending is compliant with the promises mentioned in the scroll. If not, the robot will reject the transaction as invalid
Clearly, since these promises could be a whole variety of things, a lot is possible with covenants. Its no surprise that covenants can be used to implement smart contract like behavior on Bitcoin.
Now, with this high level understanding, lets try to go one step further. Remember, the robots unlock the treasure chests and create new chests with new locks. They do that by following the unlocking and locking instructions provided to them by a transaction. In case of a simple non-convenant transaction, the robots were doing the following:
- They expected you to provide the key for the lock on the treasure chest to prove that you are indeed the owner of those bitcoins
- They then unlocked the treasure chest and created a new treasure chest and put a new lock on the newly created treasure chest. That new lock belonged to the receiver of your bitcoin
This is all well and good. Since there were no scrolls attached to the locks on these treasure chests, the robots did not need to do anything extra.
But with covenants, we have scrolls on the locks. The robots have to look at the promises mentioned in the scrolls and enforce those promises. Whats more is that, in order to enforce those promises, they might have to look at different parts of the transaction that is currently trying to spend the bitcoins. For example, the scroll might say that you can only spend bitcoins in increments of 0.01 BTC, so the robots would have to look at the transaction and see if it is indeed creating a new treasure chest which only has 0.01 BTC.
Robot language (Script) upgrade
Remember though, that the robots only follow instructions. This means that the robots would need instructions (OP codes) that could help them look at different parts of a transaction that is trying to spend the bitcoins from a treasure chest with a lock and attached scrolls. The ability to do that is called “transaction introspection” because as a part of validating the transaction, they need to look at different parts of the transaction.
So, to reiterate, the promises can be specified by hashing (summarizing or fingerprinting) the parts of the transaction (such as output amounts). At validation time, the robots can use transaction introspection to pull in the relevant parts of the current transaction, stitch them together, hash them and see if that hash matches the one specified in the promise. If so, the current transaction upholds the promise and is valid. If not, it is an invalid transaction.
Now that we understand that covenants need a way to specify the restrictions, and a way to enforce them, there are several possible paths people have proposed and explored to implement covenants. Please keep in mind the earlier discussion we had about general-purpose extensions and targeted improvements as you explore the specific methods further.
Steven Roose and Jeremy Rubin have some excellent resources for exploring this deep rabbit hole further. Lets look at two possible approaches to covenants that have been discussed in the community.
OP_CAT
OP_CAT is an OP code that used to be part of Bitcoin Script language. It can take two pieces of information and stick them together. It was disabled in the Bitcoin codebase as a follow up to the Value Overflow Incident.
While OP_CAT is not directly related to covenants, it shines in transaction introspection, where robots stitch together transaction pieces. Its power lies in its generality – it simply merges two elements. This versatility, while enabling a vast range of tasks, comes at the cost of efficiency and introduces the need for thorough exploration and understanding of its effects and side effects through methods like fuzz testing. The unbounded nature of OP_CAT’s potential also means future OP code additions must be considered in the light of their interactions with OP_CAT.
There is a Bitcoin Improvement Proposal (BIP) to enable OP_CAT within a limited (tapscript) context. This limited context is better than enabling OP_CAT outright like it was before. However, still, a lot of testing, exploration and experimentation needs to be done in order to establish hard data about what adding OP_CAT back would entail.
Pros
- Re-enabling OP_CAT is a simple code change, its only 9 lines of code
- It has been deployed for years in Blockstream’s Elements
- OP_CAT is not brand new, it used to be in Bitcoin
- Due to its generic nature, OP_CAT can be combined with other OP codes to make things more flexible even if that is done somewhat inefficiently
Cons
- OP_CAT in combination with other OP codes could potentially lead to Turing Completeness which could pose the risk of undecidable programs in Script
- OP_CAT is flexible but not efficient
OP_CTV
OP_CTV, short for CHECKTEMPLATEVERIFY, is a proposed OP code for the Bitcoin scripting language. Its key strengths lie in its focus and efficiency which is why it is considered a targeted improvement. Compared to the broader, more open-ended OP_CAT, OP_CTV operates within a well-defined scope, specifically dedicated to enabling certain kinds of covenants. This makes it predictable, straightforward to understand, and relatively inexpensive to use in terms of transaction fees.
What is interesting is that utxos.org has a lot of great content on what OP_CTV can achieve. It even has code examples for several things. There is already a programming language called Sapio which can be used to design contracts that use OP_CTV.
One of the stated goals of OP_CTV BIP is to have a minimal impact on the existing Bitcoin codebase. Its designed to be safe and adaptable in the sense that we start with a “template” (the kind of covenanet OP_CTV enables) that we have more confidence about and as we gain more experience in the future and find new use cases that we think could work safely, we could then add new template types to support them.
My Personal Conclusion
I think that a lot of work needs to be done to try OP_CAT and compare it with other OP codes (not just OP_CTV). In addition to that, do get more confidence we need to do a lot of fuzz testing to get a better idea of the potential side effects of OP_CAT. Fuzz testing allows you to provide random inputs to a system to see if it breaks anything. In my opinion, discussing OP_CAT is great and this has truly been a great exercise for me to delve into the details of these OP codes and associated debates. But, in addition to the discussion, we need to practically build things with these different OP codes including OP_CAT. I know some of it has been on going. I have seen a ton of real code examples using OP_CTV but I think we need to see the same happen with OP_CAT. Hopefully, then, equipped with data, we will be in a much better position to decide.
Resources
Do not let me fool you though. The choice of improving Bitcoin and adding features to it is not a binary choice between OP_CAT and OP_CTV. There are so many other things to explore such as APO, Template Key, OP_TXHASH, OP_VAULT, OP_COV, OP_PUSHTXDATA, OP_CHECKSIGFROMSTACKVERIFY, TLUV, CATT, MATT and much much more.
Some excellent resources for reading further about the power of OP_CAT are part 1 and part 2 of Andrew Poelstra’s blog posts. Also, Rusty’s blog post about covenants.
An implementation of OP_CAT is available on Bitcoin Inquisition.
Here is a table from utxos.org that can take you much further along this wonderful journey:
Good luck and adios!