Coder Social home page Coder Social logo

cashtokens / cashtokens Goto Github PK

View Code? Open in Web Editor NEW
46.0 8.0 32.0 636 KB

A proposal to enable two new primitives on Bitcoin Cash: fungible tokens and non-fungible tokens.

Home Page: https://cashtokens.org

bitcoin-cash bitcoin-cash-chip cashtokens bitcoin cryptocurrency tokenization

cashtokens's Introduction

CHIP-2022-02-CashTokens: Token Primitives for Bitcoin Cash

    Title: Token Primitives for Bitcoin Cash
    Type: Standards
    Layer: Consensus
    Maintainer: Jason Dreyzehner
    Status: Final
    Initial Publication Date: 2022-02-22
    Final Revision Date: 2023-5-20
    Version: 2.2.2
Table of Contents

Summary

This proposal enables two new primitives on Bitcoin Cash: fungible tokens and non-fungible tokens.

Terms

A token is an asset – distinct from the Bitcoin Cash currency – that can be created and transferred on the Bitcoin Cash network.

Non-Fungible tokens (NFTs) are a token type in which individual units cannot be merged or divided – each NFT contains a commitment, a short byte string attested to by the issuer of the NFT.

Fungible tokens are a token type in which individual units are undifferentiated – groups of fungible tokens can be freely divided and merged without tracking the identity of individual tokens (much like the Bitcoin Cash currency).

Deployment

Deployment of this specification is proposed for the May 2023 upgrade.

  • Activation is proposed for 1668513600 MTP, (2022-11-15T12:00:00.000Z) on chipnet.
  • Activation is proposed for 1684152000 MTP, (2023-05-15T12:00:00.000Z) on the BCH network (mainnet), testnet3, testnet4, and scalenet.

Motivation

Bitcoin Cash contracts lack primitives for issuing messages that can be verified by other contracts, preventing the development of decentralized application ecosystems on Bitcoin Cash.

Contract-Issued Commitments

In the context of the Bitcoin Cash virtual machine (VM), a commitment can be defined as an irrevocable message that was provably issued by a particular entity. Two forms of commitments are currently available to Bitcoin Cash contracts:

  • Transaction signatures – a commitment made by a private key attesting to the signing serialization of a transaction.
  • Data signatures – a commitment made by a private key attesting to the hash of an arbitrary message (introduced in 2018 by OP_CHECKDATASIG).

Each of these commitment types require the presence of a trusted private key. Because contracts cannot themselves hold a private key, any use case that requires a contract to issue a verifiable message must necessarily rely on trusted entities to provide signatures1. This limitation prevents Bitcoin Cash contracts from offering or using decentralized oracles – multiparty consensus systems that produce verifiable messages upon which other contracts can act.

By providing a commitment primitive that can be used directly by contracts, the Bitcoin Cash contract system can support advanced, decentralized applications without increasing transaction or block validation costs.

Notes
  1. Signature aggregation schemes can enable contracts to issue some commitments with reduced trust (e.g. by requiring a quorum of always-online entities to join a multiparty signing process), but these schemes typically require active coordination, carefully-designed participation incentives, fallback strategies, and other significant fixed costs (e.g. always-online servers). In practice, few such systems are able to maintain sufficient traction to continue functioning, and notably, forcing a contract to rely on these systems is arbitrary and wasteful (in terms of network bandwidth and validation costs) when their purpose is simply to attest to a result already produced by that contract.

Byte-String Commitments

The most general type of contract-issued commitment is a simple string of bytes. This type can be used to commit to any type of contract state: certifications of ownership, authorizations, credit, debt, contract-internal time or epoch, vote counts, receipts (e.g. to support refunds or future redemption), etc. Identities may commit to state within a hash structure (e.g. a merkle tree), or – to reduce transaction sizes – as a raw byte string (e.g. public keys, static numbers, boolean values).

In this proposal, byte-string commitments are called non-fungible tokens.

Numeric Commitments

The Bitcoin Cash virtual machine (VM) supports two primary data types in VM bytecode evaluation: byte strings and numbers. Given the existence of a primitive allowing contracts to commit to byte strings, another commitment primitive can be inferred: numeric commitments.

Numeric commitments are a specialization of byte-string commitments – they are commitments with numeric values which can be divided and merged in the same way as the Bitcoin Cash currency. With numeric commitments, contracts can efficiently represent fractional parts of abstract concepts – shares, pegged assets, bonds, loans, options, tickets, loyalty points, voting outcomes, etc.

While many use cases for numeric commitments can be emulated with only byte-string commitments, a numeric primitive enables many contracts to reduce or offload state management altogether (e.g. shareholder voting), simplifying contract audits and reducing transaction sizes.

In this proposal, numeric commitments are called fungible tokens.

Benefits

By enabling token primitives on Bitcoin Cash, this proposal offers several benefits.

Cross-Contract Interfaces

Using non-fungible tokens (NFTs), contracts can create messages that can be read by other contracts. These messages are impersonation-proof: other contracts can safely read and act on the commitment, certain that it was produced by the claimed contract.

With contract interoperability, behavior can be broken into clusters of smaller, coordinating contracts, reducing transaction sizes. This interoperability further enables covenants to communicate over public interfaces, allowing diverse ecosystems of compatible covenants to work together, even when developed and deployed separately.

Critically, this cross-contract interaction can be achieved within the "stateless" transaction model employed by Bitcoin Cash, rather than coordinating via shared global state. This allows Bitcoin Cash to support comparable contract functionality while retaining its >1000x efficiency advantage in transaction and block validation.

Decentralized Applications

Beyond enabling covenants to interoperate with other covenants, these token primitives allow for byte-efficient representations of complex internal state – supporting advanced, decentralized applications on Bitcoin Cash.

Non-fungible tokens are critical for coordinating activity trustlessly between multiple covenants, enabling covenant-tracking tokens, depository child covenants, multithreaded covenants, and other constructions in which a particular covenant instance must be authenticated.

Fungible tokens are valuable for covenants to represent on-chain assets – e.g. voting shares, utility tokens, collateralized loans, prediction market options, etc. – and implement complex coordination tasks – e.g. liquidity-pooling, auctions, voting, sidechain withdrawals, spin-offs, mergers, and more.

Universal Token Primitives

By exposing basic, consensus-validated token primitives, this proposal supports the development of higher-level, interoperable token standards (e.g. SLP). Token primitives can be held by any contract, wallets can easily verify the authenticity of a token or group of tokens, and tokens cannot be inadvertently destroyed by wallet software that does not support tokens.

Technical Summary

  1. A token category can include both non-fungible and fungible tokens, and every category is represented by a 32-byte category identifier – the transaction ID of the outpoint spent to create the category.
    1. All fungible tokens for a category must be created when the token category is created, ensuring the total supply within a category remains below the maximum VM number.
    2. Non-fungible tokens may be created either at category creation or in later transactions that spend tokens with minting or mutable capabilities for that category.
  2. Transaction outputs are extended to support four new token fields; every output can include one non-fungible token and any amount of fungible tokens from a single token category.
  3. Token inspection opcodes allow contracts to operate on tokens, enabling cross-contract interfaces and decentralized applications.

Transaction Output Data Model

This proposal extends the data model of transaction outputs to add four new token fields: token category, non-fungible token capability, non-fungible token commitment, and fungible token amount.

Existing Fields Description
Value The value of the output in satoshis, the smallest unit of bitcoin cash. (A.K.A. vout)
Locking Bytecode The VM bytecode used to encumber this transaction output. (A.K.A. scriptPubKey)
Token Fields (New, optional fields added by this proposal:)
Category ID The 32-byte ID of the token category to which the token(s) in this output belong. This field is omitted if no tokens are present.
Capability The capability of the NFT held in this output: none, mutable, or minting. This field is omitted if no NFT is present.
Commitment The commitment contents of the NFT held in this output (0 to 40 bytes). This field is omitted if no NFT is present.
Amount The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). This field is omitted if no fungible tokens are present.
Transaction Output JSON Format

The following snippet demonstrates a JSON representation of a transaction output using TypeScript types.

A new, optional token property is added, and the existing lockingBytecode and valueSatoshis properties are unmodified.

Note, this type is used by the test vectors.

/**
 * Data type representing a Transaction Output.
 */
type Output = {
  /**
   * The bytecode used to encumber this transaction output. To spend the output,
   * unlocking bytecode must be included in a transaction input that – when
   * evaluated before the locking bytecode – completes in a valid state.
   *
   * A.K.A. `scriptPubKey` or "locking script"
   */
  lockingBytecode: Uint8Array;

  /**
   * The CashToken contents of this output. This property is only defined if the
   * output contains one or more tokens.
   */
  token?: {
    /**
     * The number of fungible tokens held in this output.
     *
     * Because `Number.MAX_SAFE_INTEGER` (`9007199254740991`) is less than the
     * maximum token amount (`9223372036854775807`), this value is encoded as
     * a `bigint`. (Note, because standard JSON does not support `bigint`, this
     * value must be converted to and from a `string` to pass over the network.)
     */
    amount: bigint;
    /**
     * The 32-byte token category ID to which the token(s) in this output belong
     * in big-endian byte order. This is the byte order typically seen in block
     * explorers and user interfaces (as opposed to little-endian byte order,
     * which is used in standard P2P network messages).
     */
    category: Uint8Array;
    /**
     * If present, the non-fungible token (NFT) held by this output. If the
     * output does not include a non-fungible token, `undefined`.
     */
    nft?: {
      /**
       * The capability of this non-fungible token.
       */
      capability: 'none' | 'mutable' | 'minting';

      /**
       * The commitment contents included in the non-fungible token held in
       * this output.
       */
      commitment: Uint8Array;
    };
  };

  /**
   * The value of the output in satoshis, the smallest unit of bitcoin cash.
   */
  valueSatoshis: number;
};

Technical Specification

Subsections

Token primitives are defined, token encoding and activation are specified, and six new token inspection opcodes are introduced. Transaction validation and transaction signing serialization is modified to support tokens, SIGHASH_UTXOS is specified, BIP69 sorting is extended to support tokens, and CashAddress types with token support are defined.

Token Categories

Every token belongs to a token category specified via an immutable, 32-byte Token Category ID assigned in the category's genesis transaction – the transaction in which the token category is initially created.

Every token category ID is a transaction ID: the ID must be selected from the inputs of its genesis transaction, and only token genesis inputs – inputs which spend output 0 of their parent transaction – are eligible (i.e. outpoint transaction hashes of inputs with an outpoint index of 0). As such, implementations can locate the genesis transaction of any category by identifying the transaction that spent the 0th output of the transaction referenced by the category ID. (See Use of Transaction IDs as Token Category IDs.)

Note that because every transaction has at least one output, every transaction ID can later become a token category ID.

CashToken Creation Figure 1. Two new token categories are created by transaction c3a601.... The first category (b201a0...) is created by spending the 0th output of transaction b201a0...; for this category, a supply of 150 fungible tokens are created across two outputs (100 and 50). The second category (a1efcd...) is created by spending the 0th output of transaction a1efcd...; for this category, a supply of 100 fungible tokens are created across two outputs (20 and 80), and two NFTs are created (each with a commitment of 0x010203).

Token Types

Two token types are introduced: fungible tokens and non-fungible tokens. Fungible tokens have only one property: a 32-byte category. Non-fungible tokens have three properties: a 32-byte category, a 0 to 40 byte commitment, and a capability of minting, mutable, or none.

Token Behavior

Token behavior is enforced by the token validation algorithm. This algorithm has the following effects:

Universal Token Behavior

  1. A single transaction can create multiple new token categories, and each category can contain both fungible and non-fungible tokens.
  2. Tokens can be implicitly destroyed by omission from a transaction's outputs.
  3. Each transaction output can contain zero or one non-fungible token and any amount of fungible tokens, but all tokens in an output must share the same token category.

Non-Fungible Token Behavior

  1. A transaction output can contain zero or one non-fungible token.
  2. Non-fungible tokens (NFTs) of a particular category are created either in the category's genesis transaction or by later transactions that spend minting or mutable tokens of the same category.
  3. It is possible for multiple NFTs of the same category to carry the same commitment. (Though uniqueness can be enforced by covenants.)
  4. Minting tokens (NFTs with the minting capability) allow the spending transaction to create any number of new NFTs of the same category, each with any commitment and (optionally) the minting or mutable capability.
  5. Each Mutable token (NFTs with the mutable capability) allows the spending transaction to create one NFT of the same category, with any commitment and (optionally) the mutable capability.
  6. Immutable tokens (NFTs without a capability) cannot have their commitment modified when spent.

Fungible Token Behavior

  1. A transaction output can contain any amount of fungible tokens from a single category.
  2. All fungible tokens of a category are created in that category's genesis transaction; their combined amount may not exceed 9223372036854775807.
  3. A transaction can spend fungible tokens from any number of UTXOs to any number of outputs, so long as the sum of output amounts do not exceed the sum of input amounts (for each token category).

Note that fungible tokens behave independently from non-fungible tokens: non-fungible tokens are never counted in the amount, and the existence of minting or mutable NFTs in a transaction's inputs do not allow for new fungible tokens to be created.

Token Encoding

Tokens are encoded in outputs using a token prefix, a data structure that can encode a token category, zero or one non-fungible token (NFT), and an amount of fungible tokens (FTs).

For backwards-compatibility with existing transaction decoding implementations, a transaction output's token prefix (if present) is encoded before index 0 of its locking bytecode, and the CompactSize length preceding the two fields is increased to cover both fields (such that the length could be renamed token_prefix_and_locking_bytecode_length). The token prefix is not part of the locking bytecode and must not be included in bytecode evaluation. To illustrate, after deployment, the serialized output format becomes:

<satoshi_value> <token_prefix_and_locking_bytecode_length> [PREFIX_TOKEN <token_data>] <locking_bytecode>

Token Prefix

PREFIX_TOKEN is defined at codepoint 0xef (239) and indicates the presence of a token prefix:

PREFIX_TOKEN <category_id> <token_bitfield> [nft_commitment_length nft_commitment] [ft_amount]
  1. <category_id> – After the PREFIX_TOKEN byte, a 32-byte Token Category ID is required, encoded in OP_HASH256 byte order1.
  2. <token_bitfield> - A bitfield encoding two 4-bit fields is required:
    1. prefix_structure (token_bitfield & 0xf0) - 4 bitflags, defined at the higher half of the bitfield, indicating the structure of the token prefix:
      1. 0x80 (0b10000000) - RESERVED_BIT, must be unset.
      2. 0x40 (0b01000000) - HAS_COMMITMENT_LENGTH, the prefix encodes a commitment length and commitment.
      3. 0x20 (0b00100000) - HAS_NFT, the prefix encodes a non-fungible token.
      4. 0x10 (0b00010000) - HAS_AMOUNT, the prefix encodes an amount of fungible tokens.
    2. nft_capability (token_bitfield & 0x0f) – A 4-bit value, defined at the lower half of the bitfield, indicating the non-fungible token capability, if present.
      1. If not HAS_NFT: must be 0x00.
      2. If HAS_NFT:
        1. 0x00 – No capability – the encoded non-fungible token is an immutable token.
        2. 0x01 – The mutable capability – the encoded non-fungible token is a mutable token.
        3. 0x02 – The minting capability – the encoded non-fungible token is a minting token.
        4. Values greater than 0x02 are reserved and must not be used.
  3. If HAS_COMMITMENT_LENGTH:
    1. commitment_length – A commitment length is required (minimally-encoded in CompactSize format2) with a minimum value of 1 (0x01).
    2. commitment – The non-fungible token's commitment byte string of commitment_length is required.
  4. If HAS_AMOUNT:
    1. ft_amount – An amount of fungible tokens is required (minimally-encoded in CompactSize format2) with a minimum value of 1 (0x01) and a maximum value equal to the maximum VM number, 9223372036854775807 (0xffffffffffffff7f).
Notes
  1. This is the byte order produced/required by all BCH VM operations which employ SHA-256 (including OP_SHA256 and OP_HASH256), the byte order used for outpoint transaction hashes in the P2P transaction format, and the byte order produced by most SHA-256 libraries. For reference, the genesis block header in this byte order is little-endian – 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 – and can be produced by this script: <0x0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c> OP_HASH256. (Note, this is the opposite byte order as is commonly used in user interfaces like block explorers.)
  2. The CompactSize Format is a variable-length, little-endian, positive integer format used to indicate the length of the following byte array in Bitcoin Cash P2P protocol message formats (present since the protocol's publication in 2008). The format historically allowed some values to be encoded in multiple ways; token prefixes must always use minimally-encoded/canonically-encoded CompactSizes, e.g. the value 1 must be encoded as 0x01 rather than 0xfd0100, 0xfe0100000, or 0xff010000000000000.

Token Prefix Validation

  1. By consensus, commitment_length is limited to 40 (0x28), but future upgrades may increase this limit. Implementers are advised to ensure that values between 253 (0xfdfd00) and 65535 (0xfdffff) can be parsed. (See Non-Fungible Token Commitment Length.)
  2. A token prefix encoding no tokens (both HAS_NFT and HAS_AMOUNT are unset) is invalid.
  3. A token prefix encoding HAS_COMMITMENT_LENGTH without HAS_NFT is invalid.
  4. A token prefix where HAS_NFT is unset must encode nft_capability of 0x00.

Token Prefix Standardness

Implementations must recognize otherwise-standard outputs with token prefixes as standard.

Token Prefix Encoding Test Vectors

The following test vectors demonstrate valid, reserved, and invalid token prefix encodings. The token category ID is 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb and commitments use repetitions of 0xcc.

For the complete set of test vectors, see Test Vectors.

Valid Token Prefix Encodings

Description Encoded (Hex)
no NFT; 1 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb1001
no NFT; 252 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb10fc
no NFT; 253 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb10fdfd00
no NFT; 9223372036854775807 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb10ffffffffffffffff7f
0-byte immutable NFT; 0 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb20
0-byte immutable NFT; 1 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb3001
0-byte immutable NFT; 253 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb30fdfd00
0-byte immutable NFT; 9223372036854775807 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb30ffffffffffffffff7f
1-byte immutable NFT; 0 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb6001cc
1-byte immutable NFT; 252 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7001ccfc
2-byte immutable NFT; 253 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7002ccccfdfd00
10-byte immutable NFT; 65535 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb700accccccccccccccccccccfdffff
40-byte immutable NFT; 65536 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7028ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccfe00000100
0-byte, mutable NFT; 0 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb21
0-byte, mutable NFT; 4294967295 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb31feffffffff
1-byte, mutable NFT; 0 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb6101cc
1-byte, mutable NFT; 4294967296 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7101ccff0000000001000000
2-byte, mutable NFT; 9223372036854775807 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7102ccccffffffffffffffff7f
10-byte, mutable NFT; 1 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb710acccccccccccccccccccc01
40-byte, mutable NFT; 252 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7128ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccfc
0-byte, minting NFT; 0 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb22
0-byte, minting NFT; 253 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb32fdfd00
1-byte, minting NFT; 0 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb6201cc
1-byte, minting NFT; 65535 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7201ccfdffff
2-byte, minting NFT; 65536 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7202ccccfe00000100
10-byte, minting NFT; 4294967297 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb720accccccccccccccccccccff0100000001000000
40-byte, minting NFT; 9223372036854775807 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7228ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccffffffffffffffff7f
Reserved Token Prefix Encodings

These encodings are valid but disabled due to excessive commitment_lengths. Transactions attempting to create outputs with these token prefixes are currently rejected by consensus, but future upgrades may increase the maximum valid commitment_length.

Description Encoded (Hex)
41-byte immutable NFT; 65536 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7029ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccfe00000100
41-byte, mutable NFT; 252 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7129ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccfc
41-byte, minting NFT; 9223372036854775807 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7229ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccffffffffffffffff7f
253-byte, immutable NFT; 0 fungible efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb60fdfd00cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc

Invalid Token Prefix Encodings

Reason Encoded (Hex)
Token prefix must encode at least one token efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb00
Token prefix must encode at least one token (0 fungible) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb1000
Token prefix requires a token category ID ef
Token category IDs must be 32 bytes efbbbbbbbb1001
Missing token bitfield efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Token bitfield sets reserved bit efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb9001
Unknown capability (0-byte NFT, capability 3) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb23
Has commitment length without NFT (1 fungible) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb5001
Prefix encodes a capability without an NFT efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb1101
Commitment length must be specified (immutable token) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb60
Commitment length must be specified (mutable token) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb61
Commitment length must be specified (minting token) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb62
Commitment length must be minimally-encoded efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb60fd0100cc
If specified, commitment length must be greater than 0 efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb6000
Not enough bytes remaining in locking bytecode to satisfy commitment length (0/1 bytes) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb6001
Not enough bytes remaining in locking bytecode to satisfy commitment length (mutable token, 0/1 bytes) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb6101
Not enough bytes remaining in locking bytecode to satisfy commitment length (mutable token, 1/2 bytes) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb6102cc
Not enough bytes remaining in locking bytecode to satisfy commitment length (minting token, 1/2 bytes) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb6202cc
Not enough bytes remaining in locking bytecode to satisfy token amount (no NFT, 1-byte amount) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb10
Not enough bytes remaining in locking bytecode to satisfy token amount (no NFT, 2-byte amount) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb10fd00
Not enough bytes remaining in locking bytecode to satisfy token amount (no NFT, 4-byte amount) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb10fe000000
Not enough bytes remaining in locking bytecode to satisfy token amount (no NFT, 8-byte amount) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb10ff00000000000000
Not enough bytes remaining in locking bytecode to satisfy token amount (immutable NFT, 1-byte amount) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7001cc
Not enough bytes remaining in locking bytecode to satisfy token amount (immutable NFT, 2-byte amount) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7001ccfd00
Not enough bytes remaining in locking bytecode to satisfy token amount (immutable NFT, 4-byte amount) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7001ccfe000000
Not enough bytes remaining in locking bytecode to satisfy token amount (immutable NFT, 8-byte amount) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7001ccff00000000000000
Token amount must be specified efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb30
If specified, token amount must be greater than 0 (no NFT) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb1000
If specified, token amount must be greater than 0 (0-byte NFT) efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb3000
Token amount must be minimally-encoded efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb10fd0100
Token amount (9223372036854775808) may not exceed 9223372036854775807 efbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb30ff0000000000000080

Token Encoding Activation

Pre-activation token-forgery outputs (PATFOs) are transaction outputs mined in blocks prior to activation of this specification where locking bytecode index 0 is set to the PREFIX_TOKEN codepoint.

Prior to activation, PATFOs remain nonstandard but do not invalidate the transaction by consensus. Because they can still be mined in valid blocks, PATFOs can be used to prepare outputs that, after activation of this specification, could encode tokens for which Token-Aware Transaction Validation was not enforced (producing token categories that do not map to a confirmed transaction hash or have a fungible token supply exceeding the maximum amount).

Note, even properly-encoded token outputs included in transactions mined prior to activation are considered PATFOs, regardless of whether or not the transaction would pass token-aware transaction validation after activation. Due to the possibility of a chain re-organization impacting the precise activation time, token issuers are advised to wait until activation is confirmed to a depth of at least 11 blocks before broadcasting critical transactions involving tokens.

PATFOs are provably unspendable1; all software implementing this specification should immediately mark PATFOs as unspendable and/or logically treat them as such for the purposes of spending.

By consensus, PATFOs mined in blocks prior to the activation of Token-Aware Transaction Validation must remain unspendable after activation. (Please note: the presence of PATFOs does not render a transaction invalid; until activation, valid blocks may contain PATFOs.)

After activation, any transaction creating an invalid token prefix2 is itself invalid, and all transactions must pass Token-Aware Transaction Validation.

Notes
  1. For pre-activation token-forgery outputs (PATFOs), this has been the case for even longer than OP_RETURN outputs: PATFOs have been provably unspendable since the Bitcoin Cash protocol's publication in 2008.
  2. That is, any transaction output where locking bytecode index 0 is set to the PREFIX_TOKEN codepoint, but a valid token prefix cannot be parsed.

Token-Aware Transaction Validation

For any transaction to be valid, the token validation algorithm must succeed.

Token Validation Algorithm

Given the following definitions:

  1. Reducing the set of UTXOs spent by the transaction:
    1. A key-value map of Available_Sums_By_Category (mapping category IDs to positive, 64-bit integers) is created by summing the input amount of each token category.
    2. A key-value map of Available_Mutable_Tokens_By_Category (mapping category IDs to positive integers) is created by summing the count of input mutable tokens for each category.
    3. A list of Genesis_Categories is created including the outpoint transaction hash of each input with an outpoint index of 0 (i.e. the spent UTXO was the 0th output in its transaction).
    4. A de-duplicated list of Input_Minting_Categories is created including the category ID of each input minting token.
    5. A list of Available_Minting_Categories is the combination of Genesis_Categories and Input_Minting_Categories.
    6. A list of all Available_Immutable_Tokens (including duplicates) is created including each NFT which does not have a minting or mutable capability.
  2. Reducing the set of outputs created by the transaction:
    1. A key-value map of Output_Sums_By_Category (mapping category IDs to positive, 64-bit integers) is created by summing the output amount of each token category.
    2. A key-value map of Output_Mutable_Tokens_By_Category (mapping category IDs to positive integers) is created by summing the count of output mutable tokens for each category.
    3. A de-duplicated list of Output_Minting_Categories is created including the category ID of each output minting token.
    4. A list of all Output_Immutable_Tokens (including duplicates) is created including each NFT which does not have a minting or mutable capability.

Perform the following validations:

  1. Each category in Output_Minting_Categories must exist in Available_Minting_Categories.
  2. Each category in Output_Sums_By_Category must either:
    1. Have an equal or greater sum in Available_Sums_By_Category, or
    2. Exist in Genesis_Categories and have an output sum no greater than 9223372036854775807 (the maximum VM number).
  3. For each category in Output_Mutable_Tokens_By_Category, if the token's category ID exists in Available_Minting_Categories, skip this (valid) category. Else:
    1. Deduct the sum in Output_Mutable_Tokens_By_Category from the sum available in Available_Mutable_Tokens_By_Category. If the value falls below 0, fail validation.
  4. For each token in Output_Immutable_Tokens, if the token's category ID exists in Available_Minting_Categories, skip this (valid) token. Else:
    1. If an equivalent token exists in Available_Immutable_Tokens (comparing both category ID and commitment), remove it and continue to the next token. Else:
      1. Deduct 1 from the sum available for the token's category in Available_Mutable_Tokens_By_Category. If no mutable tokens are available to downgrade, fail validation.

Note: because coinbase transactions have only one input with an outpoint index of 4294967295, coinbase transactions can never include a token prefix in any output.

See Implementations for examples of this algorithm in multiple programming languages.

Token Inspection Operations

The following 6 operations pop the top item from the stack as an index (VM Number) and push a single result to the stack. If the consumed value is not a valid, minimally-encoded index for the operation, an error is produced.

Name Codepoint Description
OP_UTXOTOKENCATEGORY 0xce (206) Pop the top item from the stack as an input index (VM Number). If the Unspent Transaction Output (UTXO) spent by that input includes no tokens, push a 0 (VM Number) to the stack. If the UTXO does not include a non-fungible token with a capability, push the UTXO's token category, otherwise, push the concatenation of the token category and capability, where the mutable capability is represented by 1 (VM Number) and the minting capability is represented by 2 (VM Number).
OP_UTXOTOKENCOMMITMENT 0xcf (207) Pop the top item from the stack as an input index (VM Number). Push the token commitment of the Unspent Transaction Output (UTXO) spent by that input to the stack. If the UTXO does not include a non-fungible token, or if it includes a non-fungible token with a zero-length commitment, push a 0 (VM Number).
OP_UTXOTOKENAMOUNT 0xd0 (208) Pop the top item from the stack as an input index (VM Number). Push the fungible token amount of the Unspent Transaction Output (UTXO) spent by that input to the stack as a VM Number. If the UTXO includes no fungible tokens, push a 0 (VM Number).
OP_OUTPUTTOKENCATEGORY 0xd1 (209) Pop the top item from the stack as an output index (VM Number). If the output at that index includes no tokens, push a 0 (VM Number) to the stack. If the output does not include a non-fungible token with a capability, push the output's token category, otherwise, push the concatenation of the token category and capability, where the mutable capability is represented by 1 (VM Number) and the minting capability is represented by 2 (VM Number).
OP_OUTPUTTOKENCOMMITMENT 0xd2 (210) Pop the top item from the stack as an output index (VM Number). Push the token commitment of the output at that index to the stack. If the output does not include a non-fungible token, or if it includes a non-fungible token with a zero-length commitment, push a 0 (VM Number).
OP_OUTPUTTOKENAMOUNT 0xd3 (211) Pop the top item from the stack as an output index (VM Number). Push the fungible token amount of the output at that index to the stack as a VM Number. If the output includes no fungible tokens, push a 0 (VM Number).
Token Inspection Operation Test Vectors

The following test vectors demonstrate the expected result of each token inspection operation for a particular output. The token category ID is 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb and commitments use repetitions of 0xcc.

For the complete set of test vectors, see Test Vectors.

Description OP_UTXOTOKENCATEGORY/OP_OUTPUTTOKENCATEGORY (Hex) OP_UTXOTOKENCOMMITMENT/OP_OUTPUTTOKENCOMMITMENT (Hex) OP_UTXOTOKENAMOUNT/OP_OUTPUTTOKENAMOUNT (Hex)
no NFT; 0 fungible (empty item) (empty item) (empty item)
no NFT; 1 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb (empty item) 01
no NFT; 252 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb (empty item) fc
no NFT; 253 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb (empty item) fd00
no NFT; 9223372036854775807 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb (empty item) ffffffffffffff7f
0-byte immutable NFT; 0 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb (empty item) (empty item)
0-byte immutable NFT; 1 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb (empty item) 01
0-byte immutable NFT; 253 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb (empty item) fd00
0-byte immutable NFT; 9223372036854775807 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb (empty item) ffffffffffffff7f
1-byte immutable NFT; 252 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cc fc
2-byte immutable NFT; 253 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccc fd00
10-byte immutable NFT; 65535 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccc ffff
40-byte immutable NFT; 65536 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc 00000100
0-byte, mutable NFT; 0 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb01 (empty item) (empty item)
0-byte, mutable NFT; 4294967295 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb01 (empty item) ffffffff
1-byte, mutable NFT; 4294967296 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb01 cc 0000000001000000
2-byte, mutable NFT; 9223372036854775807 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb01 cccc ffffffffffffff7f
10-byte, mutable NFT; 1 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb01 cccccccccccccccccccc 01
40-byte, mutable NFT; 252 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb01 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc fc
0-byte, minting NFT; 0 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb02 (empty item) (empty item)
0-byte, minting NFT; 253 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb02 (empty item) fd00
1-byte, minting NFT; 65535 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb02 cc ffff
2-byte, minting NFT; 65536 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb02 cccc 00000100
10-byte, minting NFT; 4294967297 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb02 cccccccccccccccccccc 0100000001000000
40-byte, minting NFT; 9223372036854775807 fungible bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb02 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc ffffffffffffff7f

Existing Introspection Operations

Note, this specification has no impact on the behavior of OP_UTXOBYTECODE, OP_ACTIVEBYTECODE, or OP_OUTPUTBYTECODE. Each operation continues to return only the contents of the respective bytecode, excluding both the CompactSize-encoded "token_prefix_and_locking_bytecode_length" and the token prefix (if present).

Interpretation of Signature Preimage Inspection

It is possible to design contracts which inefficiently inspect the encoding of tokens using signature preimage inspection – inspecting the contents of a preimage for which a signature passes both OP_CHECKSIG(VERIFY) and OP_CHECKDATASIG(VERIFY).

This specification interprets all signature preimage inspection of tokens as intentional: these constructions are designed to succeed or fail based on the encoding of the signature preimage, and they can be used (by design) to test for 1) the availability of some types of proposed-but-not-activated upgrades, and/or 2) a contracts' presence on a fork of Bitcoin Cash. This notice codifies a network policy: the possible existence of these contracts will not preclude future upgrades from adding additional output prefix or transaction formats. (The security of a contract is the responsibility of the entity locking funds in that contract; funds can always be locked in insecure contracts, e.g. OP_DROP OP_1.)

Contract authors are advised to use Token Inspection Operations for all constructions intended to inspect the actual properties of tokens within a transaction.

Signing Serialization of Tokens

The signing serialization algorithm (A.K.A SIGHASH algorithm) is enhanced to support tokens: when evaluating a UTXO that includes tokens, the full, encoded token prefix (including PREFIX_TOKEN) must be included immediately before the coveredBytecode (A.K.A. scriptCode). Note: this behavior applies for all signing serialization types in the evaluation; it does not require a signing serialization type/flag.

SIGHASH_UTXOS

A new signing serialization type, SIGHASH_UTXOS, is defined at 0x20 (32/0b100000). When SIGHASH_UTXOS is enabled, hashUtxos is inserted in the signing serialization algorithm immediately following hashPrevouts. hashUtxos is a 32-byte double SHA256 of the serialization of all UTXOs spent by the transaction's inputs, concatenated in input order, excluding output count. (Note: this serialization is equivalent to the segment of a P2P transaction message beginning after output count and ending before locktime if the UTXOs were serialized in order as the transaction's outputs.)

The SIGHASH_UTXOS and SIGHASH_ANYONECANPAY types must not be used together; if a signature in which both flags are enabled is encountered during VM evaluation, an error is emitted (evaluation fails).

The SIGHASH_UTXOS type must be used with the SIGHASH_FORKID type; if a signature is encountered during VM evaluation with the SIGHASH_UTXOS flag and without the SIGHASH_FORKID flag, an error is emitted (evaluation fails).

For security, wallets should enable SIGHASH_UTXOS when participating in multi-entity transactions. This includes both 1) transactions where signatures are collected from multiple keys and assembled into a single transaction, and 2) transactions involving contracts that can be influenced by multiple entities (e.g. covenants). (See Recommendation of SIGHASH_UTXOS for Multi-Entity Transactions.)

Double Spend Proof Support

Transactions employing SIGHASH_UTXOS and transactions spending outputs containing tokens are not protected by either of the beta specifications for Double Spend Proofs (DSP). Support for these features are left to future proposals.

CashAddress Token Support

Two new CashAddress types are specified to indicate support for accepting tokens:

Type Bits Meaning
2 (0b0010) Token-Aware P2PKH
3 (0b0011) Token-Aware P2SH

Token-aware wallet software – wallet software which supports management of tokens – may use these CashAddress version byte values to signal token support.

Token-aware wallet software must refuse to send tokens to addresses without explicit token support i.e. P2PKH CashAddresses (type bits: 0b0000), P2SH CashAddresses (type bits: 0b0001), and legacy Base58 addresses.

Token-Aware CashAddress Test Vectors

Test vectors for the CashAddress format have been standardized and widely used since 2017, including test vectors for not-yet-defined type values.

While this specification simply uses the available type values, to assist implementers, the below test vectors have been added to the existing CashAddress test vectors in test-vectors/cashaddr.json. (For details, see Test Vectors.)

Token-Aware CashAddresses

CashAddress Type Bits Size Bits Payload (Hex)
bitcoincash:qr7fzmep8g7h7ymfxy74lgc0v950j3r2959lhtxxsl 0 (P2PKH) 0 (20 bytes) fc916f213a3d7f1369313d5fa30f6168f9446a2d
bitcoincash:zr7fzmep8g7h7ymfxy74lgc0v950j3r295z4y4gq0v 2 (Token-Aware P2PKH) 0 (20 bytes) fc916f213a3d7f1369313d5fa30f6168f9446a2d
bchtest:qr7fzmep8g7h7ymfxy74lgc0v950j3r295pdnvy3hr 0 (P2PKH) 0 (20 bytes) fc916f213a3d7f1369313d5fa30f6168f9446a2d
bchtest:zr7fzmep8g7h7ymfxy74lgc0v950j3r295x8qj2hgs 2 (Token-Aware P2PKH) 0 (20 bytes) fc916f213a3d7f1369313d5fa30f6168f9446a2d
bchreg:qr7fzmep8g7h7ymfxy74lgc0v950j3r295m39d8z59 0 (P2PKH) 0 (20 bytes) fc916f213a3d7f1369313d5fa30f6168f9446a2d
bchreg:zr7fzmep8g7h7ymfxy74lgc0v950j3r295umknfytk 2 (Token-Aware P2PKH) 0 (20 bytes) fc916f213a3d7f1369313d5fa30f6168f9446a2d
prefix:qr7fzmep8g7h7ymfxy74lgc0v950j3r295fu6e430r 0 (P2PKH) 0 (20 bytes) fc916f213a3d7f1369313d5fa30f6168f9446a2d
prefix:zr7fzmep8g7h7ymfxy74lgc0v950j3r295wkf8mhss 2 (Token-Aware P2PKH) 0 (20 bytes) fc916f213a3d7f1369313d5fa30f6168f9446a2d
bitcoincash:qpagr634w55t4wp56ftxx53xukhqgl24yse53qxdge 0 (P2PKH) 0 (20 bytes) 7a81ea357528bab834d256635226e5ae047d5524
bitcoincash:zpagr634w55t4wp56ftxx53xukhqgl24ys77z7gth2 2 (Token-Aware P2PKH) 0 (20 bytes) 7a81ea357528bab834d256635226e5ae047d5524
bitcoincash:qq9l9e2dgkx0hp43qm3c3h252e9euugrfc6vlt3r9e 0 (P2PKH) 0 (20 bytes) 0bf2e54d458cfb86b106e388dd54564b9e71034e
bitcoincash:zq9l9e2dgkx0hp43qm3c3h252e9euugrfcaxv4l962 2 (Token-Aware P2PKH) 0 (20 bytes) 0bf2e54d458cfb86b106e388dd54564b9e71034e
bitcoincash:qre24q38ghy6k3pegpyvtxahu8q8hqmxmqqn28z85p 0 (P2PKH) 0 (20 bytes) f2aa822745c9ab44394048c59bb7e1c07b8366d8
bitcoincash:zre24q38ghy6k3pegpyvtxahu8q8hqmxmq8eeevptj 2 (Token-Aware P2PKH) 0 (20 bytes) f2aa822745c9ab44394048c59bb7e1c07b8366d8
bitcoincash:qz7xc0vl85nck65ffrsx5wvewjznp9lflgktxc5878 0 (P2PKH) 0 (20 bytes) bc6c3d9f3d278b6a8948e06a399974853097e9fa
bitcoincash:zz7xc0vl85nck65ffrsx5wvewjznp9lflg3p4x6pp5 2 (Token-Aware P2PKH) 0 (20 bytes) bc6c3d9f3d278b6a8948e06a399974853097e9fa
bitcoincash:ppawqn2h74a4t50phuza84kdp3794pq3ccvm92p8sh 1 (P2SH) 0 (20 bytes) 7ae04d57f57b55d1e1bf05d3d6cd0c7c5a8411c6
bitcoincash:rpawqn2h74a4t50phuza84kdp3794pq3cct3k50p0y 3 (Token-Aware P2SH) 0 (20 bytes) 7ae04d57f57b55d1e1bf05d3d6cd0c7c5a8411c6
bitcoincash:pqv53dwyatxse2xh7nnlqhyr6ryjgfdtagkd4vc388 1 (P2SH) 0 (20 bytes) 1948b5c4eacd0ca8d7f4e7f05c83d0c92425abea
bitcoincash:rqv53dwyatxse2xh7nnlqhyr6ryjgfdtag38xjkhc5 3 (Token-Aware P2SH) 0 (20 bytes) 1948b5c4eacd0ca8d7f4e7f05c83d0c92425abea
bitcoincash:prseh0a4aejjcewhc665wjqhppgwrz2lw5txgn666a 1 (P2SH) 0 (20 bytes) e19bbfb5ee652c65d7c6b54748170850e1895f75
bitcoincash:rrseh0a4aejjcewhc665wjqhppgwrz2lw5vvmd5u9w 3 (Token-Aware P2SH) 0 (20 bytes) e19bbfb5ee652c65d7c6b54748170850e1895f75
bitcoincash:pzltaslh7xnrsxeqm7qtvh0v53n3gfk0v5wwf6d7j4 1 (P2SH) 0 (20 bytes) bebec3f7f1a6381b20df80b65deca4671426cf65
bitcoincash:rzltaslh7xnrsxeqm7qtvh0v53n3gfk0v5fy6yrcdx 3 (Token-Aware P2SH) 0 (20 bytes) bebec3f7f1a6381b20df80b65deca4671426cf65
bitcoincash:pvqqqqqqqqqqqqqqqqqqqqqqzg69v7ysqqqqqqqqqqqqqqqqqqqqqpkp7fqn0 1 (P2SH) 3 (32 bytes) 0000000000000000000000000000123456789000000000000000000000000000
bitcoincash:rvqqqqqqqqqqqqqqqqqqqqqqzg69v7ysqqqqqqqqqqqqqqqqqqqqqn9alsp2y 3 (Token-Aware P2SH) 3 (32 bytes) 0000000000000000000000000000123456789000000000000000000000000000
bitcoincash:pdzyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3jh2p5nn 1 (P2SH) 3 (32 bytes) 4444444444444444444444444444444444444444444444444444444444444444
bitcoincash:rdzyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygrpttc42c 3 (Token-Aware P2SH) 3 (32 bytes) 4444444444444444444444444444444444444444444444444444444444444444
bitcoincash:pwyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsh3sujgcr 1 (P2SH) 3 (32 bytes) 8888888888888888888888888888888888888888888888888888888888888888
bitcoincash:rwyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9zvatfpg 3 (Token-Aware P2SH) 3 (32 bytes) 8888888888888888888888888888888888888888888888888888888888888888
bitcoincash:p0xvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvcm6gz4t77 1 (P2SH) 3 (32 bytes) cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
bitcoincash:r0xvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvcff5rv284 3 (Token-Aware P2SH) 3 (32 bytes) cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
bitcoincash:p0llllllllllllllllllllllllllllllllllllllllllllllllll7x3vthu35 1 (P2SH) 3 (32 bytes) ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
bitcoincash:r0llllllllllllllllllllllllllllllllllllllllllllllllll75zs2wagl 3 (Token-Aware P2SH) 3 (32 bytes) ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

Token-Aware BIP69 Sorting Algorithm

The BIP69 transaction output sorting algorithm is extended to support the sorting of outputs by token information.

As with the existing algorithm, the additional fields are ordered for sorting efficiency:

  1. Output value – ascending
  2. locking bytecode – sorted lexicographically1, ascending (e.g. 0x51 < 0x5161)
  3. token information: (no tokens < has tokens)
    1. amount – ascending (e.g. 0 < 1)
    2. HAS_NFTfalse < true
      1. capability – ascending; none < mutable < minting
      2. commitment – sorted lexicographically, ascending, short-to-long; e.g. zero-length < 0x00 < 0x01 < 0x0100)
    3. category – sorted lexicographically, ascending, where bytes are in little-endian order (matching the order used in encoded transactions)

Notes

  1. For clarity, lexicographically-sorted fields must be compared byte-by-byte from start to end. Where one item is a prefix of another item, the shorter item is considered the "lower" value. E.g. the following values are sorted lexicographically in ascending order: 0x00, 0x0011, 0x11, 0x1100.

Fungible Token Supply Definitions

Several measurements of fungible token supply are standardized for wider ecosystem compatibility. (See Specification of Token Supply Definitions.)

By design, Genesis Supply, Reserved Supply, Circulating Supply, and Total Supply of any fungible token category will not exceed 9223372036854775807 (the maximum VM number).

Genesis Supply

A token category's genesis supply is an immutable, easily-computed, maximum possible supply, known since the token category's genesis transaction. It overestimates total supply if any amount of tokens have been destroyed since the genesis transaction.

The genesis supply of a fungible token category can be computed by parsing the outputs of the category's genesis transaction and summing the amount of fungible tokens matching the category's ID.

Total Supply

A token category's total supply is the sum – at a particular moment in time – of tokens which are either in circulation or may enter circulation in the future. A token category's total supply is always less than or equal to its genesis supply.

The total supply of a fungible token category can be computed by retrieving all UTXOs which contain token prefixes matching the category ID, removing provably-destroyed outputs (spent to OP_RETURN outputs), and summing the remaining amounts.

Software implementations should emphasize total supply in user interfaces for token categories which do not meet the requirements for emphasizing circulating supply.

Reserved Supply

A token category's reserved supply or unissued supply is the sum – at a particular moment in time – of tokens held in reserve by the issuing entity. This is the portion of the supply which the issuer represents as "not in circulation".

The reserved supply of a fungible token category can be computed by retrieving all UTXOs which contain token prefixes matching the category ID, removing provably-destroyed outputs (spent to OP_RETURN outputs), and summing the amounts held in prefixes which have either the minting or mutable capability.

Circulating Supply

A token category's circulating supply is the sum – at a particular moment in time – of tokens not held in reserve by the issuing entity. This is the portion of the supply which the issuer represents as "in circulation".

The circulating supply of a fungible token category can be computed by subtracting the reserved supply from the total supply.

Software implementations might choose to emphasize circulating supply (rather than total supply) in user interfaces for token categories which:

  • are issued by an entity trusted by the user, or
  • are issued by a covenant (of a construction known to the verifier) for which token issuance is limited (via a strategy trusted by the user).

Usage Examples

Rationale

Prior Art & Alternatives

Test Vectors

Sets of cross-implementation test vectors are provided in the test-vectors directory. Each set is described below.

Token-Aware CashAddress Test Vectors

cashaddr.json includes an updated set of test vectors including tests for token-aware CashAddresses.

Test vectors for the CashAddress format have been standardized and widely used since 2017, including test vectors for not-yet-defined type values.

While this specification simply uses the available type values, to assist implementers, several additional test vectors have been added to the existing CashAddress test vectors in test-vectors/cashaddr.json.

Token Encoding Test Vectors

A complete set of test vectors that validate token encoding can be found in test-vectors/token-prefix-valid.json and test-vectors/token-prefix-invalid.json, respectively.

Transaction Validation Test Vectors

The test-vectors/vmb_tests directory contains sets of transaction test vectors that validate all technical elements of this proposal.

To maximize portability between implementations, these test vectors use Libauth's full-transaction testing strategy. Test vectors are sorted into files based on their expected behavior:

Each test vector is an array including:

  1. A short, unique identifier for the test (based on the hash of the test contents)
  2. A string describing the purpose/behavior of the test
  3. The unlocking script under test (disassembled, i.e. human-readable)
  4. The locking script under test (disassembled)
  5. The full, encoded test transaction
  6. An encoded list of unspent transaction outputs (UTXOs) with which to verify the test transaction (ordered to match the input order of the test transaction)

Only array items 5 and 6 are strictly necessary; items 1 through 4 are purely informational, and may be useful in debugging and cross-implementation communication.

To use these test vectors, implementations should decode the transaction under test (5) and its UTXOs (6), then validate the transaction using all of the implementation's transaction validation infrastructure initialized in the expected standard/nonstandard mode(s). See Implementations for examples.

Implementations

Please see the following implementations for additional examples and test vectors:

Stakeholder Responses & Statements

Stakeholder Responses & Statements →

Feedback & Reviews

Acknowledgements

Thank you to the following contributors for reviewing and contributing improvements to this proposal, providing feedback, and promoting consensus among stakeholders: Calin Culianu, bitcoincashautist, imaginary_username, Joshua Green, Andrew Groot, Tom Zander, Andrew #128, Mathieu Geukens, Richard Brady, Marty Alcala, John Nieri, Jonathan Silverblood, Benjamin Scherrey, Rosco Kalis, Deyan Dimitrov, Jonas Lundqvist, Joemar Taganna, Rucknium.

Changelog

This section summarizes the evolution of this document.

  • v2.2.2 – 2022-5-20
    • Mention unissued supply as an alternative term for reserved supply
    • Mark CHIP as final
  • v2.2.1 – 2022-11-15 (7552da2d)
  • v2.2.0 – 2022-9-30 (e02012a2)
    • Compress token encoding using bitfield (#33)
    • Encode mutable capability as 0x01 and minting capability as 0x02
    • Revert to limiting commitment_length to 40 bytes by consensus (#23)
    • Revert PREFIX_TOKEN to a unique codepoint (0xef) (#41)
    • Modify OP_*TOKENCOMMITMENT to push 0 for zero-length commitments (#25)
    • Extend BIP69 sorting algorithm to support tokens (#60)
    • Specify activation times
    • Expand test vectors
    • Note non-support of beta specs for double spend proofs
    • Improve rationale
  • v2.1.0 – 2022-6-30 (f8b500a0)
    • Expand motivation, benefits, rationale, prior art & alternatives
    • Simplify token encoding, update test vectors
    • Set PREFIX_TOKEN to 0xd0 and limit commitment_length using standardness
    • Specify handling of pre-activation token-forgery outputs
    • Specify token-aware signing serialization algorithm and SIGHASH_UTXOS (#22)
  • v2.0.1 – 2022-2-25 (fcb110c3)
    • Expand rationale
    • Note impossibility of valid token outputs in coinbase transactions
  • v2.0.0 – 2022-2-22 (879c55ed)
    • Initial publication (versioning begins at v2 to differentiate from CashTokens v1)

Copyright

This document is placed in the public domain.

cashtokens's People

Contributors

2qx avatar a60ab5450353f40e avatar andrew-128 avatar bitjson avatar cculianu avatar christroutner avatar dagurval avatar damascene avatar dikel avatar fpelliccioni avatar imaginaryusername avatar kzkallisti avatar osoftware avatar rnbrady avatar rucknium avatar scherrey avatar sickpig avatar tomokazukozuma avatar zander avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cashtokens's Issues

Specify that `SIGHASH_UTXOS` must also have `SIGHASH_FORKID` otherwise sigHashType is invalid

While this is implicit because of the fact that SIGHASH_FORKID is always required after August 2017, it would be nice to also see this in the specification as an explicitly stated thing.

This would avoid some implementations from e.g. forgetting to check that this flag must be active and somehow accepting "old" blocks that were invalid but that happened to use 0x20 -- this concern I have is imaginary really because we checkpoint in our nodes but -- just in case, it is worth mentioning in the spec.

sighash question

The spec states;

The SIGHASH_UTXOS and SIGHASH_ANYONECANPAY types must not be used together; if a signature in which both flags are enabled is encountered during VM evaluation, an error is emitted (evaluation fails).

First of all, this should likely not be validatd in the VM, but before. But the real reason I'm opening this issue is because I don't think this should be a consensus rule. It feels very much like a standardness rule. Falls much more in line with the kind of checks that happen there anyway.

Implement explicit token genesis declaration as input's unlocking script PreFiX

With some tweaks, this can be adapted from Group v6.1

Rationale: https://bitcoincashresearch.org/t/chip-2022-02-cashtokens-token-primitives-for-bitcoin-cash/725/16

Input format

  • transaction inputs
    • input 0
      • previous output transaction hash, 32 raw bytes
      • previous output index, 4-byte uint
      • unlocking script length, compact variable length integer
      • unlocking script
        • OPTIONAL: PFX_TOKEN_GENESIS, 1-byte constant 0xEF
        • real unlocking script, variable number of raw bytes
      • sequence number, 4-byte uint
    • ...
    • input N

Note: same 0xEF codepoint can be used because it's another context - exclusive to inputs.

Genesis category ID derivation

categoryGenID == Hash(categoryGenPreimage), where:

  • Hash is the double SHA-256 hash function, and
  • categoryGenPreimage is byte concatenation of:
    1. Hash of the input's:
      - prevout transaction hash,
      - prevout index;
    2. Byte concatenation of genesis input parameters:
      - hash type,
      - has_nonfungible,
      - commitment length,
      - commitment,
      - amount;
    3. If hashType == 0x01 then append the double SHA-256 of the whole prevout UTXO serialization

Note: the input prefix will NOT be part of signature preimage, and doesn't need to be. See notes on malleability here.

Token logic validation

Add both the input's prevout token (if present) and the input-generated token to the input side tally of the transaction and enforce token logic over the sums all the same.

Introspection

In Script VM context, same codepoint 0xEF can be used to encode OP_INPUTTOKENGENESIS which would return concatenation of genesis parameters.

OP_UTXO... introspection opcodes will return the prevout's token if present, NOT the genesis one created with the input.

Rationale for introducing token-aware CashAddress type bits

Suggested rationale

Wallet Address Type

Because we require transactions involving categorized outputs to be signed using modified signing serialization, funds unintentionally sent to non-upgraded wallets can't be accidentally lost and will be recoverable by importing the recipient's wallet into upgraded software.

The existing CashAddress standard supports extending the address through the use of a type bit, which could be used to indicate wallet support for receiving tokens:

Encoding the size of the hash in the version field ensure that it is possible to check that the length of the address is correct.

Type bits Meaning Version byte value
0 P2KH 0
1 P2SH 8

Further types will be added as new features are added.

However we expect that, over time, most wallets will support receiving and sending tokens.
Introducing two more address types just to signal readiness to receive tokens would only have a temporary benefit, while the cost of having to maintain two more address encodings would be permanent.

Therefore, we do NOT recommend a new address type.

Risks - Wrong Wallet Implementation

If a wallet would obtain a categorized output but be unable to spend it, then it could cause it to temporarily become unusable until the issue is resolved.

This could happen in two ways:

  1. Non-upgraded and badly implemented wallet registering a categorized output as one of its own, which is unlikely and has been discussed;
  2. Upgraded wallet having a bug in transaction building, which can be expected since bugs happen, and not all wallets pass through the same quality assurance processes.

Burning funds by non-upgraded wallets will not be possible since they wouldn't be able to produce a valid signature, and it would at worst result in temporary loss of service - users unable to send their funds until the issue is resolved.
The same would hold for upgraded wallets that are upgraded but have a bug that would prevent them from producing valid transactions.

The riskiest class of bugs is that of an upgraded wallet able to produce signatures but failing to properly account amounts to create categorized "change" outputs that would capture amounts not intended to be spent.
This risk was already present with pure BCH wallet implementations, where the result would be a donation to miners in form of fees.
With this proposal, the risk applies the same to token amounts - but instead of being donated they'd be burned.

This is a risk external to protocol development, and the only mitigation available is to write good documentation and suggest using wallet software that has good quality-assurance processes.
Since most popular wallets have proper processes in place and upgrades are reviewed and tested by multiple developers, we consider this risk to be low.

Future Upgradeability

From #24 (comment)

E.g. even if we had a simple OP_OUTPUT_TOKEN_CAPABILITY that accepted an index and returned one of these above numbers (0, 3, 7, 15), while many contracts would be written to validate results by direct comparison ( OP_OUTPUT_TOKEN_CAPABILITY <3> OP_EQUAL), almost all byte-efficient contracts with multiple codepaths (based on capability) would be broken unpredictably: a contract might perform some validation for <=3, some for 7, and an OP_ELSE branch for the 15. If an upgrade slips a new 14 result into the set of possible results, it breaks the API used by that contract and produces a serious vulnerability.

This problem is not giving me rest. I first thought, well, if we make it so that new values are properly ordered, like if 14 is more restrictive than 7 and less restrictive than 15 then it should be no problem right? After all - it would not enable users to do new things. Well...

There's another class of problems: it could break public covenants. Imagine that anyone can access some covenant placed on immutable NFT (0x03) and then we enable making the Script field immutable too (0x01). What if the public covenant relies on updating the contract's locking script with each tick, and requires "capability > 0" and then we enable 0x01 someone comes and slips in the 0x01 and breaks the contract by essentially terminating it because it would not be allowed to change its own locking script anymore.

As you suggested, solution would be to define a new codepoint for the new behavior, so existing categories would continue to operate under rules that were in force when they were created. Interaction between old/new could also be troublesome if same introspection opcodes would be used for both, so what could we do?

How about defining the OP*CATEGORY to also return the PFX byte, the returned stack item would then be: PFX+ID+[nft_capability].

Also, imagine if in the future we define some scheme for fractional sats, like another PFX but without the ID, then we could probably use the same Script API (introspection opcodes) to interact with both CashTokens2.0 and some FracSats upgrade, the OP*CATEGORY would return 1-byte value for FracSats.

Actually, because CashTokens are first - they don't need to return the PFX+ID, plain ID is enough, but some later upgrade would have to somehow expose to the Script API that it's not the same thing as CashTokens2.0, or it would need to define new introspection opcodes...

just some thoughts about strategy to upgrade

Specify BIP69 sort order for txouts containing tokens

Summary

BIP69 sorts outputs in a txn lexicographically by:

  • amount
  • scriptPubKey

A specification of how to sort the new txouts with tokens in them may be warranted.

Suggestion

Since token data conceptually doesn't live inside scriptPubKey (except for serialization but NOT on the application layer), it is onerous and a performance hit for implementations e.g. BCHN to re-serialize the token data just for bip69 txn sorting.

BIP69's philosophy is to sort by "cheapest to compare first". As such this is how I am currently sorting token outputs in BCHN:

  • amount
  • scriptPubKey
  • tokenData

Where tokenData sorts as follows:

    return std::tie(amount, capability, commitment, id) < std::tie(o.amount, o.capability, o.commitment, o.id);

Additionally, if all things are equal, txouts with no token data should sort before txouts with token data.

I politely suggest that maybe you should specify the above as the BIP69 sort order for Cash Token txouts.

Specify that CompactSize must be "canonically encoded" and add test vectors that test this

Non canonically encoded CompactSize should be rejected as parse failure.

What is a canonically encoded CompactSize? Let's say you want to encode 40 (0x28). You have 4 options about how to encode it, but only one of them is canonical (it must be the minimally encoded representation, basically):

  • 0x28 <-- canonical
  • 0xfd2800 <-- not canonical (wastes space)
  • 0xfe28000000 <-- again, non-canonical (wastes space)
  • 0xff2800000000000000 <-- ditto

Single-prefix+bitfield token encoding

Proposed specification for the format, implements everything talked about in #27 #25 #24 #23

Because nft_commitment and ft_amount will be optional, then we get a more compact encoding for 0. So ft_amount==0 will be disallowed because 0-value FT will be encoded by absence of HAS_FT_AMOUNT format flag so parsers can know to skip the ft_amount. Similarly, nft_commitment_length==0 is disallowed because zero-byte commitment will be encoded by absence of HAS_NFT_COMMITMENT format flag so parsers can skip both nft_commitment_length and nft_commitment.

S1 Transaction Format Changes

Definitions

We define transaction output prefix byte that will indicate presence of optional category output annotation:

  • OUTPUT_PREFIX_BYTE_CATEGORY = 0xd0.

We define the maximum length for NFT messages:

  • MAX_SERIALIZED_NFT_COMMITMENT_LENGTH = 40.

S1.1 Transaction Outputs

If OUTPUT_PREFIX_BYTE_CATEGORY byte is encoded at index 0 of a transaction outputs's locking script serialization then it will mark the start of a data structure that will encode the output category annotation.
The whole annotation, including the prefix byte, will be removed from the locking script before handing it to Script VM:

<satoshi value><locking script length> [OUTPUT_PREFIX_BYTE_CATEGORY <category data>] <real locking script>.

Category output annotation is defined as:

OUTPUT_PREFIX_BYTE_CATEGORY <category_id> <token_format | nft_capability> [<nft_commitment_length><nft_commitment>] [ft_amount]

where:

  • category_id - a 32-byte value
  • token_format | nft_capability - a 1-byte value containing 2 fields where they are deserialized as:
    • token_format = serialized_byte & 0xf0 and
    • nft_capability = serialized_byte & 0x0f
  • nft_commitment_length - a compact-size uint value that may encode a 1, 2, 4, or 8 byte uint, skip if token_format & HAS_NFT_COMMITMENT == 0
  • nft_commitment - byte array of nft_message_length bytes, skip if (token_format & HAS_NFT_COMMITMENT == 0) || (nft_commitment_length == 0)
  • ft_amount - a compact-size uint value that may encode a 1, 2, 4, or 8 byte uint, skip if token_format & HAS_FT_AMOUNT == 0

Bit flags that indicate presence of optional fields are defined as:

  • HAS_NFT = 0x40,
  • HAS_NFT_COMMITMENT = 0x20,
  • HAS_FT_AMOUNT = 0x10.

Not all combinations will be allowed.

If token_format == 0x00 then the transaction shall fail immediately. (REQ-1.1.1.)

If (token_format & HAS_NFT == 0) && (token_format & HAS_NFT_COMMITMENT != 0) then the transaction shall fail immediately. (REQ-1.1.2.)

These two requirements leave us with 5 allowed values for token_format, listed in table below.

token_format bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 Note
0x10 (16) 0 0 0 1 0 0 0 0 FT
0x40 (64) 0 1 0 0 0 0 0 0 NFT
0x50 (80) 0 1 0 1 0 0 0 0 BOTH
0x60 (96) 0 1 1 0 0 0 0 0 NFT+commitment
0x70 (112) 0 1 1 1 0 0 0 0 BOTH+commitment

If token_format & HAS_NFT == 0 then nft_capability MUST be 0. (REQ-1.1.3.)

If token_format & HAS_NFT > 0 then nft_capability MUST be one of allowed values:

  • NFT_IMMUTABLE = 0x00,
  • NFT_MUTABLE = 0x01,
  • NFT_MINT = 0x02.

(REQ-1.1.4.)

When combined into the serialized byte, there will be 13 allowed states for the byte, as listed in table below.

token_format | nft_capability bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 Note
0x10 (16) 0 0 0 1 0 0 0 0 FT
0x40 (64) 0 1 0 0 0 0 0 0 Immutable NFT
0x41 (65) 0 1 0 0 0 0 0 1 Mutable NFT
0x42 (66) 0 1 0 0 0 0 1 0 Mint NFT
0x50 (80) 0 1 0 1 0 0 0 0 FT + Immutable NFT
0x51 (81) 0 1 0 1 0 0 0 1 FT + Mutable NFT
0x52 (82) 0 1 0 1 0 0 1 0 FT + Mint NFT
0x60 (96) 0 1 1 0 0 0 0 0 Immutable NFT + commitment
0x61 (97) 0 1 1 0 0 0 0 1 Mutable NFT + commitment
0x62 (98) 0 1 1 0 0 0 1 0 Mint NFT + commitment
0x70 (112) 0 1 1 1 0 0 0 0 FT + Immutable NFT + commitment
0x71 (113) 0 1 1 1 0 0 0 1 FT + Mutable NFT + commitment
0x72 (114) 0 1 1 1 0 0 1 0 FT + Mint NFT + commitment

When encoded, value of ft_amount must obey these limits: minimum value of 1 and maximum value of 9223372036854775807 (0xffffffffffffff7f). (REQ-1.1.5.)

When encoded, value of nft_commitment_length must obey these limits: minimum value of 1 and maximum value of MAX_SERIALIZED_NFT_COMMITMENT_LENGTH. (REQ-1.1.6)

The annotation SHALL NOT count against length limits placed on locking script. (REQ-1.1.7)

Reserved Supply

The spec today says this: (Reserved Supply)

A token category's reserved supply is the sum – at a particular moment in time – of tokens held in reserve by the issuing entity. This is the portion of the supply which the issuer represents as "not in circulation".

The reserved supply of a fungible token category can be computed by retrieving all UTXOs which contain token prefixes matching the category ID, removing provably-destroyed outputs (spent to OP_RETURN outputs), and summing the amounts held in prefixes which have either the minting or mutable capability.

This raises several questions;

  1. as the decision of what part of the supply constitutes 'reserved' is squarely put on the plate of the issuer, how is it possible to make an algorithm for this? That algo is at best a heuristic, and doesn't feel like it belongs in a spec.
  2. We talk about provably destroyed outputs using op_returns. An op-return output is defined as starting with op_return. Yet cashtokens do the same, right? Doesn't that mean that they are mutualy incompatible?
    Notice that for cashtokens you don't need op_returns to burn as the difference between input and output just goes poof anyway.
  3. what is a mutuble fungible token? In this context we are talking about a fungible token, we should count this by checking outputs that have the minting or mutable capability. Why would a fungible token ever have a mutable capability flag set?

Consider moving the "capability" byte to be the first thing we read right after TOKEN_PREFIX

I suggest this in conjunction with #47 .

This idea came up as part of discussion in #41 here: #41 (comment)

Note that if we move the capability byte to come right after the TOKEN_PREFIX, as the first thing we read (rather than category-id being the first thing we read, as specified currently), then this buys us a great deal of headroom and freedom to be able to completely extend or redo the token data serialization format in the future. Since the capability byte has free bits in it, we could potentially use some of those bits (if set) to indicate a completely different serialization code path for some hypothetical future extended token serialization format.

TOKEN_PREFIX byte needs to be a unique, unused and invalid op-code; disallow creation of locking scripts with prefix that failed to parse

This suggestion directly contradicts issue #41 and PR #43.

If this suggestion were implemented it would simplify and/or reduce the "urgency" and/or criticality of #44, #45 and #46.

Suggestion Summary

I think it’s not a great idea for prefix byte to do double duty with the introspection opcode OP_UTXOTOKENAMOUNT. We need to make the TOKEN_PREFIX be a unique codepoint that is otherwise an invalid opcode. See discussion starting with my comment here: #41 (comment)

Suggest: 0xbf as the token prefix .

Additionally: Post-activation, we should disallow the creation of locking scripts that start with 0xbf but fail to parse as token data -- to save potential headaches in the future should we somehow loosen the serialization rules or otherwise modify them for the token data.

Rationale

While implementing #41, I noticed a couple of corner cases arise if we have the PREFIX_BYTE do double-duty as an op-code.

  • It means that we need to be very careful about what is a consensus failure and what is a serialization failure.
    • This is because if we fail to parse a byte stream as token data (i.e. a serialization failure), the byte stream just ends up entirely put into the beginning of a scriptPubKey (locking script) of an output. And entirely different consensus rules now apply to that data, as opposed to an output containing token data. Thus, it puts a lot of burden on this specification to clearly delineate what is a parse error versus what is disallowed for consensus. See issue #44
  • What's more, some locking scripts may unintentionally get "parsed" as token data when they intended to just have 0xd0 be their first op-code (very rare corner case, requires miners to mine these, but the fact that this is possible is troubling).... This "quirk" thus makes some subset of the set of all possible theoretical locking scripts beginning with 0xd0 impossible to construct.

If we use a unique codepoint that does no double-duty as an op-code (and is a disabled code-point otherwise),.. the above caveats completely evaporate.


Update: This has been implemented already here: https://gitlab.com/bitcoin-cash-node/bitcoin-cash-node/-/merge_requests/1580/diffs?commit_id=874f825c8f33b19327cf6c55b62782b62548215f

Compress `token_output_type` encoding to 1 byte

We don't have enough room in 1 byte to encode everything (FT/NFT + NFT capabilities + message length) so something must spill over to some other byte. Since message is specific to NFTs that opt to use it, then maybe only message-carrying NFTs should have to be burdened with that extra byte instead of burdening outputs that don't even use the feature.

Output format:

<categoryID><tokenOutputType>[amount][<message_length><message>]

Use bit flags to encode fundamental token types using 2 highest bits of the tokenOutputType, and 3 more bits to encode NFT type:

  • HAS_FUNGIBLE_TOKENS = 0x80,
  • HAS_NON_FUNGIBLE_TOKEN = 0x40,
  • NFT_MINT = 0x20,
  • NFT_MUTABLE = 0x10,
  • NFT_HAS_MESSAGE = 0x08.

Clarify in spec that **only** PATFOs should be pruned/unspendable, but regular locking script starting with PREFIX_BYTE is ok.

This issue assumes #41 will be implemented.

If a locking script starts with PREFIX_BYTE (0xd0) it is currently unspendable. Post-activation should the locking script happen to parse as token data, it would remain unspendable as per the spec, and is considered a pre-activation token forgery output (PATFO). However, what do we do with locking scripts starting with 0xd0 but that fail to parse as token data? As per #41 we allow these if created post-activation.

I suggest we also allow them if created pre-activation! Why? This is the most natural thing to do. And it also matches the behavior of other op-codes we added since 2022 (such as native-introspection), where previously-unspendable non-standard locking scripts now become spendable.

The spec needs to be clear on this edge case.

FT Zero, NFT message NULL and EMPTY

Hey Jason, what's your view on these edge cases:

FT Amount 0

If we allow 0-amount FTs that would be consistent with satoshi amounts, and would allow a way to "stamp" some data even if the category is FT-only, like a 0-amount OP_RETURN. Only a FT could emit 0-amount UTXOs, a non-FT NFT could not.

NFT message NULL and EMPTY

  • No message = NULL (only ID + token_type encoded)
  • Message of 0-length = EMPTY (only ID + token_type encoded)
  • Message of >=1 length = the message (ID + token_type + nft_message field)

With your composite ID||type introspection opcode you could distinguish between NULL and EMPTY, and it could be usable as a free "bool" to be purposed by the contract. Toggling it would of course require mutable(edit) capability.

Add test vectors for `SIGHASH_UTXOS`

In reference to: https://github.com/bitjson/cashtokens/blob/master/readme.md#sighash_utxos

Rationale:

  • This is a complex change
  • Breaking it out into a separate CHIP allows for separate testing
    • Why? implementation requires some significant changes to codebase, to how node signs, etc. In general signing doesn't take all the input coins as input to the sign functions.
  • By breaking it out as a separate chip more focus can be placed on it and it can be studied and tested more carefully
  • CashTokens is already a huge change, tacking this on as a "hey by the way" is rather significant and not ideal.

Rationale for `immutable` tokens not returning a capability byte from `OP_*TOKENCATEGORY`

This is a follow-up to #25 .

Using native introspection, it's currently impossible to differentiate an NFT_Immutable with a 0-byte commitment and an NFT_Fungible. Both return the exact same thing forOP_OUTPUTTOKENCOMMITMENT and OP_OUTPUTTOKENCATEGORY (and the UTXO counterpart opcodes).

Both return a categoryId without the 0x11 byte (or 0x0 byte) appended (as per latest spec).

There is no way for a contract to differentiate the two. Suggest: making NFT_Immutable append the 0x11 byte anyway, to differentiate it from a FT.

SIGHASH_UTXOS will need to take into account interaction with Double-Spend proofs

The current implementation of DSProofs will not be able to encapsulate the hash of the UTXOs for the proof. As such, DSProofs will not be possible to protect and/or provide a proof for an input that is double-spent using SIGHASH_UTXOS.

This caveat needs to be mentioned in the spec. Ultimately, the DSProof spec needs to be expanded to cover the additional hash preimage data that must be shared as part of a DSProof. (Namely, the hash of the utxos).

Amount decimals

In other protocols (ERC-20, for example) it is common when creating a token, to be able to define the amount of decimal digits to represent the amounts to the user.
How do we achieve the same with CashTokens?

Please describe more Terms

The document starts with a listing of all technical terms used in the document. It currently limits itself to terms unique to cashtokens and I think that it would be useful to expand that to terms that are not so well known but required to understand cashtokens.

Specifically I would like to see a description of:

  • Covenants
  • Commitment

I learned about covenants from a high level overview quite some time ago and while they are not really mentioned in the spec, I believe they are crucial because they really are the glue that holds the different ingredients together.

I honestly still am fuzzy on commitments. The examples didn't really explain, instead they just used. So I guess they assume you already know how cashfusion is using commitments.

A little 2 paragraph technicall (can't stress this enough, this should no be fuzzy) description of each would be really a nice addition.

Confusion about "Capability"

These two definitions seem to be contradictory:

The capability of the NFT held in this output: none, mutable, or minting. This field is omitted if no NFT is present.

The reserved supply of a fungible token category can be computed by retrieving all UTXOs which contain token prefixes matching the category ID, removing provably-destroyed outputs (spent to OP_RETURN outputs), and summing the amounts held in prefixes which have either the minting or mutable capability.

Accommodating existing SLP use cases and other metadata support

Cashtokens are gearing up to be awesome and likely will enable a large number of very interesting usecases that frankly are unique in any decentralized system.

Why I'm writing here is to also take a look at the usecases that SLP is currently very successful at and make sure those are not forgotten and support is the best it can be.

To take one great example; olicrypto. This is an SLP token which is openly traded and all the technicals are she pays divident to investors (example). Technicals are trivially supported in cashtokens. Except that it has a user-visible name. A URL and some other details that are important for the usability of the token and for the trading in a decentralized manner.

"Simple" traded tokens may be a good way to make the point, but even more advanced tokens will benefit from meta-data like which date the token was started, what the maximum number of fungible tokens are and more details.

Genesis transaction

All the above examples revolve around information that is (or can be) stored on the genesis transaction. The start of one specific cashtoken.

So, lets look at how we can get hold of that transaction based on anyone looking at any cashtoken transaction. This starts with the fact that the token has a 'category', which is intentionally a copy from a TXID. So we can start with that transaction.

Unfortunately, by looking up that transaction we don't actually find the genesis. The genesis is the one that spends from the "category = txid". And in blockchain that link is not stored. Additionally, there is no limit to how old that transaction can be when the genesis is created. It could be from years before the actual token is minted.

We need to either use a indexer server, but that makes things much more complex and costly and we learned from SLP that its easy to assume they will keep on being maintained while reality is much more harsh and bleak. Or an alternative path to our genesis tx is to use BIP 37, which is using bloom filters and merkleblock. The problem with that is we would need to ask a full node to filter every single block in sequence. Which, at best, severely limits the full nodes from serving better deserving SVP wallets. Its also slow and thus not very UX friendly.

Rationale

So, I understand the reason why the current design was picked. The simplest solution which uses the best f both worlds; a txid as a unique ID for a category (avoiding risk of duplicates) but not in the same transaction in order to allow the setup script for a token to actually include the category in its script. Can't include your own TXID in the same transaction, so a previd it became.

So, we have today:

Tx-k: the txid setting transaction.
Tx-g: a coin genesis transaction.

With the problem that there is no link from the first to the second.

Suggestion

In a sentence: to merge those two into one. At least for many simpler usecases.

So, for simple usecase you just end up with:

Tx-g: a coin genesis transaction. Its txid is the coin category.

This would be enough to do all the things that SLP supports today. It can have an op_return output that is specified much like SLP does today. And most important, all transactions ever transacting this token will refer to its txid and most full nodes will be happy to serve a single transaction based on its txid.

But you'll find there is a need to do more things because cashtokes are just too cool (read: powerful). How would that work?

A setup where multiple tokens are working together works best by them being supplied with their details all in different outputs of the same transaction. For that, you'd need something like this;

Tx-g1: genesis transaction for token 1. Output script is a simple p2pkh. (mutable enabled!)
Tx-gn: genesis transaction for token n. Output script is a simple p2pkh.

Tx-start: transaction that spends all genesis tokens and initializes them in its outputs.

Those could all be broadcast at the same time, no problem there. In practically all topic this is just as flexible as the current design, it really is only a small refactor that makes the worst case slightly more expensive at the benefit of allowing a whole range of new features that SLP users are used to.

One detail that makes sense to mention is that likely the easiest way to differentiate a genesis is if we were to decide to lists its own category as zeros. This is cheap to check during validation, really at worst just as expensive as the check we need now.

That would be a really strong advantage for those currently on SLP.

Encode of none capability

  • mutable capability is encoded as 0x01
  • minting capability is encoded as 0x02

What about none? Is it 0x00?

Unexecuted branches and PREFIX_TOKEN

So currently codepoint 0xef (the proposed PREFIX_TOKEN) is invalid, but it can peacefully exist so long as it never gets executed (e.g. can be in a branch that never is executed).

However, after upgrade, PREFIX_TOKEN (0xef) always yields a script error for any scripts it lives-in, even in unexecuted branches From the spec:

Name Codepoint (Opcode-Equivalent) Description
PREFIX_TOKEN 0xef (239) Error, even when found in an unexecuted conditional branch. (May occur before an output's locking bytecode to indicate locked tokens.)

If we do end up going with this spec -- I feel this is a potential "gotcha" that really needs to emphasized. It could cause a subtle chain split exploit if some nodes with miner hash ever follow different rules. I feel that this rule is so subtle there is a high risk for a dev to miss it (I almost did!).

Maybe I am just being paranoid but: I almost want to propose that we eliminate the requirement that any script containing this codepoint be deemed invalid... in light of that gotcha. I.e. it can exist so long as it is never executed (as is the case now).

Or do you feel it's better for this codepoint to become super-verboten (the same as DISABLED opcodes are now) after upgrade? Do you feel it adds benefits to do this?

Thoughts on this?

Two-stage activation (minimize long-term impact of activation)

@zander brought up that a two-stage activation might allow us to clean up any "pre-activation token-forgery outputs" before tokens are activated, e.g.:

At block N, outputs beginning with PREFIX_TOKEN become effectively OP_TRUE, while new token encodings are validated properly (but minting tokens is still useless, as the output can be spent by anyone). At block N+X, spending validation activates.

Opening an issue to explore if this strategy would allow us to simplify the upgrade logic that will ultimately remain in node implementations.

Clarification: "invalid token prefixes" can just be locking bytecode

@thesquaregroot identified an ambiguity in the spec we need to be sure is handled explicitly: any locking bytecode can begin with 0xd0 (OP_UTXOTOKENAMOUNT), even for outputs that do not themselves contain tokens. There are many useful covenants that might begin with OP_UTXOTOKENAMOUNT, and those outputs may or may not also carry tokens.

I overlooked this in the current version of the CHIP. I'll clarify the spec to ensure starting with 0xd0 isn't assumed to make the output invalid, and I've added this to the set of test vectors that will be in the next release. 👍

Suggest renaming "VarInt" to CompactSize in the spec

Since it's liable to cause confusion. Note the bitcoin cash docs also use the term VarInt incorrectly. VarInt != CompactSize.

VarInt is used internally by the Core line of BCH nodes (Flowee, BCHN, BU) to save txos to the db and for other internal things.

CompactSize is what you meant in the spec.

Note that the Bitcoin Core docs about their txn serialization format etc make this distinction and call it a compactsize (and not a VarInt).

SIGHASH_UTXOS weakens the utility of double-spend proofs

The addition of SIGHASH_UTXOS would break double-spend proofs for inputs signed with this sighash type. I say "break" in that DSPs as currently specified and implemented cannot capture all the information needed to prove a double-spend, if one of the conflicting inputs was spent using the proposed SIGHASH_UTXOS.

This is because the proofs don't encapsulate all of the UTXOs used in the conflicting txn. In fact, a node may even lack those UTXOs altogether. All the proof really encapsulates is information about the spent UTXO that conflicts, not all of the UTXOs in a conflicting txn. This was ok previous to the addition of SIGHASH_UTXOS since before signing never involved examining all of the other coins in a txn.

The addition of SIGHASH_UTXOS to BCH would thus drastically weaken the utility of DSPs unless the DSP specification were also expanded somehow. But even if the specification were expanded, it's not clear to me that such a proof would be possible since nodes may lack the UTXOs that a conflicting txn is referring to, thus making the proofs potentially impossible to both construct and verify.

Thus, an attacker can double-spend using the proposed SIGHASH_UTXOS and nobody would ever get an alert that such a txn were double-spent, thus weakening the utility of Double-Spend proofs.

SIGHASH_TOKEN

Just opening an issue to track a change discussed here:

This is a great point, thanks for noticing @im_uname! As @bitcoincashautist mentioned, requiring OP_RETURN for token destruction makes cleaning up “token dust” much more expensive than some sort of SIGHASH_TOKEN. A signing serialization type flag also simplifies away the awkward destruction policy difference between fungible or immutable tokens and minting or mutable tokens (where the former currently cannot be implicitly destroyed, but the later can).

In fact, to prevent offline signers from being mislead by omitting token information in signing requests, we also need to add the token information directly to the signing serialization. Maybe we include the full contents of the token output prefix (same encoding) after value in the algorithm for SIGHASH_TOKEN signatures?

If you’d be interested in sending a PR, I’d definitely appreciate it! (I’m also happy to write the update or any relevant rationale, just let me know what you’re interested in doing.)

Improve `Shared Codepoint for All Tokens` Rationale

The Shared Codepoint for All Tokens rationale was written for a pre-release iteration of the CHIP and has gotten progressively weaker. (Now that we're reusing the token inspection operation codepoints to indicate a token prefix, additional encodings do not "cost" instruction set codepoints.)

I'm starting to think that rather than the sort of compound encoding we're currently using (PREFIX_TOKEN <category_id> <has_nft> [commitment_length commitment] <ft_amount>), we should use multiple prefixes to avoid the 0x00 placeholders being used.

So we'd have encodings for each of:

  1. Only fungible tokens – e.g. PREFIX_FUNGIBLETOKENS <category_id> <ft_amount>
  2. Only a nonfungible token – e.g. PREFIX_NONFUNGIBLETOKEN <category_id> <capability> <commitment_length> [commitment]
  3. Both – e.g. PREFIX_TOKENS <category_id> <capability> <ft_amount> <commitment_length> [commitment]

This saves 1 byte per output when encoding only fungible tokens or only a nonfungible token (likely far more common than instances where both types are held), and it makes the encodings themselves simpler: there's no "empty NFT" or "zero FTs" state, and the presence of FTs and NFTs can always be determined by only the prefix.

Previously I'd thought that a shared prefix would make adding support to indexers slightly simpler, but in reality, indexers will almost certainly "translate" token encodings for their database; the encoding of tokens in transactions should just focus on being as simple and efficient as possible.

Allow outputs to carry either FT, NFT, or BOTH

Use bit flags to encode fundamental token types using 2 highest bits of the tokenOutputType:

  • FUNGIBLE_TOKENS = 0x80,
  • NON_FUNGIBLE_TOKEN = 0x40.

Encode NFT type using the remaining 6 bits, counting in reverse from the maximum value:

  • Minting NFT: b0111 1111 (0x7F)
  • Mutable NFT: b0111 1110 (0x7E)
  • Immutable NFT: b0111 1101 (0x7D)

If the output also encodes a fungible token amount then the highest bit will be toggled:

  • FT + minting NFT: b1111 1111 (0xFF)
  • FT + mutable NFT: b1111 1110 (0xFE)
  • FT + immutable NFT: b1111 1101 (0xFD)

This still lets you specify the length of the message field using the 0-0x7C range (while masking the highest 2 bits when reading the length) but lets us omit the FT amount field when the output encodes only the NFT.

Rationale: `OP_UTXOTOKENCOMMITMENT` and `OP_OUTPUTTOKENCOMMITMENT` push `0` for zero-length commitments

"If the UTXO includes a non-fungible token with a zero-byte commitment, push 0x00. If the UTXO does not include a non-fungible token, push a 0 (VM Number)."

If we encode a 1-byte commitment that encodes a commitment[0]=0x00 it would result in the same 0x00 value being pushed to the stack.

Suggestion: push an empty stack item in both no-NFT and no-commitment cases, and the user should check the capability to distinguish no-NFT from no-commitment.

Please don't use the word "undefined" in the "Transaction Data Model" section

Here: https://github.com/bitjson/cashtokens#transaction-output-data-model

The word "undefined" has a specific meaning in many languages or systems (such as in mathematics or in C++) to indicate something that should NEVER happen or that is invalid in some way (e.g. "division by zero is undefined"). The meaning of that word in your section is different from that (you mean to say, it can be missing or null).

I suggest a different word or a different phrase to capture that idea so as to avoid confusion.

Clarify handling of valid token prefixes mined in transactions prior to activation

These all came from my comments as TODOs and I want to enumerate them here. We should make sure the spec clearly makes the following points (if it doesn't already):

  • Standardness changes. Care must be taken that standardness is respected pre-activation (all PATFOs or even any txn locking script blobs starting with 0xef should remain non-standard pre-activation).
    • Post-activation, standardness rules change. Any token-containing input or output is now standard (even PATFOs are now standard, although they fail evaluation later in the pipeline regardless). Unparseables starting with 0xef continue to remain non-standard.
  • Failure to parse -> keep bytes in scriptPubKey. Failure to parse a token should stuff all the bytes in the blob including the leading 0xef back into the scriptPubKey, and tokenData should remain null. (Thus making this output unspendable).
  • BIP69 sorting. Should specify BIP69 sorting of TX Outs containing token data, sort order was:
    • (a.nValue, a.scriptPubKey) < (b.nValue, b.scriptPubKey) <-- lexicographical compare
    • It should become:
      • (a.nValue, a.scriptPubKey, a.tokenData) < (b.nValue, b.scriptPubKey, b.tokenData) <-- lexicographical compare
        • a.tokenData < b.tokenData can be expanded to lex compare as follows:
          • (a.tokenData.amount, a.tokenData.bitfield, a.tokenData.commitment, a.tokenData.id) < (b.tokenData.amount, b.tokenData.bitfield, b.tokenData.commitment, b.tokenData.id)

Opcode to return genesis input "virtual" state

Just to leave some notes here about our last discussion

it's about something like OP_GETGENESISINPUT
it should tell you: if the input that has index0 prevout is actually used for genesis or not
and if used: what's the total amount of FT being made
without that you have to check all outputs and see if they match against some input's txid

Jason:

If you see a really important use case here that's worth the increased protocol complexity, I'd encourage you to write an example that is as concise as possible to demonstrate the issue. I'll have to think more about it but my first impression is that you're wanting a specific built-in aggregation operation: https://gitlab.com/GeneralProtocols/research/chips/-/blob/master/CHIP-2021-02-Add-Native-Introspection-Opcodes.md#exclusion-of-aggregation-operations – there's a near infinite space of these, and most may never be definable as protocol-level builtins. Bounded loops might be a simpler option to enable any kind of aggregation at the contract level: https://github.com/bitjson/bch-loops

And my response:

that's right, it's a specific aggregation operation, but it's only so because your genesis input is kind of a singularity (defines only category_id, initial state undefined) that can create anything with the new categoryID on output end
and it's an aggregate you already must have in the cache in order to validate the tx

In addition, just to further clarify, we can think of genesis inputs as having a "virtual" state. Genesis input only allows a category to be created and sets the ID, but it doesn't initialize the category's token state - that's what the first batch of outputs does.
Normally transactions are introducing some state on the inputs, saying how the state will change, and recording the new state on the outputs. Because a token genesis creates tokens "from nothing" there's no input state, but there is the initial state which can be seen as "virtual" state of the input.

Here's how I'd define the virtual state of the genesis input:

  • categoryID - prevout.txid, empty stack item if input not used for genesis
  • ft_amount - sum of all of the category's outputs ft_amount, empty stack item if FT not used (could still be a genesis)
  • nft_capability - logical sum of all the category's output's nft_capability, the minimum capability that would be required to produce the outputs, e.g. if outputs have mint or multiple messages then mint, if they have only 1 message and mutable then mutable, if none then none, for case of cloneable, if one output has cloneable and other has edit, then the virtual input would be a mint. If no NFT, then empty.
  • nft_message - in case of immutable capability, return the first (and only) nft message, else empty.. or it could return the 1st message anyway

As a concept I think this is closer to OP_ACTIVEBYTECODE than aggregate (even though it needs aggregation under the hood).

Use case: you want to code a contract that requires you to create an immutable NFT with a particular nft message, how do you do that? With the genesis opcode it's as simple as verifying that some other pre-existing NFT on the input (prevout) is immutable. Wihout that - you have to inspect ALL outputs of the TX and check that the genesis input didn't create anything other than a single immutable NFT.

naming of OP_UTXO* is confusing.

The opcodes that start with "OP_UTXOTOKEN" caused me to get really confused for a while. There instantly was a thought towards the utxo-set, but that doesn't make sense. That is global info. Even "local" info makes it hard to figure out what an indexed utxo is..

Reading further I think what you named the utxo here is essentially the 'prev-out'. The previous-transaction's output.

Maybe to make it more understandable to most people a rename towards something like this makes sense?

OP_PREVOUT_TOKENCATEGORY and OP_OUTPUT_TOKENCATEGORY ? (repeat pattern for all opcodes described in this chip)

Its about readability and consistency, the numbers won't change.

Clarify what is invalid as consensus vs what is unparseable (serialization level invalid)

For example, from the spec right now:

A token prefix encoding no tokens (both has_nft and amount are 0x00) is invalid.

Is this parseable but invalid consensus-wise? Or is this a lower-level parse failure (which leads to the byte blob assigned all to scriptPubKey)? This distinction is now important since @bitjson plans on implementing #41 in the spec.

There are other such examples in the spec where it's unclear if parse failure or if token consensus failure.

SIGHASH_UTXOS spec incomplete.

The text states;

hashUtxos is inserted in the signing serialization algorithm immediately following hashPrevouts. hashUtxos is a 32-byte double SHA256 of the serialization of all UTXOs spent by the transaction's inputs (concatenated in input order).

Specifically the part "serialization of all UTXOs" is a problem, as that is not technically a field in Bitcoin full node software. I expect actual fields there (probably a subset of the input: https://flowee.org/docs/spec/blockchain/transaction/#transaction-input)

Limit the commitment length

I've had a chat with Calin, the length limit should be independent of Script length instead of eating into the Script limit with token data, much less mess, and 520 is a sane limit since anything above that would be unusable by contracts. If a future hard-fork adjusts the limit of stack items, then it will be an opportunity to adjust this in the same go.

With that outputs could have full 10,000b of Script + UTXO state (satoshi amount, token annotation)

see here: https://gitlab.com/bitcoin-cash-node/bitcoin-cash-node/-/merge_requests/1580/diffs?commit_id=cc3a38ca01a09427b9fc217692b19cea638730d5

Rationale for excluding cloneable

NFT Type

Categorized outputs may encode one of 5 NFT states:

  • NFT_NULL = 0x00, (b00000000)
  • NFT_TRANSFER = 0x3F, (b00111111) ("no capability", "immutable")
  • NFT_EDIT = 0x7F, (b10111111) ("mutable")
  • NFT_CLONE = 0xBF, (b01111111)
  • NFT_MINT = 0xFF. (b11111111)

All other values are reserved and reading a reserved value will immediately fail the transaction.

Introspection Codepoint

The same codepoint can be defined across 3 contexts: input's unlocking script prefix, Script VM, output's locking script prefix.

We can use the same 0xef for a Script VM opcode, and in another issue I had proposed to use in the input to declare a category's initialization state, and in the Script VM to access that state.

It got me thinking, could we contain the proposal to using up just 1 codepoint by making introspection opcodes pop 2 stack values, one for the index, other for the property being read. This would make the opcode more expensive but on the other hand we could avoid having to slice the capability off the categoryID opcode, and instead have a mode for obtaining it directly.

Suggest not adding cashaddr type bits, instead change the prefix to `bch:` to indicate token support.

Summary of Suggestion

I suggest we kill two birds with one stone and just change the prefix to bch: for mainnet and tbch: for testnets and scalenet. Perhaps also rbch: for regtest (although we can just leave regtest as bchreg: if we wanted to since regtest is not used to send funds to anybody ever.. and if it is, those funds are ephemeral anyway). This in effect allows us to not eat up 2 out of 16 type positions for signaling token support.

EDIT: Since testnet coins are "worthless", there is no need to go with tbch: for testnet and we should continue to just use bchtest: for testnet, to keep things as simple as possible.

Please note that simply changing the prefix string to bch: does end up affecting how the final address is encoded (since it is used to encode and calculate the checksum). So it would be sufficient to ensure upgraded software fails to send tokens to un-upgraded software to simply change the prefix. (We would of course still support the old prefix).

For those worried that changing the prefix is not "harsh" enough: I believe it really is. Do note that changing the prefix changes the encoding because the prefix string is used to calculate how the checksum looks. So a different prefix for the same payload yields a different address string. Moreover, we can state as a requirement for the cashaddr spec that the new bch: prefix can only be used for wallets that are upgraded and are token-aware. It would be a violation of upgrade9 rules and/or the cashaddr spec to deviate from this.

Rationale

  1. There has been discussion in the past that bitcoincash: is too long and bch: is far better. Bitcoin Core went with bc1: after all. Our cashaddress prefix is too huge!!
  2. We only get 4 bits for the type in cashaddr encoding. That's 16 possible values. We already use 2 of them for p2sh and p2pkh. The spec proposes eating up 2 more for token-aware-p2sh and token-aware-p2pkh. This is 12.5% of the possible remaining values eaten up here. We only have 12 out of 16 left over.
  3. Technically speaking these aren't new "types". I believe the intention of the "type" field was not in this spirit.
  4. Changing the prefix to indicate token support has the added benefit that it is more human-friendly and naive-one-off-script-friendly. The changed prefix affects only the last group of 8 characters in the qp3rsz... address string. In this way, it's far easier for humans and for naive software to map the same set of token aware bch: and "legacy" bitcoincash: addreses to each other. Even visually you can scan your wallet and notice that basically the same address qpcal1n...uuvxzprz is now also starting with qpcal1n... and it just has a different suffix. This is much more "human-friendly" IMHO. (For an example: Consider how simpleledger: addresses vs bitcoincash: addresses in Electron Cash SLP look almost the same so it's easier on human eyes just scanning their addresses visually).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.