Parallel session was right — the previously-shipped f94834b5 pre-dated:
1. the ASCII em-dash sweep in setup/install.ps1 (Inno's [Run] uses
powershell.exe / PS 5.1; em-dashes in the file crashed the parser
under UAC, so the installer copied files but never registered the
scheduled task or NRPT rule)
2. the multi-TLD NRPT expansion (added .p2p .bit .nav alongside .bch)
3. the VPS-primary electrum server list — resolver-web.js now dials
wss://coinspectrum.duckdns.org:50011 first (Silent Mode's own
BCHN-backed BNS-only indexer), public servers as fallback
Result: a real user installing 3438d558 on a fresh Windows box now gets
a working system-wide resolver with no manual steps.
Deployed to dl.silentmode.st and mirrored to Sia bns/silentmode/.
8.7 KiB
Design: BCNR ↔ ICANN collision modes
Status: design (2026-07-30). Daemon-side hard modes ✅ built; client-side soft mode + switcher 📐 to build.
The definitive spec for how a client picks between the Bitcoin Cash Name Registry (BCNR) and ICANN when both roots claim the same name — a user choice, modeled on the OS "Open with…" pattern.
1. The problem in one paragraph
Two roots resolve names now: ICANN (traditional DNS) and BCNR (on-chain). Most
names live in only one root (google.com on ICANN, hello.bch on BCNR — no
conflict). But sometimes both claim the same name: you register syskypo.de on
BCNR while syskypo.de also exists at the German .de registry. That's a
collision. Somebody has to decide which one loads. This document defines who
decides, how, and where the code lives.
2. Terminology
- BCNR — Bitcoin Cash Name Registry (the on-chain registry, this project).
- ICANN — the incumbent DNS root and its registries.
- BCNR-native TLD — a TLD that BCNR treats as its own (currently
.bch, and any TLD the on-chain root TLD certificate lists). Not in the IANA root. - ICANN TLD — a TLD in the IANA root (
.com,.de,.dev, …). - Collision — a name that resolves in both roots (only possible on an ICANN TLD, since ICANN doesn't serve BCNR-native TLDs).
- Hard mode — the daemon picks automatically per policy, no prompting.
- Soft mode — the client prompts on collision, remembers the choice.
- Switcher — a per-tab UI to flip an already-loaded page between roots.
3. The three modes
Modes 1 and 2 are hard: implemented in the daemon; they always resolve automatically. Mode 3 is soft: implemented in the client; it prompts on collision (like "Open with…") and delegates non-collision cases to the daemon underneath.
| # | Policy | Where | Collision winner | Non-collision |
|---|---|---|---|---|
| 1 | BCNR-priority (default) | daemon | BCNR | falls through to the other root |
| 2 | ICANN-priority | daemon | ICANN | falls through to the other root |
| 3 | Soft — Ask each time | client (Theseus / extension) | asked, remembered per name/TLD | same as underlying hard mode |
Env: BNS_POLICY=bcnr-first (default) · BNS_POLICY=icann-first.
(Alias: bns-first → bcnr-first.)
A previous "strict / native" hard mode was removed — its user-visible outputs matched mode 2 for every real case. The only genuinely new user-facing behavior soft brings is prompting, so it lives in one place: the client.
4. The mental model — "Open with…"
Users already know this pattern from every OS:
| OS "Open with…" | BCNR/ICANN soft mode |
|---|---|
| One app can open the file → opens automatically | No collision → resolves automatically |
| Multiple apps → dialog appears | Collision → prompt appears |
| "Always use this app" checkbox → sets a default | "Always use for this name" → remembers per name |
| Change default in Settings → new default sticks | Change in browser Settings → new default sticks |
| Right-click → "Open with…" (one-off override) | Live switcher (⛓ BCNR ↔ 🌐 ICANN) → per-tab flip |
Zero learning curve — build the same UX and users get it immediately.
5. Granularity — same as file associations
OSes let you set defaults per file type AND per file. The client does both, resolved top-to-bottom on collision:
- Per-name override — "always use ICANN for
syskypo.de" — most specific wins. - Per-TLD default — "always use ICANN for
.de" — set once, applies to all.de. - Global tie-breaker — the hard-mode setting of the underlying daemon (bcnr-first or icann-first). Whatever the user picked at install.
If none of #1/#2 have an entry and mode is soft, prompt. Otherwise auto-resolve.
6. The live switcher
Always available in the browser chrome (Theseus natively; other browsers via the extension). It:
- Shows a badge — ⛓ BCNR or 🌐 ICANN — indicating what's serving the tab.
- One click flips it. Reload with the other root. Reversible.
- Offers to remember — "Always use ICANN for this name? / for all .de?" — which writes the per-name or per-TLD default (§5), so the choice persists.
- Available even in hard modes — a hard-mode user can still one-off override for a specific tab without changing their global setting.
7. The prompt (soft mode)
Fires only when: mode is soft, both roots have an answer, and no per-name / per-TLD override exists. Contents:
syskypo.de exists in two registries.
⛓ Bitcoin Cash Name Registry (BCNR) preview: <domain-owner label>
🌐 ICANN (.de national registry) preview: real IP / cert issuer
[ ] Always use this choice for syskypo.de
[ ] Always use this choice for all .de names
[ Open with BCNR ] [ Open with ICANN ] [ Cancel ]
Design notes: don't preload either page before the choice (privacy — no
silent request leaks); the previews come from cached record metadata, not
live fetches. [Cancel] returns to the previous page.
8. Storage — user choices
Small JSON in the client's userData (Theseus) or extension storage:
{
"collisions": {
"byName": {
"syskypo.de": "icann",
"myname.dev": "bcnr"
},
"byTld": {
"de": "icann"
},
"history": [
{ "name": "syskypo.de", "at": "2026-07-30T12:34:56Z", "chose": "icann" }
]
}
}
Reset: "Reset default registries" (like OS "Reset default apps") wipes
byName and byTld in one action.
9. Where the two lists come from (both must stay fresh)
Collision detection needs to classify every TLD as ICANN, BCNR-native, or both:
- ICANN root → IANA (
iana-tlds.txt, currentlyD:\Dev\NameCoin\src\data\). A tiny file (~1400 lines), updated by IANA rarely. Refresh script runs monthly or ships with the installer/extension update. - BCNR-native TLDs → the on-chain root TLD certificate:
tlds.bch(✅ live on chipnet). Thetldsrecord is a space-separated list of activated TLDs; every resolver reads it viafetchBcnrTlds(). Adding a TLD is one on-chain UPDATE. Full spec:DESIGN-root-tld-cert.md. Cached with a short TTL by clients.
The two sets together define what "collision" means and let clients preempt the soft prompt (or skip it when only one root claims the TLD).
10. Where each piece lives
| Piece | Home | Status |
|---|---|---|
| Hard modes 1 & 2 | bnsd daemon (BNS_POLICY env / --policy flag) |
✅ built |
| IANA root list | D:\Dev\NameCoin\src\data\iana-tlds.txt (+ refresh cron 📐) |
✅ file · 📐 refresher |
| BCNR-native TLD list (on-chain) | root TLD certificate | 📐 |
| Install-time mode prompt | setup/install.ps1 — routing (surgical/full) + policy |
📐 |
| Soft mode + prompt UI | Theseus in-process; extension for other browsers | 📐 |
| Live switcher | Theseus chrome; extension toolbar | 📐 |
| Per-name / per-TLD overrides | client userData / extension storage | 📐 |
| Reset defaults | client Settings | 📐 |
| Bootstrap TLD list (before first resolve) | shipped list; refresh from on-chain root cert after startup | 📐 |
11. Open questions
- The prompt's preview — how much do we show without a network fetch? Cached record type + a small "site title" hint from the on-chain record set for BCNR; cached favicon + issuer for ICANN. If nothing cached, prompt shows only the choice, no preview. Never fetch before the user chooses.
- Uncooperative surfaces — other browsers running raw against the system daemon (no extension) get only the hard mode; that's fine and documented. Push soft users toward Theseus or the extension.
- Confidence in the BCNR-native list. If the root TLD certificate is briefly
unreachable, cached list is used with a warning; if never seen, fall back to a
shipped baseline (
bch+ a small set). Never guess. - Per-subdomain overrides? Probably not — per-name and per-TLD cover it. Adding per-subdomain complicates the storage without a clear use case.
- Policy roaming across devices? Explicitly out of scope for v0 — local only.
12. Not in scope
- Reputation systems for either root (which name is "trustworthy"). The user chooses; the client doesn't editorialize.
- Splitting resources across roots (fetching CSS from ICANN, HTML from BCNR). Each page loads from one root at a time.
- Any automatic "smart" mode that guesses — every collision is either a per-name override, per-TLD default, or a prompt. Never silent.
This document is the reference for future sessions building the installer, the Theseus switcher, and the extension. Any deviation should update this file.