Skip to content

Nexa Group Tokenization

Introduction

Groups are a method for implementing representative tokens -- also named "colored coins" within the cryptocurrency community. Groups implement native tokens; that is, the tokens are a fundamental primitive in the blockchain rather than implemented as a smart contract or a in a layer 2 data-carrier protocol.

Group tokens differ in significant aspects from other existing or proposed tokenization techniques:

  • Implemented as a primitive within the blockchain.
  • Is miner validated
    • Like NEX transactions, light wallets can rely on the idea that transactions committed some blocks behind the blockchain tip are valid
  • Does not require knowledge of the blockchain history to determine token grouping
    • Compatible with pruning and forthcoming UTXO commitment techniques
  • Simple "light" wallet implementation
    • Can use the same merkle proof technique (SPV or "simplified payment validation") used for transactions today.

The format of this document

This document is meant to be a summary of the full description linked in the references section.

This document uses the term MUST to indicate a rule that must be followed by all implementations. The term MAY indicates optional functionality. However, if optional functionality is implemented it is highly recommended that be implemented as defined here for interoperability with other software.

[This document places the "why" -- the discussion concerning the reasons and justifications of certain decisions -- in bracketed italics. Text in italics is informative, not normative]

For the purposes of discussion of this draft additional notes are contained throughout in marked "DISCUSSION" and formatted in bold brackets [DISCUSSION: like this]. These are intended to be removed from the final version of this document.

Risks and philosophical approach

The purpose of Group Tokenization on the Nexa blockchain and in SPV (phone) wallets, covering major use cases with a fraction of the development effort and maintenance of other token proposals. These use cases include ICOs (initial coin offerings), representative tokens for stocks, bonds and currencies, and other uses I haven't considered. But these use cases notably do not include "smart" contracts, since the introduction of a sophisticated programming language would add tremendous complexity and competes with the evolving Nexa Script language.

Definitions

  • P2T - Pay to (grouped) template output script type.
  • mint-melt address - An address that can be used to mint or melt tokens. This is actually the same number as the group identifier.
  • group identifier - A number used to identify a group. This is the same number as the group's mint-melt address, but it uses a cashaddr type of 2.
  • nexa group - A special-case group (denoted by 0 in the output script) that includes all transactions with no explicit group id. This group represents the "native" NEX tokens during transaction analysis.
  • mint - Create new tokens
  • melt - Destroy existing tokens. This is different than sending tokens to an unspendable address which is often called "burning" tokens, although the result is functionally similar.
  • UTXO - Unspent Transaction Output: A record of current value on the nexa blockchain

Theory of Operation

Please refer to Group Tokenization.

Specification

This specification is divided into 3 sections: miner (consensus) requirements, full node implementations, and wallet implementations.

Miner (Consensus) Requirements

Deployment

Miners MUST enforce Group Tokenization as a part of script templates in the Genesis block release.

Validation

Note this validation document was written in the context of the old format that used an opcode prefix to denote grouping. Nexa validation proceeds similarly, but there is not opcode prefix -- there is an explicit output script type that always carries a group identifier (although that id might be 0 which means nexa tokens).

A new Script opcode MUST be created, named OP_GROUP and replacing OP_NOP7 (0xb6).

This opcode MUST behave as a no-op during script evaluation. It therefore may legally appear anywhere in a script. [Its simpler and less error prone to let it appear anywhere and ignore it (as per the OP_NOP7 semantics today) than to add a consensus rule enforcing its proper appearance. Having such a rule would be a place where implementation could fall out of consensus. I believe that consensus rules should be minimal and be those that protect or enhance the monetary function of NEX. Limiting extraneous appearances of OP_GROUP does neither]

This opcode comes into play during transaction validation, and ensures that the quantity of input and outputs within every group balance.

First a "mint-melt group", a "single-mint group" and a "group identifier" are identified for each input and output.

The group identifier is the token group that this input or output belongs to. A group identifier is a data string of 20 or 32 bytes. It is not a number (so no zero or sign extension is allowed). A 19 byte group identifier is simply invalid. Transactions that do not use OP_GROUP are put in a special group called "NoGroup" that designates the "native" NEX token. "NoGroup" is a conceptual aid -- it will never be used outside your implementation.

Inputs may also have a mint-melt group, depending on their construction. The mint-melt group indicates the ability to either mint or melt tokens into or from the corresponding group. All inputs have a single-mint group.

Identification proceeds in the following manner:

For all inputs:

The mint-melt group and group identifier is the same as that of the "previous output" (specified in the input by its transaction id and index). [The prevout is already needed for script validation and transaction signing, and is located in the UTXO database so this backpointer adds no additional data storage requirements on nodes or wallets]

The single-mint group is the double SHA256 of the input's serialized transaction id and vout index.

For all outputs:

To specify a group identifier, a script MUST begin with the following exact format: <20 or 32 byte group identfier> <1,2,4, or 8 byte unsigned little-endian quantity> OP_GROUP ....

In words, If a script begins with 0x14 or 0x20 (i.e. 20 or 32 byte data push opcodes), followed by data, followed by OP_GROUP, the group identifier is the data pushed. This sequence MUST begin the script and there MUST NOT be other opcodes between the group identifier data push and the OP_GROUP instruction.

If the script does not meet the above specification, its group identifier is "NoGroup".

[It is necessary to identify the group without executing the script so the group cannot be located within conditional code. The simplest solution is to put it first]

If a script is a P2PKH, P2SH, or GP2PKH (group P2PKH) or GP2SH, the mint-melt group is the public key hash or script hash. For complete clarity, these are the only scripts that have a mint-melt group:

P2PKH: OP_DUP OP_HASH160 <mint-melt group> OP_EQUALVERIFY OP_CHECKSIG

P2SH: OP_HASH160 <mint-melt group> OP_EQUAL

GP2PKH: <data> OP_GROUP OP_DROP OP_DUP OP_HASH160 <mint-melt group> OP_EQUALVERIFY OP_CHECKSIG

GP2SH: <data> OP_GROUP OP_DROP OP_HASH160 <mint-melt group> OP_EQUAL

As you can see, the mint melt group is another name for the script hash, and public key hash (for these scripts only).

[It makes OP_GROUP operation much simpler if the mint-melt group can be identified without executing the script. Therefore, we limit the mintable or meltable input transaction types to well-known script templates. This does not limit functionality. Tokens that exist in a nonstandard script can be minted or melted in 2 transactions by first sending them to an output that uses one of the above scripts, and then issuing the mint/melt transaction]

If a script does not match one of the above templates, it MUST have no mint-melt group.

Transaction Validation algorithm

[The algorithm below is described pedantically to make it easy to understand how it actually succeeds at balancing group inputs and outputs and how it correctly applies mint and melt coins. It should be possible to write it much more succinctly -- for example, there is no need for an "input" field. Instead, you could subtract the output field down to 0]

Create a data structure (let's call it GroupBalances) that associates group identifiers with one boolean "mintableOrMeltable", and 2 Amounts "input" and "output". Initialize these to false/0.

First we'll find the final quantity of tokens in every group. We need this so we can match it with inputs to balance the transaction:

  • For every transaction output (vout):
  • Identify its group identifier and quantity If NoGroup, then continue.
  • Add the vout's quantity to the group's "output" field.

Next we go through every transaction input (vin) and add its value to the vin group's "input" field, in the same way we did for the output. However, there is one caveat -- we need to identify whether the value of that input could be used as a mint or melt. In that case we add to the "mintable" or "meltable" field rather than the "input" field:

  • For every transaction input (vin), find its group identifier and quantity, its single-mint group, and its mint-melt group:
  • If the single-mint group is in GroupBalances, mark "mintableOrMeltable" true
  • If the mint-melt group is in GroupBalances, mark "mintableOrMeltable" true
  • If the group identifier is valid (not NoGroup), add the quantity to "input"

  • For every group in GroupBalances, compare the "input" to the "output":

  • If "mintableOrMeltable" is false and "input" != "output", FAIL

Full node Implementations

Wallet Implementations

Group is implemented as direct fields in the output script of Nexa script templates. For more information see script templates.

Group Identifier

Group identifiers are 32 data bytes which are also nexa addresses. For more information see Nexa Addressing.

JSON-RPC calls

It is recommended that all wallets with a JSON-RPC (nexa-cli) interface provide the same API so that applications built on top of this interface will work with different wallet implementations.

One new RPC is defined, named "token" that contains several sub-functions. Parameters are similar for all sub-functions and are as follows:

group id (string): The group identifier in cashaddr format. This identifier is generated in the "token new" command. All group identifiers have a "z" prefix.

address (string): A nexa address. Token addresses are interchangable with each other and NEX addresses, so use the standard "getnewaddress" RPC command to create one. [having the same addresses for multiple token types allows one to put different tokens in the same address. This feature may have many uses, such as paying interest in NEX to token holders]

quantity (large integer): All functions express token quantities in single units. This is different than the non-token API which expresses values as NEX or 100,000,000 Satoshi.

RPC: "token new"

Create a new group (token type).

Syntax:

token new [address] [ticker name [descUrl descHash]]

  • address: (string, required) the destination address
  • ticker: (string, optional) the token's preferred ticker symbol
  • name: (string, optional) the name of the token
  • descUrl: (string, optional) the url of the token description json document
  • descHash: (string, optional) the hash of the token description json document

Returns:

{
  "groupIdentifier": "<group identifier>",
  "transaction": "<transaction idem>",
  "tokenDescriptorSigningAddress":"<address to use when signing the token descripting document>"
}

Example:

$ ./nexa-cli token new NIFTY NiftyArt https://niftyart.cash/td/nifty.json b0fa910a48c81cd09b414850ebec6ba040bf3f8b9e0cc39cfd13e03a02be4a0b
{
  "groupIdentifier": "nexa:tr9v70v4s9s6jfwz32ts60zqmmkp50lqv7t0ux620d50xa7dhyqqqcg6kdm6f",
  "transaction": "b6b4cf2693a8c1c38648a9f1fb05d19360f3eff3f92776eced06d4ffe46e6cb3",
  "tokenDescriptorSigningAddress": "nexa:nqtsq5g5c66uyyehfqz2ck9rauegkjqms4zm75syvp2sqkr5"
}

RPC: "token mint"

Create a certain number of tokens of a particular type and sends them to specified addresses. Converts (redeems) tokens back into NEX. You must be the owner of this group (have the ability to sign the controllingAddress) to successfully execute this function.

Depending on the implementation, you may need to manually load the group's controlling address with some NEX before calling this function.

Syntax:

token mint <group id> <address> <quantity> [ <address> <quantity>...]

Returns:

A transaction id (hex string) or list of transaction ids.

Example:

./nexa-cli token mint nexareg:zzm4ufz5erpzphtxm5knllxyv9kwut8vnsjjrsfg48 nexareg:qza38qklu2ztay60xaxl2wuhdzc5327p0ssaqjadz0 100000 nexareg:qpjal7uqcgqv7crjc3s2098ha4thv4z6es6fjnww35 50000
635243c3bc1f7b6f5f0dc0f3b5cd5aa82d483e9ec669f4e81b0c734bccb9c762

RPC: "token send"

Send tokens to other addresses.

Syntax:

token send <group id> <address> <quantity> [ <address> <quantity>...]

Returns:

A transaction id (hex string).

Example:

./nexa-cli token send nexareg:zzm4ufz5erpzphtxm5knllxyv9kwut8vnsjjrsfg48 nexareg:qr4tj4zvfcmyjq55wmt4qcz0w27drzcmtcszn9xutz 42 nexareg:qrxqy0hjnjumjayf25sawvjkammspdeyxv8ejpe748 451

RPC: "token melt"

Converts (redeems) tokens back into NEX. You must be the owner of this group (have the ability to sign the controllingAddress) to successfully execute this function. Some wallets may require that tokens first be sent to the controllingAddress before melting. This allows you to control which tokens are melted.

Syntax:

token melt <group id> <address> <quantity> [ <address> <quantity>...]

Returns:

A transaction id (hex string).

Example:

./nexa-cli token melt nexareg:zzm4ufz5erpzphtxm5knllxyv9kwut8vnsjjrsfg48 nexareg:qz52hzhqdlfrvwsrt74kf6rt5utzvf5zsv4hdywqxd 100
9ef6465c47b620507fb99937b9df836820f2b103f9cf1b94be532865f7751757

Token Description Document

Wallets, explorers, or other applications that display blockchain data may want to display group token information in a manner that is more comprehensible to people than the Group ID. Additionally, a group token may contain extra blockchain information such as a legal contract, or data for an external application to use.

The Group Token Description Document is the place to put this information. For more information see: Token Description Document

References

Please refer to https://medium.com/@g.andrew.stone/bitcoin-scripting-applications-representative-tokens-ece42de81285.