# 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":,"r":}`. 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":,"r":}`. 3. Pays 600 sat to the beacon. Transferring a name = sending the certificate to a new owner (no beacon tx needed). ## 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) 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 and the name is not yet taken. **First valid REG wins.** 4. `UPD`: valid if some output of the tx carries the name's category. 5. Names: `[a-z0-9-]{1,32}`, rendered as `.bch`. ### 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. ## 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. - No expiry/renewal: names live forever once minted (Namecoin used ~8-month renewals to garbage-collect dead names). - 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).