argus/PROTOCOL.md
Local Dev a45320db7d Initial commit — Silent Mode baseline (2026-07-29)
Snapshot of the decentralized-web stack at the point of the resolver+Theseus
rebuild deploy. Includes:

- Argus (BNS engine + resolver daemon + Sia gateway)
- AriadneResolver (Windows Inno installer bundle + Android APK sources +
  Firefox extension)
- TheseusNavigator (Electron browser)
- site/ (silentmode.st content, deployed to Sia at bns/silentmode/)
- design docs, roadmap, protocol spec

Secrets excluded via .gitignore: Argus/sia-s3.json, Argus/wallets.json,
Argus/ca/*.key,*.crt. Build outputs, node_modules, and bundled runtimes
also excluded.

Shipped hashes on dl.silentmode.st at this commit:
  AriadneResolver-Setup-0.1.0.exe    5bcb216eef31ea28ed767e4134ab74bd5ac69dfbd365fd249e9e6938e55c986a
  TheseusNavigator-Setup-0.0.1.exe   7c735e88bad2da3347145adba3016c8f626a18b8422289c8c6ba471972e2952b
  TheseusNavigator-0.0.1-portable.exe 008fd84445babeabb401b2bca40ea9466b24b0e6d6c85104da7640c5c5c84521
  ariadne-v0.2.apk                   635c8f04d44ef855a8390b9eeddb8cd2d50622e81cc4e5004e8daffc1bb0425c
2026-07-29 13:54:34 +02:00

5.5 KiB

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.

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 <name>.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).