2026-07-29 13:54:34 +02:00
# BNS1 — a minimal Namecoin-style name protocol on Bitcoin Cash (chipnet prototype)
Names are anchored by **CashTokens name certificates** on the BCH chain; records travel in
**OP_RETURN** payloads; discovery uses a **beacon address** so a plain electrum
server is the only infrastructure a resolver needs. No fork, no sidechain.
## Roles of the three mechanisms
| Mechanism | Role |
|---|---|
| CashTokens certificate (immutable NFT) | *Ownership.* The certificate's commitment is the UTF-8 name. Whoever holds the certificate owns the name. Consensus guarantees nobody can mint an output of an existing category without spending it. |
| OP_RETURN payload | *Data.* `BNS1` prefix + JSON: the name's records (URL, inline HTML page, payment address). |
| Beacon dust output | *Discovery.* Every protocol tx pays 600 sat to the well-known beacon address, so `blockchain.address.get_history(beacon)` enumerates every registration/update ever made. |
**Beacon (chipnet):** `bchtest:qp8njaa9a3ahg7259s5qajnce526n0rttsq8qwx4jl`
## Transactions
**REG** — one tx that simultaneously:
1. Mints an immutable certificate, commitment = UTF-8 name (≤ 40 bytes). Its category
(= token ID) becomes the name's permanent identity.
2. Carries `OP_RETURN "BNS1{...json...}"` with `{"o":"REG","n":<name>,"r":<records>}` .
3. Pays 600 sat to the beacon.
**UPD** — one tx that:
1. Spends (moves) the name's certificate back to the owner — this is the ownership proof.
A vout carrying the name's category is only possible if a vin held it (consensus).
2. Carries `OP_RETURN "BNS1{...}"` with `{"o":"UPD","n":<name>,"r":<newRecords>}` .
3. Pays 600 sat to the beacon.
2026-10-04 01:39:14 +02:00
**REL** — one tx that:
1. Spends the name's certificate and does **not** recreate it (no output carries
the category): the certificate is burned.
2. Carries `OP_RETURN "BNS1{...}"` with `{"o":"REL","n":<name>}` .
3. Pays 600 sat to the beacon.
The name returns to the pool. Specified in `PROTOCOL-ADDENDUM-release.md` .
2026-07-29 13:54:34 +02:00
Transferring a name = sending the certificate to a new owner (no beacon tx needed).
2026-10-04 01:39:14 +02:00
A certificate moved that way must be anchored by an `UPD` before it can be released.
2026-07-29 13:54:34 +02:00
## Records (JSON, keep total payload ≤ 200 bytes)
- `u` — https URL the name points to (gateway redirects)
- `h` — inline HTML: a tiny website stored fully on-chain
- `a` — cashaddr for payments (the "send to alice.bch" use case)
- `ip` — IPv4 of a server that answers for the .bch host directly (DNS answers
this instead of the local gateway)
- `p` — upstream URL the local gateway reverse-proxies (address bar stays .bch)
- `s3` — `bucket/key` (single object) or `bucket/prefix/` (site root) stored on
the Sia network, served by the gateway through a local s3d daemon
(credentials in sia-s3.json, not on-chain). Storage is decentralized and
encrypted; the retrieval door is the local daemon.
- `tls` — SHA-256 fingerprint (hex, over DER) of the name's TLS certificate.
The chain replaces the CA: identity comes from the certificate holder publishing
this record, encryption from ordinary TLS. Resolvers/extensions can pin it;
the prototype's trust path is the BNS Local Root CA (see ca/) which signs
per-name certs and is installed as a trust anchor by setup/install.ps1.
## Resolver rules (indexer-enforced)
2026-10-04 16:53:59 +02:00
**One message per transaction.** A transaction's message is the one output
that parses as a BNS payload with a known operation. If more than one output
does, the transaction carries **no** message. *(2026-10-04. Before, the last
one won — which let whoever assembles a transaction replace a message another
party had fixed in it. No transaction on chipnet carried more than one.)*
2026-07-29 13:54:34 +02:00
1. Fetch beacon history, sort by `(height, txid)` ; unconfirmed last.
2. **Then reorder causally:** never index a transaction before one it spends
from. Independent transactions keep the `txid` tiebreak. *(See "Ordering"
below — this rule is load-bearing, added 2026-07-26.)*
3. `REG` : valid if the same tx mints a certificate whose commitment matches the name
2026-10-04 16:13:04 +02:00
and the name is not yet taken. **First valid REG wins.** The certificate
must be the **only token of its category** : the category is created by
this transaction (it spends, at output index 0, the outpoint whose txid is
the category), exactly one output carries it, and that output is an NFT
with no capability and no fungible amount. Rule 4 accepts an update from
any token of a name's category, so a registration that left a second one
in existence would let its holder rewrite the name after selling the
certificate; such a registration is void. *(Added 2026-10-04; every valid
registration on chipnet already satisfied it.)* If the name was
2026-10-04 01:39:14 +02:00
released (rule 5), the `REG` must be in a later block than the confirmed
release — the one-block gap.
2026-07-29 13:54:34 +02:00
4. `UPD` : valid if some output of the tx carries the name's category.
2026-10-04 18:14:49 +02:00
A name whose certificate is held by the registry covenant answers only to
an `UPD` that states the covenant state (`c` ) and whose claim matches the
certificate's address; it stops resolving once its grace period has
passed. See `PROTOCOL-ADDENDUM-covenant-names.md` . *(Chipnet, 2026-10-04.)*
2026-10-04 01:39:14 +02:00
5. `REL` : valid if the name is registered, no output of the tx carries the
name's category, and one input spends an output of an earlier beacon
transaction that carries the certificate. The name leaves the index.
6. Names: `[a-z0-9-]{1,32}` , rendered as `<name>.bch` .
2026-07-29 13:54:34 +02:00
### Ordering (why rule 2 exists)
`(height, txid)` alone is arbitrary within a block and within the mempool, and
that breaks the most ordinary sequence in the protocol: **register a name, then
immediately point it somewhere.** If the `UPD` 's txid happens to sort below its
own `REG` 's, the `UPD` is evaluated against an index that does not contain the
name yet, fails rule 4, and is silently dropped — *permanently* , if both land in
the same block, since the ordering never changes afterwards.
Observed on chipnet 2026-07-26: `REG ca243c06…` for `smtest-fbe8cd.p2p` and its
`UPD 8da2cc4a…` were both unconfirmed; `8da2…` sorted first, and the record
never appeared even though the update transaction was entirely valid.
The fix is a stable topological pass over the history: keep the `(height, txid)`
baseline, but hold back any transaction whose parents (within the beacon set)
have not been emitted yet. Cost is O(n²) over the beacon history, which is read
once per index build. Implemented identically in `src/lib/resolver-web.js` and
`src/lib/bns.js` — **the two must stay in lockstep** , since a disagreement
between them is a consensus split between clients.
bns: the blocklist consumer, shipped with an empty list
A resolver that learns about blocklists only after the first scam needs a
release before it can warn anyone. The consumer ships now, pointed at a list
with no entries, so the first real report costs a transaction instead.
A list is an address, named by a BNS name whose bl record holds it. Entries
are RPT (the report), BLK and UBK. A flag counts only when it names a valid
report in the same list for the same name and reason, and only when its
transaction is spent FROM the list address: anyone can pay an address, so
being in its history proves nothing. That spend rule is what lets the
authority change hands later without the resolver learning the difference.
resolveName attaches blocked, blockReason, blockReportTxid and blockedBy to a
flagged name and leaves every other answer untouched. With no subscription it
is the old resolver exactly. A list that cannot be read never stops a name
from resolving. Lists are read from full electrum servers: the project's
beacon-only indexers answer 'no history' for any other address, which would
look like an empty list.
The default list is sirius-blocklist.x, a two-part name because a three-part
one collapses to its parent. Address conversion is dependency-free so this
file stays a single-file drop-in.
2026-10-04 02:32:58 +02:00
## Blocklist (resolver-side)
Not a chain rule. A resolver may subscribe to lists of flagged names and warn
about, or refuse, a name a list flags; the name stays registered and any
resolver that does not subscribe resolves it as before. The messages (`RPT` ,
`BLK` , `UBK` ), the authority rule and the client behaviour are in
`PROTOCOL-ADDENDUM-blocklist.md` .
2026-07-29 13:54:34 +02:00
## Honest limitations (prototype vs production)
- Uniqueness is **indexer policy** , not consensus. Production: a covenant-run
registry (CashScript) that consensus-enforces one certificate per name, or full
BCMR-style client-side validation of the genesis chain.
- Intra-block ordering uses txid as tiebreak among *independent* transactions
(electrum history has no tx index). Dependent transactions are ordered
causally — see "Ordering" above.
- The beacon is a keyed address (prototype convenience); production would use a
keyless constant script so no one can be pressured over it.
2026-10-04 01:39:14 +02:00
- No expiry/renewal: names live until their owner releases them (`REL` );
an abandoned name still lives forever (Namecoin used ~8-month renewals to
garbage-collect dead names — the covenant registry is that step).
2026-07-29 13:54:34 +02:00
- Front-running: a miner/mempool observer could see a REG and race it
(Namecoin's two-step `name_new` commit-reveal solved this; omitted here).