1-on-1 Binding Between Bitcoin UTXO and CKB SUDT Cell

Series on Bitcoin Commitment

I described a proof-of-concept prototype to verify the states bound to Bitcoin UTXO via commitments in the post CKB-side Validation on Bitcoin UTXO Commitment.

That PoC does not restrict who can commit the matching CKB transactions to the CKB chain. It only uses on chain CKB cell as a shortcut proof that the bound state is valid because it has been validated by all CKB nodes.

However, if we want to transfer the ownership of these states to CKB and back to Bitcoin (RGB++ uses the term “Jump” for this), we need to ensure there’s only one canonical matching CKB cell for each bound state. Also we must guarantee that the asset owner is able to create the matching CKB cell for his/her owned Bitcoin UTXO without coordination from other parties.

Properties for the 1-on-1 binding:

  • Uniqueness: For each Bitcoin UTXO and its bound state, there’s at most one matching CKB cell in the CKB chain.
  • Liveness: For each Bitcoin UTXO and its bound state, if there’s no matching CKB cell in the CKB chain, the owner is able to create such a cell without the coordination from other parties.

In this post, I try to establish such 1-on-1 binding between a Bitcoin UTXO with its bound state and a CKB SUDT cell. I use this as an exercise to understand how RGB++ works and its design trade-offs.

Where to Add Binding Logic

CKB VM is Turing-complete, so we can submit the BTC transaction as a witness in the corresponding CKB transaction and verify that the two transaction structures match. If there’s a Bitcoin chain oracle available in CKB, we can verify that the BTC transaction is indeed in the Bitcoin chain as well.

The problem is who is responsible for such validation? The type script will be occupied by SUDT, and we can only use the lock script. But there’s one major obstacle with lock script, it does not execute if it only occurs in the CKB transaction outputs. Fortunately, this can be workaround in SUDT because the only way to create a CKB transaction that an SUDT output without an input of the same kind is via issuer lock.

Let’s define one lock script BBCCRootLock and BBCCLock . If you are curious, the acronym BBCC stands for Bitcoin Bounded CKB Cell.

BBCCRootLock:
  args: H(G)

BBCCRootLock for Root States

Anyone is allow to create such a cell using BBCCRootLock as the lock script. The args is the hash of the genesis state to differentiate different kind assets.

The BBCCRootLock will be used as the SUDT owner lock script, so the SUDT type script is:

SUDT for BBCC:
  args: H(BBCCRootLock args=H(G))

BBCCRootLock validates the CKB transactions for the root states.

Let’s take a look at the example in the previous post:

CKB-side Validation on Bitcoin UTXO Commitment - Drawing CKB Script Groups.excalidraw

The corresponding CKB transaction to create State1a is:

inputs:
- lock: BBCCRootLock args=H(G)
  type: null
  data: any
  capacity: any 
outputs:
- lock: BBCCLock args=H(G)|UTXO1a.tx_hash|UTXO1a.index
  type: SUDT args=H(BBCCRootLock args=H(G))
  data: Data1a
  capacity: any
witnesses:
- BTC tx that creates UTXO1a

The validation logic for BBCCRootLock:

  • Reads BTC tx from witness, and verify the tx is on chain against the Bitcoin oracle (optional).
  • Check that the BTC transaction match the script group for the type script SUDT args=H(BBCCRootLock args=H(G)).

This design does not meet the uniqueness property, because it’s possible to reuse the BTC tx to create many matching CKB cells for one root state. I’ll adopt a solution similar to RGB++: adding a CKB UTXO in root states to ensure they can only be used once in CKB.

State1a: H(G) | OutPoint | Data1a

The updated CKB tx:

inputs:
- previous_output: State1a.OutPoint
  lock: BBCCRootLock args=H(G)
  type: null
  data: any
  capacity: any 
outputs:
- lock: BBCCLock args=H(G)|UTXO1a.tx_hash|UTXO1a.index
  type: SUDT args=H(BBCCRootLock args=H(G))
  data: Data1a
  capacity: any
witnesses:
- BTC tx that creates UTXO1a

The BBCCRootLock needs to reconstruct State1a from H(G), inputs[0].previous_output, and outputs[0].data.

Proof of properties:

  • Uniqueness: Yes. Because CKB UTXO cannot be double spent, the out point in State1a ensures that only one matching CKB cell can be created.
  • Liveness: No. If the specified CKB UTXO is not live, no one can create the corresponding root state. This is an acceptable trade-off because of two reasons: 1) Such root states are usually the issuing transactions created by the asset owners. 2) Users can add a step to verify that the root state CKB cell exists in the CKB chain before using the asset.

BBCCLock for State Transactions

We’ll use BBCCLock for state transactions. State transactions always have parent states, so the matching CKB transaction always have inputs locked by BBCCLock, so it will always be executed.

BBCCLock:
  args: args=H(G)|UTXO1a.tx_hash|UTXO1a.index

The args for BBCCLock is the genesis state hash, and the bound Bitcoin UTXO outpoint.

Here is the example CKB transaction to create State3a and State3b.

inputs:
- lock: BBCCLock args=H(G)|UTXO2a.tx_hash|UTXO2a.index
  type: SUDT args=H(BBCCRootLock args=H(G))
  data: Data2a
  capacity: any 
- lock: BBCCLock args=H(G)|UTXO1b.tx_hash|UTXO1b.index
  type: SUDT args=H(BBCCRootLock args=H(G))
  data: Data1b
  capacity: any 
outputs:
- lock: BBCCLock args=H(G)|UTXO3a.tx_hash|UTXO3a.index
  type: SUDT args=H(BBCCRootLock args=H(G))
  data: Data3a
  capacity: any
- lock: BBCCLock args=H(G)|UTXO3b.tx_hash|UTXO3b.index
  type: SUDT args=H(BBCCRootLock args=H(G))
  data: Data3b
  capacity: any
witnesses:
- BTC tx that creates UTXO3a and UTXO3b

Pay attention that BBCCLock scripts will execute twice since the two inputs does not belong to the same lock script group, it can optimized to avoid duplicated runnings.

BBCCLock has to validate that for each BBCC SUDT type script groups, it matches the corresponding BTC tx in the witness.

  • Uniqueness: Yes. Here we don’t need to add CKB UTXO in states, because it can be proved that no two CKB cells can have the same lock script BBCCLock args=H(G)|AnyUTXO.tx_hash|AnyUTXO.index.
  • Liveness: Yes. If the bound root state CKB cells exists in the chain, anyone can create such transactions to the CKB chain, and the parent state CKB cells can only be destroyed to create their child state CKB cells.

Transfer Ownership From Bitcoin to CKB

A simple way to transfer ownership from Bitcoin to CKB is replace one Bitcoin UTXO with OP_RETURN CKB_CELL.lock_script, so we can create a corresponding BBBC SUDT cell with the specific lock script.

However, when we transfer back CKB to Bitcoin, we will have a problem that BBCCLock can be skipped if it only occurs in outputs. A solution is that the CKB cannot be transfered to any lock script, but wrapper script which ensures the BBCC logic will run.

Another issue of my toy binding solution is that it is not general and leverage on the properties of SUDT so wen can ensure the binding validation logic always runs.

I will not delve into these issues, since that will become a solution like RGB++. I think it’s already enough here to understand how RGB++ works.

2 Likes