Hey @Ajian, I gave translating your piece into English a shot. Mind having a look and letting me know if it captures the essence of your original work? Your feedback would be awesome. Thanks!
Understanding the Programmability of CKB through Bitcoin Application Programming
Abstract
Understanding the programmability of a system requires us to identify the structural features of that system. Exploring the application programming based on Bitcoin script helps us understand the fundamental structure of CKB Cells and its programming paradigm. Furthermore, it allows us to decompose the programming components of CKB into appropriate parts and helps us understand the programmability benefits brought by each part.
1. Introduction
“Programmability” is a dimension commonly used when comparing blockchain systems. However, there is often a lack of consensus on how programmability is described. A common expression is, “XX blockchain supports Turing-complete programming languages,” or “XX blockchain supports general-purpose programming,” suggesting that the “XX blockchain” has great programmability. These statements have some truth: systems that support Turing-complete programming are typically easier to program than those that do not. However, the structural characteristics of smart contract systems have multiple aspects, and this statement only addresses one aspect. Therefore, by this statement itself we can’t gain a deep understanding towards programmability. It’s neither a guidance for developers, nor a useful tool to distinguish scams for average users.
The structural features of smart contract systems include:
- The basic form of state expression (accounts vs. transaction outputs)
- Whether arbitrary computations can be programmed (“Turing completeness” refers to this aspect)
- Whether the execution process create new meaningful data or only output boolean values (computation vs. verification)
- Whether additional state can be recorded within the contract
- Whether a contract can access the state of another contract during execution
So, apart from the question of “whether arbitrary computations can be programmed”, there are at least four other aspects that affect the programmability of a smart contract system. It can even be argued that these other aspects are more important because they, at a deeper level, determine what is easy to achieve, what is difficult to achieve, what is an economical implementation, and what is an inefficient implementation.
For example, Ethereum is usually seen as a system with great programmability. However, Ethereum’s basic form of state representation is accounts, for which it is challenging to program point-to-point contracts (e.g., payment channels, one-on-one betting contracts) on Ethereum.
While it’s not impossible to implement these on Ethereum, it can be a cumbersome process. There has been multiple attempts to implement payment channels/state channels, as well as extensive theoretical discussion on the topic. However, these projects appear to be less active today, and this cannot be solely attributed to a lack of developer effort. Currently, many active projects on Ethereum adopt the “Pool-based” model rather than the “point-to-point contract” model, and this is not by chance. Likewise, while people may be satisfied with Ethereum’s programmability today, if one were to aim for “account abstraction” ( generalization of wallets), Ethereum’s account model is inherently limited.
To explore the programmability of CKB, we also need to understand the structural features of CKB’s smart contract system in the above aspects. We already know that CKB is able to program arbitrary computations, record additional state within contracts, and allows one contract to access the state of another contract during execution. However, fundamentally different from Ethereum, CKB’s basic form of state representation is transaction outputs, referred to as “Cells.” Therefore, an understanding of Ethereum’s smart contract system and its contract instances would not help us comprehend how CKB achieves these structural features or recognize CKB’s programmability.
Fortunately, smart contracts on Bitcoin seem to provide the best foundation for understanding the programmability of CKB. This is not only because the basic form of state representation on Bitcoin is also the output of transactions (referred to as “UTXO”), but also because, with the concept of “covenants” discussed in the Bitcoin community, we can understand how CKB possesses the aforementioned structural features. We can appropriately break down the final effect into several parts and systematically identify the programmability enhancements each of them brings.
2. CKB v.s. BTC:What’s more?
(I)Basic Structure
As the basic form of state representation in Bitcoin, Bitcoin’s UTXO (Unspent Transaction Output) has two fields:
- Amount, measured in satoshis, represents the value of Bitcoin held by the UTXO.
- ScriptPubKey, also known as the locking script, represents the conditions to spend this amount, which is the smart contract program that sets the conditions to unlock these satoshis.
Compared to later smart contract systems, the Bitcoin script is quite limited:
- It does not allow arbitrary computation; it only supports a few practical computations for verification, such as signature, hash preimage, and time checks.
Note: Currently an innovation called “BitVM” challenges our stereotypes. The basic idea is that, as we can break down any arbitrary computations into ‘NAND gates’, which can be done with Bitcoin script, it is possible to produce aggrement towards any arbitrary computations between two parties. However, again, as the setup of this kind of contracts may cost amazing time, computational resources and storage. it should be seen as ‘impractical’, at least now.
- It does not allow the recording of additional state within the contract (for example, you cannot use a script to limit the maximum amount/spending limit for a single transaction, or embed a token within it).
- It also does not allow access to another contract’s state during execution (each script exists independently, with no interdependence).
While the Bitcoin script may be limited, it does not lack the ability to create impressive applications. It serves as the foundation for exploring the programmability of CKB, and in later sections I will introduce two examples of Bitcoin script programming.
Comparatively, CKB’s basic form of state representation is called a “Cell” and has four fields:
- Capacity, similar to UTXO’s amount, represents the size in bytes that this Cell can occupy.
- Lock Script, similar to UTXO’s ScriptPubKey, defines ownership of this Cell. Only when the provided data can pass the Lock Script, then it can “refresh” this Cell ( in other words, release its capacity and use it to forge new Cells).
- Data, arbitrary data, with its size limited by Capacity.
- Type Script, an optional script used to set conditions for updating Data.
Additionally, Lock Script and Type Script can perform arbitrary computation. You can program any signature verification algorithm or preimage checks for any hash algorithm, and so on.
It’s apparent that Cells offer an enhancement in programmability compared to UTXOs:
- Cells can perform arbitrary computation, rather than being limited to specific computations, making ownership verification more flexible.
- Data and Type Script fields, enable Cells to record additional state, allowing Cells to carry so-called “User-Defined Tokens (UDTs).”
Combining with the structure of “transaction output” inherent to Cells, these two points alone provide significant benefits. However, based on the above description, we still don’t know how Cells achieve “a contract can access the state of another contract during execution”. Hence, we need to delve into a concept that the Bitcoin community has been discussing for a long time: “Covenants.”
(II)Covenants & Introspection
The purpose of covenant is to limit where the money can be spent. In current Bitcoin system (which has not yet deployed any proposed covenants), once funds are unlocked, they can be spent anywhere (paid to any ScriptPubKey). However, the idea behind covenants is that they can be used to restrict spending to certain destinations. For example, a specific UTXO (Unspent Transaction Output) can only be spent for a specific transaction, so even if someone can provide a signature for that UTXO, where it can be spent has already been determined by that transaction. This feature may seem a bit strange, but it can lead to some interesting applications, which will be discussed in a dedicated section later. Importantly, it is a key element in further our understanding of CKB’s programmability.
Rusty Russell correctly points out that covenants can be understood as a construction to allow introspection. In other words, when a UTXO A is spent by a transaction B, the script program can read parts (or all) of transaction B and then check if they match the parameters required by the script. For example, the program can verify if the ScriptPubKey of the first output in transaction B matches the one required by the script of UTXO A (which is the basic idea of covenant).
Now you might realize that with full introspection capabilities, a transaction’s input can read the state of another input within the same transaction, which is the feature we mentioned earlier, “a contract can access the state of another contract during execution.” In fact, CKB Cells has been designed to operate in this manner.
Based on this, we can categorize this complete introspection into four scenarios:
- Lock Script reads other Lock Scripts (both input and output)
- Lock Script reads other Type Scripts and Data (both input and output)
- Type Script reads other Lock Scripts (both input and output)
- Type Script reads other Type Scripts and Data (both input and output)
This allows us to analyze the role of each introspection with different application scenarios assuming the roles of Lock Script and Type Script and understand the programmability enhancement brought by each part.
In the following two sections, we will learn about the current Bitcoin script programming (before the proposed covenants) and the expected features that the covenants could provide. This will help us gain a concrete understanding of how CKB Cells has been programmed to be better.
3. Bitcoin Script Programming
I will use “Lightning Network” and “Discreet Log Contracts (DLC)” as examples of Bitcoin script-based application programming. Before delving into these examples, let’s first explore two key concepts.
(I)OP_IF & “Commitment Transaction”
The first concept is the use of flow control opcodes in Bitcoin scripts, such as OP_IF
and OP_ELSE
. These opcodes are similar to IF
statements in programming, allowing different statements to be executed based on different inputs. In the context of Bitcoin scripts, this means that we can define multiple spending paths for funds, and when combined with the time lock feature, it allows us to allocate priority to different actions.
Taking the well-known “Hash Time-Locked Contract (HTLC)” as our example, in simple terms, this script can be explained as follows:
Either Bob can reveal the pre-image of a specific hash value H and provide his signature to spend the funds,
or Alice can spend the funds with her signature after a period of time T has passed.
This “either … or …” effect is achieved through flow control opcodes.
The most prominent advantage of HTLC is that it can bundle multiple operations together, achieving Atomicity.
For example, if Alice wants to exchange BTC for CKB with Bob, Bob can first provide a hash value and create an HTLC on the Nervos Network. Then, Alice can create an HTLC on the Bitcoin network using the same hash value.
Either Bob claim the BTC that Alice has provided on the Bitcoin network while revealing the pre-image, allowing Alice to take the CKB on the Nervos Network.
Or, Bob doesn’t reveal the pre-image, both contracts expire, both Alice and Bob can retrieve the funds they initially committed.
After the activation of the Taproot soft fork, the feature of multiple spending paths is further enhanced with the introduction of MAST (Merkle Abstract Syntax Tree). We can transform a spending path into a leaf on the Merkle tree, and each leaf is independent. As a result, we no longer need to use flow control opcodes like before. Moreover, since revealing one path doesn’t expose the other paths, we can add more spending paths to an output without worrying about economical concerns.
The second concept is “commitment transactions.” The idea behind commitment transactions is that, in some situations, a valid Bitcoin transaction can be considered real and binding even if it hasn’t received confirmations on the blockchain.
For example, if Alice and Bob jointly own a UTXO that requires signatures from both of them to spend (which we called ‘2-of-2 multisig output’), Alice can construct a transaction to spend it. In this transaction, she can transfer 60% of the value to Bob and transfer the remaining 40% to herself. Alice signs this transaction and sends it to Bob. From Bob’s perspective, he doesn’t need to broadcast this transaction to the Bitcoin network, nor does it require blockchain confirmations. The payment in this transaction is still real and trustworthy. This is because Alice cannot spend this UTXO on her own (therefore no double spending), and once Alice provided a valid signature, Bob can add his own signature at any time and broadcast the transaction to settle the payment. In other words, Alice has provided Bob with a “trusted commitment” through this valid (off-chain) transaction.
Commitment transactions are a fundamental concept in Bitcoin application programming. As previously mentioned, Bitcoin contracts are verification-based, stateless, and do not allow for cross-contract interactions. However, if a contract doesn’t maintain state, where is that state stored? and how does the contract safely progress (change state)? Commitment transactions provide a straightforward answer: a contract’s state can be expressed in the form of transactions, allowing the contract participants to maintain their own state off-chain, without the need to expose it on the blockchain. The issue of state changes in the contract can be transformed into the problem of how to securely update commitment transactions.
Additionally, if there is concern about the potential risks of entering into such contracts (e.g., a contract that requires both parties to sign for spending has the risk of the other party not responding and causing a deadlock), one can simply pre-generate the commitment transaction to spend that contract and obtain the necessary signatures. This can mitigate the risk and eliminate the need for trust in other participants.
(II)Lightning Channels & Lightning Network
A Lightning Channel is a one-to-one contract where both parties can make unlimited off-chain transactions between each other without boardcasting any single payment to the network, as you might expect, it relies on commitment transactions.
In the previous section on “commitment transactions,” we have introduced a type of payment channel. However, this channel, which uses a simple 2-of-2 multi-signature contract, can only facilitate one-way payments. In other words, either Alice pays Bob continuously or Bob pays Alice continuously, until their balances in the contract are exhausted. If the payments are bi-directional, after a state update, one party might have a lower balance compared to the previous state, but they still possess a commitment transaction signed by the other party —— Is there a way to prevent them from broadcasting the old commitment transactions and ensure they can only broadcast the latest commitment transaction? Currently, Lightning Channel solves this problem with a solution called “LN-Penalty.”
Now, let’s assume Alice and Bob each have 5 BTC in a channel. Alice wants to pay 1 BTC to Bob, so she signs a commitment transaction like this and sends it to Bob:
Input #0, 10 BTC:
Alice-Bob 2-of-2 multi-signature output (i.e., channel contract)
Output #0, 4 BTC:
Alice single signature
Output #1, 6 BTC:
Either
Alice-Bob temporary joint public key #1 single signature
or
Time lock T1, Bob single signature
Bob also signs a commitment transaction (corresponding to the transaction described above) and sends it to Alice:
Input #0, 10 BTC:
Alice-Bob 2-of-2 multi-signature output (i.e., channel contract)
Output #0, 6 BTC:
Bob single signature
Output #1, 4 BTC:
Either
Bob-Alice temporary joint public key #1 single signature
or
Time lock T1, Alice single signature
The trick to how Lightning Network can “revoke” old states lies in the “temporary joint public key,” which is generated using one’s own public key and the public key provided by the other party. For example, the Alice-Bob temporary joint public key is created by Alice using her own public key and Bob’s provided public key, each multiplied by a hash value and then added together. This temporary public key is generated in a way that no one knows its private key at the time of creation. However, if Bob shares the private key of the public key he provided, Alice can then calculate the private key of this temporary joint public key.
When the next time both parties want to update the channel’s state (initiate a payment), they exchange the private keys of the temporary public keys they each got in the previous round. This way, participants no longer dare to broadcast the commitment transaction they received in the previous round: the commitment transaction output allocating value for local party has two spending paths, and the private key for the temporary public key path is now known to remote party. So, if an old commitment transaction is boardcasted, remote party can immediately use the private key of joint temporary public key to claim all the funds for this output.—— This is the meaning of “LN-Penalty.”
To be more specific, the sequence of interactions goes as follows: the party initiating the payment first requests a new temporary public key from the counterparty. They then construct a new commitment transaction and hand it over to the counterparty. The party who receives the commitment transaction exposes the private key of the temporary public key they provided in the previous round. This sequence of interactions ensures that participants always receive the new commitment transaction before invalidating the commitment transaction they received in the previous round, making the process trustless.
In summary, the key design elements of the Lightning Channel are as follows:
- Both parties always use commitment transactions to represent the internal state of the contract and express payments through changes in amounts.
- Commitment transactions always spend the same input (requiring both parties to provide signatures for the input), making all commitment transactions mutually competitive. Ultimately, only one of them gets confirmed on the blockchain.
- The two participants do not sign the same commitment transaction (even though they come in pairs). They each sign transactions that are more favorable to themselves, in other words, the commitment transactions received by each participant are disadvantageous to them.
- This “disadvantageous” is reflected in the outputs that allocate value to themselves (local party), which have two unlocking paths: one path can be unlocked with their own signature but requires a waiting period, while the other path involves the counterparty’s public key and is protected only if their own temporary private key remains undisclosed.
- In each payment, both parties exchange the temporary private key used by the other party in the previous round with a new commitment transaction. This way, the party that has given away the temporary private key no longer dares to broadcast the old commitment transaction, effectively “revoking” the previous commitment transaction and updating the contract’s state. (In reality, these commitment transactions are all valid and can be broadcast on the blockchain, but participants refrain from doing so due to penalties.)
- Either party can exit the contract at any time by using the commitment transaction signed by the counterparty. However, if both parties are willing to cooperate, they can sign a new transaction that allows both of them to immediately retrieve their respective funds.
Finally, since commitment transactions can also include HTLCs, the lightning channel can also facilitate payments. Suppose Alice can find a path in the interconnected lightning channels to reach Daniel, she can make trustless multi-hop payments without having to open a direct channel with Daniel. This is the essence of the Lightning Network.
Alice -- HTLC --> Bob -- HTLC --> Carol -- HTLC --> Daniel
Alice <-- Pre-image -- Bob <-- Pre-image -- Carol <-- Pre-image -- Daniel
When Alice discovers such a path and wishes to pay Daniel, she requests a hash value from Daniel, using it to construct an HTLC to Bob and prompts Bob to forward a message to Carol while providing the same HTLC. The message to Carol then instructs her to forward the message to Daniel and provide the same HTLC. When the message reaches Daniel, he reveals the pre-image, thereby obtaining the value of the HTLC and updating the contract state. Carol follows the same process, receiving Bob’s payment and updating the channel state. Finally, Bob reveals the pre-image to Alice and updates the state. Due to the characteristics of HTLCs, this chain of payments either succeeds together or fails together, making it trustless.
The Lightning Network is composed of a network of channels, with each channel (contract) being independent of the others. This means that Alice only needs to be aware of what is happening within her channel with Bob and doesn’t need to concern herself with how many interactions have occurred in other people’s channels or what currencies have been used in those interactions. She doesn’t even need to know whether they have actually used the channel or not.
The scalability of the Lightning Network is evident in the fact that the payment speed within a single Lightning channel is only limited by the hardware resources of the involved parties, and also in the decentralized storage of channel states, allowing individual nodes to achieve the maximum leverage with minimal cost.
(III)Discreet Log Contract
Discreet Log Contract (DLC) employs a cryptographic technique called “adaptor signature” that allows Bitcoin scripts to programmatically create financial contracts dependent on external events.
Adaptor signatures make it so that a signature becomes valid only when a private key is added. Taking Schnorr signatures as an example, the standard form of a Schnorr signature is (R, s)
, where:
R = r.G
# The nonce value "r" used in the signature is multiplied by an elliptic curve point, also referred to as the public key of "r"
s = r + Hash(R || m || P) * p
# p = sighning private key,P = singing public key
Verifying a signature is verifying s.G = r.G + Hash(R || m || P) * p.G = R + Hash(R || m || P) * PK
Suppose I provide a pair of data, (R, s')
, where:
R = R1 + R2 = r1.G + r2.G
s' = r1 + Hash(R || m || P) * p
Clearly, this is not a valid Schnorr signature, and it won’t pass the verification formula. However, I can prove to the verifier that, if they know the private key r2
for R2
, it can be turned into a valid signature:
s'.G + R2 = R1 + Hash(R || m || P) * P + R2 = R + Hash(R || m || P) * P
Adaptor signatures make the validity of a signature dependent on a secret, and it is verifiable. But what does this have to do with financial contracts?
Suppose Alice and Bob want to bet on the outcome of a soccer match. Alice and Bob are betting on different teams, say the Green Magicians and the Red Arrows, with a wager of 1 BTC. Additionally, a sports analysis website, Carol, promises to publish a signature s_c_i
on the outcome with a nonce R_c
when the match result is revealed.
There are three possible outcomes (and thus three possible signatures from Carol):
-Green Magicians win, and Alice wins 1 BTC.
-Red Arrows win, and Bob wins 1 BTC.
-A draw results in a refund of their funds to both parties.
For this purpose, they create a commitment transaction for each possible outcome. For example, the commitment transaction they create for the first outcome is as follows:
Input #0, 2 BTC:
Alice-Bob 2-of-2 multi-signature output (i.e., the betting contract)
Output #0, 2 BTC:
Alice single signature
However, the signatures that Alice and Bob create for this transaction are not in the form of (R, s)
but are adaptor signatures (R, s')
. In other words, the signatures provided by both parties cannot be directly used to unlock this contract; A secret value must be revealed. This secret value is precisely the private key of s_c_1.G
, which is Carol’s signature! Because Carol’s signature nonce value is already known (it’s R_c
), s_c_1.G
can be constructed (s_c_1.G = R_c + Hash(R_c || 'Green Magicians win' || PK_c) * PK_c
).
When the result is revealed, let’s say the Green Magicians won, Carol will then publish the signature (R_c, s_c_1)
. In this case, both Alice and Bob can complete their opponent’s adaptor signature, add their own signature, making the transaction valid, and then broadcast it to the network, triggering the settlement effect. If the Green Magicians did not win, Carol will not publish s_c_1
, and this commitment transaction cannot become a valid transaction.
Following the same logic, the other two transactions follow the same pattern. This way, Alice and Bob make the execution of this contract dependent on external events (more precisely, relying on an oracle of an external event in the form of a signature) without needing to trust the other party. Various types of financial contracts, including futures and options, can be implemented using this approach.
Compared to other forms of implementation, the most significant feature of Discreet Log Contracts (DLC) is its privacy:
- Alice and Bob do not need to inform Carol that they are using Carol’s data, and this has no impact on the execution of the contract.
- On-chain observers, including Carol, cannot deduce from Alice and Bob’s contract execution transactions which website’s services they are using. They can’t even determine whether their contract is a betting contract (as opposed to a Lightning Network channel contract).
4. Applications for Covenants
(I) OP_CTV & Congestion Control
The Bitcoin community developers have proposed various ideas that can be classified as covenant proposals. One of the most prominent proposals at the moment is OP_CHECKTEMPLATEVERIFY (OP_CTV
). The concept is relatively simple but maintains significant flexibility, making it popular within the Bitcoin community that values simplicity. The idea behind OP_CTV
is to commit a hash value to a script, making the transactions specified by this hash value to be the only ones able to spend this fund. This hash value commits to the transactions’ outputs and most fields but does not commit to the transactions’ inputs; it only commits to the quantity of inputs.
“Congestion Control” is a great example that showcases the features of OP_CTV
. Its primary use case is to assist a large number of users in moving from an exchange (a trusted environment) to a payment pool. Since this payment pool utilizes OP_CTV
to plan for future spending, it can ensure that users can exit this payment pool in a trustless manner without assistance from anyone. Furthermore, because this payment pool is represented as a single UTXO, it avoids the need to pay high fees when on-chain transaction demand is high (reducing from n outputs to just 1 output). Users within the pool can also exit the pool at their convenience.
Suppose Alice, Bob, and Carol want to withdraw 5 BTC, 3 BTC, and 2 BTC, respectively, from the exchange, the exchange can create an output of 10 BTC with three OP_CTV
branches. If Alice wants to make a withdrawal, she can use branch 1. The OP_CTV
in branch 1 commits to a transaction represented by a hash, which will create two outputs. One output allocates 5 BTC to Alice, and the other output is another payment pool that uses OP_CTV
to commit to a transaction, allowing Bob to withdraw 3 BTC and sending the remaining 2 BTC to Carol.
When Bob or Carol want to make a withdrawal, the process is similar. They can only use transactions that pass the corresponding OP_CTV
checks, meaning they can only withdraw the specific amount allocated to them and cannot withdraw arbitrary amounts. The remaining funds will again enter a payment pool locked with OP_CTV
, ensuring that regardless of the withdrawal order of users, the remaining users can trustlessly exit the pool.
In abstract terms, OP_CTV
serves the purpose of planning paths for the contract towards the end of its lifecycle, ensuring that the payment pool contract, regardless of the path it follows and the state it reaches, maintains the property of trustless exits.
This type of OP_CTV
has another interesting application: “silent one-way payment channel.” Suppose Alice creates such a payment pool and ensures that funds can be trustlessly withdrawn to an output with the following script:
Either Alice and Bob can spend it together, or after a certain period, Alice can spend it alone.
If Alice doesn’t reveal it to Bob, he won’t know about the existence of this output. Once Alice reveals it to Bob, he can treat this output as a time-bound one-way payment channel, and Alice can use the funds in it to pay Bob immediately without waiting for blockchain confirmations. Bob just needs to broadcast those commitment transaction (provided by Alice) to the blockchain before Alice can spend the fund alone.
(II)OP_Vault & Vaults
OP_VAULT
is a covenant proposed for creating “vault contracts.”
Vault contracts aim to provide a more secure and advanced form of self-custody. While current multi-signature contracts eliminate the single point of failure of a single private key, if an attacker manages to obtain the threshold number of private keys, the wallet’s owner is defenseless. Vaults seek to impose a one-time spending limit on funds. As well as a an enforced waiting period when withdrawing through the regular path. During this waiting period, the withdrawal can be interrupted by emergency recovery wallet operations. With such a contract, even if the wallet is compromised, the owner can initiate a counteraction (using the emergency recovery branch).
In theory, OP_CTV
can also be used to create such contracts, but there are several inconveniences, including one related to transaction fees. When a commitment transaction is made, it also commits the transaction fees it will pay. Given the long time intervals between setting up the contract and making withdrawals for such contracts’ use cases, it’s almost impossible to predict the appropriate fees. While OP_CTV
doesn’t restrict inputs, adding more inputs to increase the transaction fee is impractical because the provided inputs would all be used for fees. Another method is Child Pays for Parent (CPFP), where the funds spent in the withdrawal are used to provide fees in a new transaction. Additionally, using OP_CTV
means these vault contracts cannot be used for batch withdrawals (and, of course, batch recoveries).
The OP_VAULT
proposal aims to address these issues by introducing new opcodes, namely OP_VAULT
and OP_UNVAULT
.
OP_UNVAULT
is designed for batch recoveries, which we won’t discuss right now. The action of OP_VAULT
is as follows: when placed on a branch of the script tree, it can be used to commit an operable opcode (e.g., OP_CTV
) without specifying the exact parameters. When spending from this branch, the transaction can provide specific parameters but cannot modify other branches. As a result, it doesn’t need to pre-commit fees and can set fees when spending this branch. Assuming this branch also has a time lock, it will enforce a time lock. Finally, because it can only change the branch it is in, other branches on the script tree (including emergency recovery branches) will not be altered, allowing us to interrupt such withdrawal operations.
In addition, two more points are worth mentioning.
(1) The action of the OP_VAULT
opcode is similar to another covenant proposal: [OP_TLUV](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019419.html).
Jeremy Rubin correctly pointed out that this, to some extent, has introduced the concept of "computation.” OP_TLUV
/OP_VAULT
first commits an opcode to allow users to pass parameters for that opcode through a new transaction, thus updating the entire script tree leaf. This goes beyond “validating incoming data based on certain conditions” to “generating new meaningful data based on incoming data,” even though the range of computation enabled is relatively limited.
(2) The full OP_VAULT
proposal also leverages some mempool policy proposals, such as v3 transaction format, to achieve better results. This reminds us that the meaning of “programming” can be broader than we might imagine. (A similar example is Open Transaction in the Nervos Network.)
5. Understand CKB
In the two previous sections, we discussed how we can program interesting applications using scripts in a more restricted structure (Bitcoin UTXO). We also introduced proposals aimed at adding introspection capabilities to this structure.
While UTXO has the ability to program these applications, you can also easily identify their drawbacks or areas for optimization, such as:
- In LN-Penalty, channel participants must store every past commitment transaction and the corresponding penalty secret values to defend against possible cheats from their counterpart. This imposes a storage burden. If there were a mechanism to ensure that only the latest commitment transaction is effective, and older commitment transactions are not, it would eliminate this burden and protect nodes from accidentally penalizing themselves due to failures.
- In DLC, assuming there are many possible outcomes for an event, there are numerous signatures that both parties must generate in advance and provide to each other, which is also a significant burden. Additionally, the revenue from DLC contracts is directly tied to public keys, making the position not easily transferable. Is there a way to transfer the position of the contract?
In fact, the Bitcoin community has already provided answers to these issues, primarily related to a Sighash proposal (BIP-118 AnyPrevOut). However, if we are programming on CKB, BIP-118 is essentially available now (using CKB’s introspection and the ability to selectively verify signatures to simulate this Sighash tag).
By learning Bitcoin programming, we not only understand how to program in the “transaction output” format (what CKB can program), but also how to improve these applications (how we can use CKB’s capabilities to enhance them when programming on CKB). For CKB developers, Bitcoin script programming can be considered as a learning resource and even a shortcut.
Next, we will analyze the programmability of each module in CKB programming, without considering introspection for now.
(I)Lock Script With Arbitrary Computation Programmability
As mentioned earlier, UTXOs cannot perform arbitrary computation. However, a Lock Script can, which means that it can program everything based on UTXOs (before deploying covenants), including but not limited to Lightning channels and DLCs.
Furthermore, this capability to verify arbitrary computations allows Lock Scripts to support more and more flexible authentication methods compared to UTXOs. For instance, you can implement a Lightning channel on CKB with one party using an ECDSA signature and the other party using an RSA signature.
Actually, this is one of the areas that led people to explore on CKB from the very beginning: utilizing this flexible authentication capability in user self-custody, thereby achieving the so-called "account abstraction” —— flexible authorization and control over transaction validity, as well as recovery of control, with almost no restrictions. In principle, this combines “multiple spending paths” and “arbitrary authentication method.” Examples of the implementations include joyid wallet and UniPass.
Moreover, Lock Scripts can also implement the eltoo proposal, enabling Lightning channels to only need to keep the latest commitment transaction (in fact, eltoo can simplify all point-to-point contracts).
(II)Type Script With Arbitrary Computation Programmability
As mentioned earlier, one major use case for Type Scripts is to program User-Defined Tokens (UDTs). Combined with Lock Scripts, this means we can implement Lightning channels (and other contract types) with UDT as the underlying asset.
The separation of Lock Script and Type Script can be seen as a security upgrade: Lock Script focuses on implementing custody methods or contract-based protocols, while Type Script focuses on defining UDTs.
Additionally, the ability to initiate checks based on UDT definitions allows UDTs to participate in contracts similar to CKBytes (native token on CKB) since UDT is a first-class citizen.
For example, I once proposed a protocol for trustless NFT-backed lending on Bitcoin. The key to this protocol is a commitment transaction with its inputs’ value less than the outputs’ value (making it not a valid transaction initially). However, once sufficient valued input is provided for this transaction, it becomes valid. If the borrower can repay the loan, the lender cannot take ownership of the pledged NFT. The trustless nature of this commitment transaction is based on checking the amounts of inputs and outputs. So, the borrower can only use Bitcoin for repayment, even if both the borrower and the lender are willing to accept another currency, such as USDT issued under the RGB protocol. Bitcoin’s commitment transactions cannot guarantee that returning enough USDT will result in the return of the borrower’s NFT because Bitcoin transactions have no knowledge of the status of USDT. In other words, it is not possible to construct a commitment transaction with repayment in USDT as a condition.
If we could perform checks based on the definition of UDT, it would enable the lender to sign another commitment transaction that allows the borrower to use USDT for repayment. The transaction would verify the input and output quantities of USDT, thus using USDT as a repayment method becomes trustless.
Revision: Suppose that the NFT used as collateral and the tokens used for repayment are issued using the same protocol, such as RGB. In that case, the issue can be resolved by constructing a commitment transaction within the RGB protocol that synchronizes the state transitions of the NFT and the repayment (binding two state transitions within the RGB protocol to one transaction). However, because RGB transactions also depend on Bitcoin transactions, constructing the commitment transaction here may be somewhat challenging. In summary, while the problem can be resolved, it does not make the token a first-class citizen.
Next, let’s consider introspection.
(III)Lock Script Introspects Other Lock Scripts
Lock Script introspecting other Lock Scripts implies that all programming possibilities on Bitcoin UTXO being reinforced with covenants, including the vault contract mentioned earlier and applications based on OP_CTV
(such as congestion control), would be feasible.
XueJie once brought up a very interesting example: a receiving account Cell on CKB. When using this Cell as an input of a transaction, if the responding output Cell (with the same Lock Script) has more Capacity, this input does not require a signature, which means no signature does not affect the transaction’s validity. This is impossible without Cell’s the ability to introspect. This receiving account Cell is well-suited as an institutional receiving method, as it can aggregate funds, but it has the disadvantage of poor privacy.
(IV)Lock Script Introspects Other Type Scripts(& Data)
An interesting application of this feature is with equity tokens. The Lock Script can determine whether to utilize its own Capacity and where those funds can be spent based on the quantity of tokens in other inputs (which requires the introspection of the Lock Script).
(V)Type Script Introspects Other Lock Scripts
Assuming this could be be useful. For example, in the Type Script, it can be used to check whether the Lock Scripts of the inputs and outputs in a transaction remain unchanged.
(VI)Type Script Introspects Other Type Scripts(& Data)
Trading cards? Collecting n tokens can be exchanged for a larger token : )
6. Conclusion
Compared to previously introduced smart contract systems with arbitrary programmability, such as Ethereum, Nervos Network has adopted a different structure. Therefore, understanding Nervos Network can often be challenging based on prior knowledge of those smart contract systems. This post starts from BTC UTXO, a more constrained structure than CKB Cells, to provide a method for understanding the programmability of CKB Cells. By using the concept of “introspection” to understand Cells’ capability for “cross-contract access,” we can categorize scenarios that utilize introspection and determine their specific use cases.
Revision:
- Without considering Cell’s cross-contract access capability (i.e., introspection), lock scripts can be seen as stateful and highly programmable Bitcoin Scripts. Therefore, based on this point alone, you can program all applications based on Bitcoin Script.
- Without considering Cell’s introspection, the distinction between lock scripts and type scripts can be seen as a security upgrade: it separates the asset definition of UDT from the custody methods. Furthermore, state-exposing type scripts (as well as Data) achieve the effect of UDT being a first-class citizen.
These two points imply a paradigm similar to “BTC + RGB” but with greater programming capabilities.
- When considering Cell’s introspection, Cells can achieve greater programmability than post-covenants BTC UTXOs and implement things that are difficult to achieve with BTC + RGB (since BTC cannot read RGB state).
As for these use cases of the programmability of CKB, examples provided here are just a few due to my knowledge about the CKB ecosystem. However, I expect that as time goes on, people will channel more and more of their creativity into CKB, creating applications that are currently beyond our imagination.