Consider the following question. Is it possible to use the Bitcoin blockchain for smart contracts? By this, I am not asking how far we can push the rather limited functionality of the native Bitcoin Script. Rather, can we write code for something like the Ethereum Virtual Machine (EVM), which is a Turing complete computation engine used by the Ethereum blockchain, and run on Bitcoin? This would enable a great variety of different uses for Bitcoin, such as ERC20 tokens, decentralized exchanges (DEX), NFTs, zk-STARKS, and any of the many different types of contract used by Ethereum.
While your first answer to the question above is probably “no, this is not possible”, there is one way in which it can be done with, albeit, a rather large caveat. Bitcoin transaction outputs incorporate a script, which is a simple stack-based programming language used to restrict how the coins can be spent. Output scripts starting with the
OP_RETURN opcode cannot be spent in any way. More than this, they are provably unspendable, so are ignored by Bitcoin nodes which do not even record such outputs in the UTXO set. The output quantity would likely to be set to zero, so as to avoid irretrievably destroying any bitcoin associated with the output. This means that we can include any arbitrary data following the
OP_RETURN to be recorded on the blockchain, which is simply ignored by all validating nodes. This is one of the uses of Bitcoin, as an immutable and decentralized store of data.
So, all we have to do, is to include smart contract code in
OP_RETURN outputs using whatever language, such as EVM, that we want. Ok, this does not form part of the protocol, so is ignored by Bitcoin nodes. Blockchain explorers would show the code as raw data, but would not interpret the contract language. This is a rather large caveat, and you may well reply that it is not really ‘running’ on Bitcoin and, really, all that we are doing is using the blockchain as data storage. However, it is interesting to consider and, even if due to reasons to be discussed below, it is not very efficient, these methods are implemented by the Omni Layer and also lead on to ideas such as Proof of Transfer.
While standard Bitcoin nodes would not attempt to validate such smart code, and simply ignore it, it would not be difficult to write applications which scan the blockchain for all
OP_RETURN statements. Any contained data would be handled as a transaction in the smart contract language, such as EVM. The application would store the ‘smart chain’ state and parse each of these contracts in turn, rejecting it if it is not valid and, otherwise, using it to update the state. This is as in figure 1, with
OP_RETURN Bitcoin outputs marked in green, which are then arranged in order. In the figure, arrows are drawn from each Bitcoin block header to the previous block, as well as from each valid smart contract to the previous one. Any which are not valid smart contracts with respect to the current state are marked in red, and ignored, with the remaining valid ones processed in sequence. Anyone with a copy of this application would be able to run it and see the current smart-chain state.
What we are doing here, is effectively building a new blockchain on top of Bitcoin with the ability to run a smart contract language. This means that we do not need to build a new consensus mechanism, such as the proof-of-work (PoW) used by Bitcoin. As such, we do not need any new miners for our chain, or a network of nodes validating it. All of that hard work is handled automatically by the Bitcoin network. All we need to do, is launch our application which can see a copy of the Bitcoin blockchain, and we can see the state and all transactions for our new ‘smart’ chain. The work of miners creating blocks and of nodes validating the chain is entirely separated out from the work of interpreting the smart contracts.
This idea begs the question: why don’t all smart chains separate out the process of validating the blockchain from interpreting the smart contract language. In fact, they could do this, but it would create some issues. Miners are rewarded by being paid out in a native chain asset, and contracts have to pay in this same asset in order to be included. At the very least, this effectively forces miners to process all transactions which can impact the validity of these payments. Anyone designing the protocol would likely want to include such an asset in the base protocol to ensure that miners are paid and give the chain a reasonable chance of success. In that case, we either include the smart contract ability in the protocol, or else it will be the case transactions in the native asset cannot be controlled, or affected in any way, by the smart contracts. In fact, this is exactly the situation with building a smart chain on top of Bitcoin as described here.
If we were to build a smart chain on Bitcoin as described, it is possible (and desirable) to allow our smart contracts to see the Bitcoin state. Since each contract exists in the Bitcoin blockchain, it has a specific Bitcoin state. To allow contracts which depend on the Bitcoin state — such as smart contracts which are triggered by a payment of bitcoin to a specific address — we should allow our smart contract language to have keywords referencing the Bitcoin state. However, the other way round is not possible. As Bitcoin nodes do not care about the contents of
OP_RETURN statements, the ‘smart chain’ state cannot impact Bitcoin transactions in any way. Still, we would have a chain supporting quite general smart contracts which can see the Bitcoin state, has the same security as Bitcoin, and without requiring building our own consensus mechanism or needing miners or validators.
The Omni Layer
The ideas discussed above have been implemented in the Omni Layer. This does not use a fully functional programming language such as EVM, but does allow custom digital assets to be represented, transacted and traded directly on the Bitcoin blockchain. The Omni layer was launched back in August 2013, along with its own asset, Omni. This pre-dates smart chains capable of handling custom assets, such as Ethereum which was officially launched in 2015. The Omni layer was initially popular for handling alternative digital assets. Notably, the Tether stablecoin pegged to the US dollar was launched on Omni but, now, mainly uses other platforms such as Ethereum. For documentation of the protocol, see the github page.
From the Omni Explorer, at the time of writing it can be seen that many Bitcoin blocks have no Omni layer transactions at all and, those that do, only contain a handful of transactions. These are mainly in Tether, with a few other altcoins also appearing (Omni token, MaidSafeCoin, …). This is to be expected since, for the reasons to be discussed below, representing transactions directly on Bitcoin is rather inefficient.
As an example, consider Bitcoin block 736873 which contains 4 Omni layer transactions, all in Tether. Choosing one of these, a transfer of 742 Tether, it is represented by a Bitcoin transaction with the Omni layer code in an OP_RETURN statement. Looking in a Bitcoin explorer, the transaction OP_RETURN output contains the following 20 bytes of data. I express this in hexadecimal and break it down into the fields specified by the omni protocol.
|Omni Layer Transaction|
|type||0000 (simple send)|
The quantity of coins is scaled by 100,000,000 so that the number in this example, which is 74,200,000,000 in decimal, represents 742 Tether. The receiving and sending addresses are given by the remaining two output addresses of the Bitcoin transaction.
Generalizing the Approach
There are some drawbacks of this idea, which are enough of a deal-breaker to make it impractical for many use cases.
- It would be very expensive to use, since our contracts would need to pay a large Bitcoin transaction fee.
- The Bitcoin blockchain has limited combined transaction size of 1MB per block. As all of our smart contracts are contained inside of Bitcoin transactions, it is bounded by the same limit and, since blocks also contain ordinary Bitcoin transactions, it is effectively an even stricter limit than this. In fact, as our chain would be competing with other Bitcoin transactions for space, it would clog up the blockchain and cause fees to increase even further.
- Bitcoin blocks are only produced on average once every 10 minutes, and our transactions need to wait for a new block to be produced in order to be included.
For the remainder of this post, I consider how we can overcome the first two of these points (spoiler: we will end up with a protocol along the lines of proof-of-transfer as used by the Stacks blockchain). The first idea is to bundle together multiple contracts into the same
OP_RETURN output. This will be a small efficiency improvement, but the entire contents of each smart contract is still being included and taking up space in the Bitcoin blockchain, incurring large costs. There is no way to avoid this, other than to group transactions together and severely compress them to only use up a small amount of valuable blockchain space. Reversible compression algorithms are not going to be able to attain anything like the required space savings. Instead, we need to use something like a hash function. This converts an arbitrarily large block of data into a fixed size, such as 256 bits for the SHA256 hash. Second preimage resistance means that it uniquely identifies the original data, although we cannot invert the hash to recover it. Instead, we need to have separate access to the block, and validate it by checking that it has the correct hash value.
The proposal is now as shown in figure 2. The hash value of a block of smart contracts is included in a Bitcoin
OP_RETURN output. Our application now needs to have access to all such blocks. It can then parse the Bitcoin blockchain checking for
OP_RETURN statements including their hashes, which are referred to as commitment transactions. Then, it can arrange these blocked smart contracts in order and parse them in sequence, as previously suggested.
We have reduced the large data requirements to only including a relatively small number of hashes in the Bitcoin blockchain, solving the problems identified above. However, at the same time, this introduces some new significant issues. First, it requires people to provide the service of collecting together individual contracts that users have submitted, block them together, and submit Bitcoin transactions containing their hash. There needs to be some incentive to do this, and to cover the cost of the Bitcoin transaction fees. A solution is for our smart contract language to include a native asset. Users can then pay a fee to be paid to the person creating the blocks. Once the total transaction fee value of submitted contracts exceeds the Bitcoin transaction fee, people will be incentivized to create the transaction blocks and add the commitment transactions to the Bitcoin blockchain.
The next issue is that users of our smart contract platform will need access to the blocks. They cannot be constructed from the Bitcoin blockchain alone, so we will have to build a separate network in order to share the blocks. The main issue, however, is that the updated proposal loses immutability. For example, someone could submit a commitment transaction to Bitcoin, but fail to send the associated smart contract block to the network. The result is that the commitment transaction would not contain a hash of any publicly known block. So, it would be rejected and the transactions would not be included in our smart contract state. If they later submitted the block, then it would be belatedly inserted in the smart contract chain, requiring the state to be recomputed and any later transactions could now become invalid. This is totally unacceptable since, at any time, the entire smart contract chain could be reorganised.
A partial fix for the immutability problem is for each block to include a reference to the previous one. This stops people from ‘inserting’ a new block into the chain. The reference could either be included in the header data for the block of transactions or, alternatively, in the data of the commitment transaction itself. The latter approach is a bit more expensive, since it takes up more Bitcoin blockchain space, but it also simplifies things by making it possible to construct the chain of block hashes from parsing the Bitcoin blockchain alone.
Now, if someone adds a commitment transaction to the Bitcoin chain, but does not submit a corresponding smart contract block to the network, subsequent block-builders are going to ignore this and build on an earlier block instead. If they were to later submit their block, it would no longer be a part of the main chain. However, we are still vulnerable to large reorganisations of the chain. Someone could add an entire sequence of commitment transactions, each of them building on their earlier ones. If they did not also submit their blocks to the network, the result would be a fork. There would be a public branch, and also the private one only viewable to the rogue block builder. If he later submits his blocks, then there could be a reorganisation. The fix to this is to make valid commitment transactions expensive to create by requiring them to spend a significant amount of bitcoin on top of the transaction fee. The block reward paid in the native smart contract asset would need to be large enough to cover this expense, so that people are still incentivized to create blocks, so long as they are a part of the publicly recognised chain.
The proposed method, with the efficiency modifications, consists of linked commitment transactions in the Bitcoin blockchain, each containing a hash of a block of smart contracts and spending a quantity of bitcoin, to be compensated by a block reward and transaction fees paid in the native smart-chain asset. This is reinventing the proof-of-transfer protocol, already implemented by the Stacks blockchain and described in an earlier post.