Overview of the Approach
Tokens are stored in individual UTXOs, like native satoshis. We use covenants to enforce a token to retain its distinguished form in all subsequent transactions. More specifically, we use recursive covenants to maintain token state.
We will construct CAT token smart contract step by step. We start with a straightforward yet insecure implementation.
First Attempt
In the diagram above, the current transaction curTx
merges tokens from two parent transactions: parent0Tx
and parent1Tx
. Each token output’s state consists of:
addr
: owner’s addressamt
: amount of tokens in the output.
Their scripts are identical and must satisfy all conditions/assertions specified in token script T
:1
Condition (1) ensures that owner A authorizes the merge. (2) and (3) ensure that script propagates from parents to the current spending/redeeming transaction, and later to its children and so on, that is, recursive covenants. (4) makes sure token amounts are preserved after merging and no new tokens are created out of thin air.
An Attack
Token script T
has a security vulnerability: an adversary can join two different types of tokens. The attack is exemplified in the figure below. Transactions parent0Tx
and parent1Tx
mint 22 and 11 units of different tokens, respectively. This is similar to new bitcoins only emitted from a coinbase transaction. curTx
joins them into a single unit of 33 of the same token.
Note all conditions in T
are met.
To prevent this attack, we assign each token a unique identifier. One straightforward way is to use the outpoint the minting transaction spends as tokenId
, i.e., txid_vout
. We call the transaction the outpoint is in the token genesis
transaction (e.g., T1
and T2
). Since each genesis outpoint is globally unique, we have a unique tokenId
. We add tokenId
into its state and add two more conditions into T
.
They ensure the tokenId
is preserved from parents to children, grandchildren, etc. All subsequent spend of the output must embed tokenId
for the entire lifecycle of the token. This technique effectively "colors" the token and ensures only the same type/color of tokens can be merged.
With these additions, the previous attack is thwarted since condition (2) is violated.
Final Piece of the Puzzle
We are not done yet. Note script S2
in T2
is evaluated, but script T
is not, when minting a token in parent1Tx
. T
will only be evaluated when the minted token is spent. Consequently, an attacker could choose an arbitrary value for tokenId
. For example, she can set tokenId
to be T1_0
, instead of T2_0
, and forge 11 units of tokens with ID T1_0
in parent1Tx
. These tokens can be merged with 22 valid tokens in parent0Tx
, bypassing all previous conditional checks.
To address this issue, we add another conditional check in token script T
, to validate tokenId
is set faithfully as the genesis output.
To see how this works, let us look at the following diagram. Suppose we are at curTx
, third from the top as indicated by the arrow on the left. Since script S
is not T
, we know grandparentTx
is the genesis transaction and we enforce the tokenId
in parentTx
is set as T1_0
. All descendant transactions from the genesis inherit the same tokenId
after it is algorithmically set when minting. Suppose we are at the fourth transaction as indicated by the arrow on the right, we skip the conditional check above, since the script of grandparent and parent are identical. Same is true when we are at the 1000-th generation transaction. At any token transaction, its grandparent can only be one of the two:
- A token genesis transaction
- The same token transaction: same script and
tokenId
The aforementioned attack is now prevented.
The attacker can still forge tokens, but they are unspendable, rendering the forgery futile.
Inductive Proof and Contract ID
An inductive proof allows a transaction to demonstrate that it is a valid descendant of an original token-minting transaction. This is achieved by including specific data from the parent and grandparent transaction in the current transaction, thereby establishing a verifiable link back to the genesis transaction. This method ensures token authenticity and prevents forgery without the need to reference the entire transaction history. It essentially creates an uncopyable unique ID for a contract in a UTXO/outpoint. This contract ID can be applied independently beyond the scope of the CAT protocol context.
Footnotes
-
Txo
is short forTx.outputs
. ↩