A User Guide to the CRC20 Protocol

First and foremost, we want to express our gratitude to the anonymous developer who published the CRC20 protocol on May 14, 2023. This guide is based on their work and our aim is to make it as accessible as possible.

Introduction to the CRC20 Protocol

In May 2023, the Bitcoin Cash upgrade activated the [Cashtoken protocol (https://github.com/bitjson/cashtokens). This major step forward brought in built-in support for both fungible and non-fungible tokens on Bitcoin Cash, moving beyond the earlier Layer-2 token issuance protocols like the Simple Ledger Protocol.

However, compared to well-known existing protocols like ERC20, Cashtoken was missing a few features. Specifically, it didn’t specify the token’s symbol, name, and decimals.

To fill these gaps, the CRC20 protocol was introduced as an extension to the Cashtoken protocol. It adds three important pieces of information for tokens:

Symbol: The abbreviation used to represent the token.
Name: The full name of the token.
Decimals: The number of decimal places the token can be divided into.

We strongly advise all users to conduct their own research and exercise caution when using our service. Your use of this platform is entirely at your own risk.

The Two-Stage Registration Process

The CRC20 protocol uses a two-step process (similar to [ENS]) to register these information fields. This is done to prevent any bad actors from trying to register token information in a hurry.

The first step involves storing a hashed version of the token information on the blockchain. The second step reveals the actual token information.

The Canonical Category

When a token is the first to register a specific symbol, it defines the “canonical category” for that symbol. Simply put, it sets a standard for that symbol. The CRC20 protocol has a method to help users find out the canonical category of a symbol without needing new services. It uses the existing Electrum protocol within the Bitcoin Cash ecosystem.

We hope this guide provides you with a clear understanding of the CRC20 protocol. As we continue to witness the growth and development within Bitcoin Cash, we’re excited about the potential of the CRC20 protocol and look forward to seeing how it will be utilized. Remember, the Bitcoin Cash culture is growing globally, and you are a part of it!

CRC20 Protocol Specification

The CRC20 protocol lays out a specific code requirement for the Genesis Output. This is essentially the starting point of the token.

contract GenesisOutput(pubkey recipientPK, bytes metadata, int symbolLength) {
    function reveal(sig recipientSig) {
        require(checkSig(recipientSig, recipientPK));
        bytes20 symbolHash = hash160(metadata.split(symbolLength)[0]);
        bytes25 outLockingBytecode = new LockingBytecodeP2PKH(symbolHash);
        require(tx.outputs[0].lockingBytecode == outLockingBytecode);
    }
}

In this code:

  • ‘pubkey recipientPK’ represents the public key of the recipient.
  • ‘bytes metadata’ contains the information about the token.
  • ‘int symbolLength’ specifies the length of the symbol.

The ‘metadata’ parameter stores the token’s symbol, decimals, and name in the format ‘metadata[:symbolLength]’, ‘metadata[symbolLength]’ and ‘metadata[symbolLength+1:]’ respectively.

To start a token using this protocol, you need to first create a Genesis Output using a transaction. Once this transaction is confirmed, you can then send the Genesis Transaction. When the Genesis Output is spent, it reveals the token’s symbol, decimals, and name.

The protocol also specifies that the first output of the Genesis Transaction should be a P2PKH (Pay-to-Public-Key-Hash) output. However, the hash of the symbol replaces the Pubkey’s Hash, meaning this UTXO (Unspent Transaction Output) cannot be spent by anyone. This is known as the Symbol UTXO, and it serves to be indexed by the Electrum protocol.

Querying a Symbol’s Canonical Category

To find out the canonical category of a symbol, you can use the Electrum protocol. The Fair Genesis Height of a token is defined as the maximum of H_commit and H_reveal-20, where H_commit and H_reveal are the heights of the transactions committing and revealing the metadata respectively. It’s advised not to have too many blocks between the committing and revealing.

Given a symbol, you can calculate the address of the Symbol UTXO and then use ‘blockchain.address.listunspent’ to find this address’s UTXO list. Sort the list according to the Fair Genesis Height and the Genesis Transaction’s hash ID, and filter out the UTXOs that do not meet certain criteria:

  1. If this UTXO has not been packaged into a block, filter it out.
  2. If this UTXO is not the first output of the transaction, filter it out.
  3. If this UTXO is not an output of the Genesis Transaction, filter it out. This requires checking whether other outputs have the token category attribute, and the Symbol UTXO itself does not need to have the token category attribute.
  4. Search for the Target Input among the inputs of this Genesis Transaction. If the Target Input cannot be found, the UTXO should be filtered out. The Target Input must satisfy the following three conditions:
    1. It is a [Genesis Input].
    1. It spends a covenant whose code is specified above.
    1. The symbol it reveals is the one we’re querying.

The canonical category of the symbol will be the token category defined by the first UTXO in the filtered list.

It’s important to note that when a reorg (reorganization) occurs in the BCH mainnet => when a reorganization occurs in the Bitcoin Cash blockchain.

I have made a CRC-20 token genesis scanner which uses a Chaingraph server to get the candidates.
Then, I verify that the candidates satisfy the CRC-20 specification to produce a list of winners and convert the result to a BCMR .json file located here:

Procedure.

  1. Execute chaingraph query to get transactions that:
    • Spend from the CRC-20 “Genesis” covenant contract pattern
    • Have the identity output of P2PKH locking script type
    • Have tokens on the output side.
  2. Populate genesis_tx_outputs and genesis_tx_inputs tables. The CRC-20 redeem script is parsed in this step to extract the metadata and calculate the expected “marker” P2PKH bytecode.
  3. Use the sqlite engine to execute a SQL query which:
    • Joins the two tables in such a way as to obtain a table of valid genesis candidates
    • Sorts the list by fair_genesis_height, tx_hash, input_index and produces a table of winners
  4. Convert the result to BCMR .json. It uses the last checked block as registry’s latestRevision and uses the timestamp of block which contains the token’s genesis TX as the token’s identity timestamp.
1 Like

Well @bchsimon good to see a CRC20 post here, but the research forum isn’t really the place for a user guide. Instead it would be more helpful if we could give feedback on the technical details of the CRC20 protocol.

Much constructive criticism has been given in the CashTokens_devs telegram channel but unfortunately there is no CHIP-document for the CRC20 standard so no outreach process or clear place to go to.

The most important feedback is that the symbol should use some encoding (utf-8 / utf-16) instead of just straight binary. This can easily be added to the minting contract by using the Cashscript string-type

Thanks @bitcoincashautist for bridging the gap between East & West by bridging the CRC20 metadata standard to the BCMR standard.

2 Likes

Note that BCMR “symbol” field allows only uppercase alphanumericals and the - symbol where it may not be the 1st character, i.e. regex: ^[A-Z0-9]+[-A-Z0-9]*$).

So when translating CRC20 → BCMR, I couldn’t directly put their symbol as the ticker so I put their symbol in the name, and used the raw hex for the symbol, example:

    "0d538dba8ede9fe5ca0c576c1c830a03bef6f11ef9f48033b5abd6dff333b6d2": {
      "2023-05-21T17:03:30.000Z": {
        "name": "[CRC20][<10K] <10K",
        "description": "CashTokens token category which first claimed this metadata according to CRC-20 genesis specification.\\nInitial fungible token supply: 2100000000000000\\nGenesis TXID: 04192cdeedd34db6567edeca153951592e54c9adef2fb5dfed2d553a20f31daa.",
        "token": {
          "category": "0d538dba8ede9fe5ca0c576c1c830a03bef6f11ef9f48033b5abd6dff333b6d2",
          "decimals": 8,
          "symbol": "CRC20-0X3C31304B"
        }
      }
    },

This can easily be added to the minting contract by using the Cashscript string-type

The current contract simply splits a blob to 2 byte arrays (symbol, decimals&name), and hashes the symbol array verbatim to get the marker P2PKH.
The metadata standard is blockchain-external, so they could just update the convention to reject weird symbols without needing to change the script at all.

My main complaint was usage of an unspendable P2PKH as the genesis “marker”. I suggested another approach:

contract GenesisOutput(pubkey recipientPK, bytes metadata, int symbolLength) {
    function reveal(sig recipientSig) {
        require(checkSig(recipientSig, recipientPK));
        bytes symbol = metadata.split(symbolLength)[0];
        bytes20 markerHash = hash160(
            bytes(symbol.length, 1)
            + symbol
            + 0x7551
        );
        bytes23 outLockingBytecode = 0xa914 + markerHash + 0x87;
        require(tx.outputs[0].lockingBytecode == outLockingBytecode);
    }
}

This would still leave a marker and allow the symbol to be looked up using Electrum’s address history API, but the marker would be spent (unless symbol would be longer than 75 bytes, in which case bytes(symbol.length, 1) would fail and make it unspendable).

Indexing these is not hard, especially since we have 2 community-run instances of Changraph up and running, and my scanner demonstrates how to use it to look up CRC-20 genesis candidates and build a little index of created tokens.

1 Like