New Group Tokenization Scheme - One Token Standard (WIP)
Intro
While I was busy with the Group CHIP, others have been busy too with thinking up a token solution for Bitcoin Cash. Emil Oldenburg approached me to discuss a proposal which had a lot in common with the Group CHIP, and then we had an intense discussion which lasted for days and which brought us close to marrying the two proposals. :) The fruit of our brainstorming is now presented here for more eyes to see.
I was presenting Group from the bottom-up, saying "Here are the building blocks, we can do X, Y, Z with them!", and Emil was working on "I need X, what do I need from the low-level protocol to have X?". By chipping away at Group, we arrived to something that enables only X, the "One Token Standard" which is presented here.
The base concept is the same, attaching Group annotations to BCH outputs.
However, the authority system got a lot less complex with restricting it to just one baton output per tokenID.
Working on it from both sides, low level and high level, we converged on a design which happens to follow the Python maxim: "There should be one-- and preferably only one --obvious way to do it."
If this Bitcoin Cash one token system should become as popular as Python, then I believe it will bring in a lot of value to the Bitcoin Cash ecosystem and BCH market cap!
Major changes
- Exactly one baton UTXO, previously known as "authority output", allowed for each tokenID and it can only be destroyed by spending it to an OP_RETURN.
- Token baton state tracked with MINT flag, and finite minting pool field.
- Token metadata state tracked with 5 locks encoded in the baton flag, and 5 variable-length user-defined data fields. If a lock is set, consensus enforces the matching fields to be equal across the TX.
- Immutable token properties are deducible from the set of consensus rules and the initial setup of the baton genesis output.
We encode the genesis baton initialization into one byte of the
tokenID, from which users can later tell, without requiring the genesis TX, what flags were set at genesis, and whether the baton started with minting capacity of ==0, ==1, or >1, and whether it had the metadata locked or not.
Grouped Token Output Annotation
Ordinary token amount output:
PFX_GROUP <tokenID><qty> <pubKeyScript>
Token baton output:
PFX_GROUP <tokenID><0x00><batonFlags><leftToMint><<metaLen1>[metaData1]... <metaLen5>[metaData5]> <pubKeyScript>
of which there can be exactly one baton UTXO for each distinct tokenID created.
Where:
The tokenID field is a fixed size of 32 bytes.
Fields qty, batonFlags, leftToMint, metaLenX are encoded as VarInt, for which existing consensus code already has deserialization functions.
The 0x00 value for qty can not be used for ordinary token amounts and is instead interpreted as the baton output.
The 0x00 value for batonFlags is allowed, and has an use described later.
The 0x00 value for batonMint is allowed, and has an use described later.
Except for the genesis, any token's baton must exist on both sides of a transaction.
Therefore it can not be accidentially destroyed by not including it as a baton "change" output.
It can only be made unspendable by spending it to a baton with OP_RETURN for the pubKeyScript.
Supported baton flags are:
MINT == 0x01, to allow minting or rescinding tokens back into the minting pool.
LOCK_1 == 0x02, locks the 1st metadata field.
LOCK_2 == 0x04, locks the 2nd metadata field.
...
LOCK_5 == 0x20, locks the 5th metadata field.
RESERVED == 0x40, no effect, has to be always unset.
RESERVED == 0x80, no effect, has to be always unset.
General Rules
Token balancing rule:
in_tokens == out_tokens
Token outputs can be spent to a token OP_RETURN allowing for permissionless destruction of either the token, baton, BCH or their combinations, because an OP_RETURN doesn't violate balancing rules.
The amounts would still balance, but some of them would be made permanently unspendable.
Baton MINT Flag
Presence of a MINT flag allows minting token amounts from the leftToMint minting pool.
Token amounts created in a transaction must balance with leftToMint amounts on both sides of the transaction.
We allow token amounts to be rescinded and put back into the MINT pool. This allows taking tokens out of circulation without removing them from the max. supply i.e. the balancing rule becomes:
in_tokenSum + in_leftToMint >= out_tokenSum + out_leftToMint
So the supply cap will always be the one set at genesis, and the total minted supply can be calculated by subtracting the baton UTXO from the genesis UTXO.
Token supply can be locked by giving up the mint flag in which case the remaining leftToMint can never be used.
Baton Metadata
The current metadata will always be in the baton UTXO:
PFX_GROUP <tokenID><0x00><batonFlags><leftToMint><<metaLen1>[metaData1]... <metaLen5>[metaData5]> <pubKeyScript>
It has no impact on token balancing rules.
The 5 LOCK_X flags refer to metadata fields in order, and ALL 5 fields MUST be included in every baton output.
All locked fields must be the same on both sides of a transaction.
A lock can be toggled only if the field it applies to hasn't been changed in the same transaction. This is to prevent accidential lock & change in the same transaction, making it impossible for the user to correct this error.
NULL fields are allowed and specified as <0x00> without the [metaDataX].
There are only 5 hardcoded fields, and while the consensus doesn't specify their meaning, we're expecting them to be mapped to:
metaData1: Decimals
metaData2: Ticker
metaData3: Name
metaData4: URL
metaData5: Document
by downstream protocols.
The isStandard() network rule could restrict the sizes for each field, allowing for a possibility of adjusting them if there will be user need.
To make the isStandard check simpler, the length of the whole metadata extension could be fixed, and users could 0-pad one of the fields to match the length.
The isStandard rule can be used to enforce a different dust limit rule for the baton UTXO. Because this UTXO is created only at token genesis, we want to incentivize only legitimate usage of it and deter usage for arbitrary data storage. Later metadata updates will result in adding data to the archival part of the blockchain and the UTXO load will be constant or reduced throughout the token's lifetime. If the token is locked, there will be incentive to destroy the baton into an OP_RETURN and recover the dust limit BCH.
Changing the number of lockable fields and locks would require a hard fork upgrade.
Token GENESIS
The GENESIS TX creates a new tokenID baton, called the genesis baton.
The output must satisfy the rule:
PFX_GROUP <tokenID = H(genData)><0x00><batonFlags><leftToMint><<metaLen1>[metaData1]... <metaLen5>[metaData5]> <pubKeyScript>
Where the genData is a byte concatenation of:
- First prevout TXID
- First prevout vout index
- The genesis output being generated, but with the tokenID left out
- Index of the genesis output being generated
With this, it is possible to create any number of distinct tokenID genesis batons in a single TX.
These rules apply only to the genesis baton:
- If MINT is set, then leftToMint MUST be > 0.
- If MINT is unset, then leftToMint MUST be == 0.
- Metadata CAN be set and locked at the same time.
- Metadata MUST be either completely unlocked or completely locked.
This is to reduce the number of token types.
For future upgradeability of the token system itself, we require the 1st MSB byte of the tokenID be:
ONE_TOKEN_STANDARD == 0x01.
We require the 2nd MSB byte to encode the genesis setup:
IS_NFT == 0x80, if leftToMint == 1
FLAGS_CONFIG == batonFlags
The unused baton flag 0x80 must therefore be DISABLED and always set to 0.
This makes token types easily identifiable by their tokenID:
0x0101... - standard token,
0x013F... - standard token with metadata locked at genesis,
0x0181... - standard NFT token,
0x01BF... - standard NFT token with metadata locked at genesis,
0x0100... - baton NFT token,
0x013E... - baton NFT token with metadata locked at genesis.
and also doubles as a PoW puzzle for token genesis.
Security Considerations
Ordinary Token Transactions
Overflowing the Sums
Guards to be implemented.
Spamming With TX-es packed with tokenIDs
Impact on validation performance?
Spamming With Malformed group outputs
Trivial to check and fail, first encounter fails the whole TX.
Spamming With Valid TX-es
Less effective than spamming with valid pure BCH TX-es, because a pure BCH TX of the same total size can fit more inputs and ouputs.
Baton Transactions
Spamming With Invalid Genesis Transactions
Genesis setups trivial to check and fail.
Invalid tokenID requires exactly 1 SHA256 hash to invalidate, and invalidating the first encounter fails the whole TX.
Spamming With Malformed TX-es
Trivial to check and fail.
Spamming With Metadata Updates
TBC. Requires comparing 5 arrays up to X byte each times Y batons in a TX.