BRC-176 Tokens

BSV-21 — Validity Proofs

This BRC defines an offline-verifiable packet that proves one or more transaction outputs are valid BRC-161 / BRC-162 BSV-21 tokens. The packet is a BRC-62 BEEF. Delivery is transaction-level (BRC-95 Atomic BEEF encoding) when proving outputs of one transaction, or BRC-158 Outpoint BEEF when proving a single outpoint. Completeness and validation are this pro

Open Protocol Labs (info@opl.dev) Authors: David Case (dcase@opl.dev) Contributors: Luke Rohenaz (luke@opl.dev), Kurt Wuckert Jr. (kurt@opl.dev), Michael Boyd (root@opl.dev), Dan Wagner (dan@opl.dev)

How it connects

Relationships

Found in the source

Implementation links

These links are extracted from the upstream implementation section. Inclusion is not an OpenStandards endorsement.

Authoritative content mirror

Full upstream text

View source
Rendered from tokens/0176.md at the indexed upstream commit

Abstract

This BRC defines an offline-verifiable packet that proves one or more transaction outputs are valid BRC-161 / BRC-162 BSV-21 tokens.

The packet is a BRC-62 BEEF. Delivery is transaction-level (BRC-95 Atomic BEEF encoding) when proving outputs of one transaction, or BRC-158 Outpoint BEEF when proving a single outpoint. Completeness and validation are this profile — not Atomic BEEF’s SPV-minimal ancestor set.

BRC-161 / BRC-162 say when an output of one transaction is valid given valid inputs. This document says which transactions must be in the bag, and how to check those inputs by walking the bag.

Motivation

BSV-21 balances live in UTXOs. A receiver who is handed a tip (or several tips in one transaction) needs to check that each claimed output is really a token of the named id and amount — without a global indexer.

1Sat solves the analogous problem with a linear tip→origin walk (BRC-159) and a remittance bag (BRC-150). BSV-21 is different:

  • Identity is the deploy outpoint, not a sat origin.
  • Splits and merges make the ancestry a DAG, not a path.
  • Validity is per token id per transaction (conservation / authority), not sat ordering.
  • Funding inputs are irrelevant. Token-input source bodies are not.

Atomic BEEF is an SPV package: mined ancestors may be reduced to merkle proofs, and transactions outside the subject’s SPV graph are stripped. Those bodies are not extraneous here. This profile keeps them.

Relationship to other documents

Concern Document
Token id, ops, per-tx validation (JSON) BRC-161
Same model, binary prefix BRC-162
Basket bsv21 remittance / tags BRC-163
BEEF / Atomic BEEF / Outpoint BEEF encodings BRC-62, BRC-95, BRC-158
1Sat tip→origin (not used for BSV-21 validity) BRC-159

Specification

What is proven

Given a packet and a set of subject outpoints, a successful verify means: each subject output is a valid BSV-21 output under BRC-161 / BRC-162, of the token id and amount carried on that output, funded by a valid deploy (and, for mints, a valid authority) present in the packet.

The packet proves local lineage of the subjects. It does not prove that the issuer minted nothing else, and it does not prove UTXO set membership.

Packet

There is one packet: a BRC-62 BEEF (V1 or V2) plus a scope — the set of subject outpoints the packet is verified against. The envelope only determines how the scope travels: embedded, or supplied by the caller.

Envelope When Scope
Atomic BEEF (0x01010101 + txid) Proving one or more outputs of one transaction Embedded: outputs of that transaction id
Outpoint BEEF (0x16a7beef + txid + vout) Proving one outpoint Embedded: that outpoint
Bare BEEF Otherwise (several transactions’ outputs in one bag) Out-of-band: caller names the subject outpoints

The verify result is always bound to the scope, however it arrived. A caller MUST prove exactly the outpoints it intends to accept; a proof obtained for one scope says nothing about any other outpoint.

Atomic BEEF encoding is reused — only its byte framing. These BRC-95 rules do not apply to a BRC-176 packet:

  • Fail if a transaction is not an SPV ancestor of the subject.
  • Omit a transaction body once a merkle proof exists.

A BRC-176 packet MUST NOT be validated, stripped, or round-tripped as ordinary BRC-95 Atomic BEEF: BRC-95 body omission would remove the token-parent bodies this profile requires, and BRC-95 ancestor rejection would reject packets that are complete under this profile.

BRC-96 txid-only entries are not a transaction body. Missing body → that hop cannot be proven.

Completeness

Let S be the subject outpoints. Let T be the set of token ids those outputs name (deploy outputs contribute their own outpoint as id).

The BEEF MUST contain a full transaction body for:

  1. Every transaction that has a subject outpoint.
  2. Recursively, the source transaction of every BSV-21 input of a token id in T spent in those transactions, until each lineage reaches its deploy.

It need not contain source transactions of non-token inputs (funding, fees, other protocols).

Extra transactions MAY be present. They MUST NOT cause the packet to fail.

To prove even a single transfer output of a transaction, the bag MUST still include every same-id token input of that transaction — conservation is per token id, not per vout.

Decode

Each output is decoded as BSV-21 JSON (BRC-161) or binary (BRC-162). Either encoding is a BSV-21 output. Unrecognized scripts are ignored.

An output that parses under both encodings resolves to binary — BRC-162 recognition rules.

Normalized record:

Encoding Fields id Kind amt
JSON deploy+mint this outpoint deploy-value amt
JSON deploy+auth this outpoint deploy-auth 0
JSON mint id field mint amt
JSON transfer id field transfer amt
JSON burn id field burn amt
JSON auth id field auth 0
Binary, empty id, amt > 0 this outpoint deploy-value amount
Binary, empty id, amt = 0 this outpoint deploy-auth 0
Binary, id, amt = 0 36-byte id that outpoint auth 0
Binary, id, amt > 0 36-byte id that outpoint value amount

Token ids compare in underscore form txid_vout (BRC-161).

Validation

Parse the envelope; load the BEEF. Structural failure → unproven.

Before any token rule is evaluated, the BEEF itself MUST be valid under BRC-62 / BRC-95 / BRC-158: every txid MUST match its raw transaction body, every included merkle path MUST verify against its trusted header (for unmined transactions, against the caller's trust anchor), and unmined descendants MUST chain to proven ancestors. BSV-21 admission is evaluated only over that verified transaction set. A self-consistent synthetic DAG is not a proof.

Implementations MAY impose resource limits (transaction count, packet size, recursion depth). A tripped limit MUST be reported as unproven, failing closed for the affected subjects.

prove(outpoint) means: this output is a valid BSV-21 token of the id and amt on its script. That is conservation (or a mint from proven authority) at this hop, with every value/auth input itself proven, back to a deploy. A deploy in the bag is not enough.

Walk backward. A same-id token input counts only if prove succeeds on it. Deploy → genesis, done.

Missing source body → that input counts as nothing. A missing body can only reduce I, never increase O: omitting a same-id parent may produce false negatives (unproven), but can never produce a false positive. Missing non-token/funding parents are irrelevant to BSV-21 lineage. Bitcoin transactions cannot cycle; if prove re-enters an outpoint still on the stack, the bag is malformed → unproven.

Conservation is per token id in the transaction: proving one transfer uses the other same-id output amounts on that tx. It does not prove those siblings unless they are subjects too.

function prove(outpoint, beef):
    if outpoint is already on the prove stack: fail          // malformed BEEF

    tx = load outpoint's transaction from beef
    if missing: fail

    token = decodeBsv21(tx.outputs[outpoint.vout])
    if token is none: fail
    if token is a deploy: return ok                          // genesis

    I = 0
    hasAuth = false
    for each input of tx:
        sourceTx = load input's source transaction from beef
        if missing: continue
        spent = decodeBsv21(sourceTx.outputs[input.vout])
        if spent is none: continue
        if spent is a deploy:
            spent.id = input's source outpoint
        if spent.id != token.id: continue                    // other token / funding
        if prove(input's source outpoint, beef) fails: continue

        if spent is authority: hasAuth = true
        else if spent is burn: continue
        else: I += spent.amt

    // O = same-id amounts this tx moves (not mints). Valid only if I >= O.
    O = 0
    for each output of tx:
        created = decodeBsv21(output)
        if created is none: continue
        if created is a deploy:
            created.id = this output's outpoint
        if created.id != token.id: continue
        if created is transfer or burn:
            O += created.amt
        else if created is binary value and not hasAuth:
            O += created.amt

    if token is transfer or burn or (binary value and not hasAuth):
        if I < O: fail
        return ok
    if token is mint or auth or (binary value and hasAuth):
        if not hasAuth: fail
        return ok
    fail

On failure, the named subjects are unproven. On success, what is proven for each subject is the record (outpoint, id, amt) decoded from its own script in the verified packet — not any externally supplied claim about that outpoint.

What this document does not cover

  • Unspent status / UTXO set membership
  • Global circulating supply
  • Basket remittance, tags, display fields — BRC-163
  • 1Sat sat ordering — BRC-159

Examples

Fixed-supply transfer (one parent):

deploy+mint 10_000 at D
  → transfer 4_900 + transfer 5_100

Packet for the 4_900 tip: subject tx + deploy tx. I = 10_000 >= 10_000, so that transfer is valid (the 5_100 sibling is used only for the conservation check).

Merge — both branches required:

D 10_000
  → A 6_000 + B 4_000
      → M 10_000   (spends A and B)

Packet for M MUST include M, A’s tx, B’s tx, and D. Omitting B: I = 6_000, O = 10_000 → unproven. Spend-as-input of A alone is not enough.

Mint:

deploy+auth at D
  → mint 1_000_000 + auth     (spends D)

Packet for the mint: subject tx + D. hasAuth from a proven deploy-auth. Value inputs are not required.

Over-transfer (fail):

In:  500
Out: 300 + 400 transfer

I < O → neither transfer is valid.

Security considerations

  • Claims — A sender id / amt / sym string is not this packet.
  • Issuer mint policy — Authority mints are valid if the auth lineage reaches deploy+auth. This does not cap global supply.
  • Unspent status — Not in scope. Same UTXO checks as any BSV payment.

Implementations

References

  1. BRC-161 — BSV-21 Fungible Tokens (JSON / Legacy)
  2. BRC-162 — BSV-21 Fungible Tokens (Binary)
  3. BRC-163 — BSV-21 Basket Profile
  4. BRC-62 — BEEF
  5. BRC-95 — Atomic BEEF
  6. BRC-96 — BEEF V2 Txid Only
  7. BRC-158 — Outpoint BEEF
  8. BRC-159 — 1Sat origin tracking
  9. BRC-150 — 1Sat provenance remittance