14 KiB
FIPS Extension: Accessing .fips Content Without a Local Daemon
The Question
If a user came across a website with FIPS links embedded in it, could the browser extension resolve those links and make them usable without the user having the FIPS daemon installed and running?
Short Answer
Yes, but with important tradeoffs. The extension can always resolve .fips names (the math is pure computation). But actually connecting to the destination requires either a local FIPS node or a gateway. There are three approaches, ranging from simple-but-less-private to ambitious-but-fully-encrypted.
Why Name Resolution Works Without a Daemon
The npub → IPv6 derivation is pure computation — no network, no daemon, no DNS:
npub1qmc3... (bech32 string)
↓ decode
32-byte public key
↓ SHA-256, take first 16 bytes
node_addr (16 bytes)
↓ prepend 0xfd
fd00::/8 IPv6 address
The extension can do this entirely in JavaScript. Bech32 decoding and SHA-256 are both trivial in JS (Web Crypto API has crypto.subtle.digest('SHA-256', ...)). So for any npub1xxx.fips URL, the extension can compute the target IPv6 address instantly, with zero network dependency.
Friendly hostnames (mysite.fips instead of npub1xxx.fips) are different — those require a lookup against /etc/fips/hosts or the FIPS DNS resolver. Without a daemon, the extension would need its own host map (stored in extension storage) or a gateway that can do the lookup.
The Connectivity Problem
Here's the catch: the resolved IPv6 address (e.g., fdab:1234:...) lives in the fd00::/8 ULA range. This address only exists inside the FIPS mesh. Without a FIPS node running:
- There's no
fips0TUN interface - There's no route for
fd00::/8traffic - The OS has nowhere to send packets to that IPv6 address
- The connection fails with "No route to host"
So resolution works, but connectivity doesn't — unless we provide an alternative path.
Three Approaches to Daemon-Free Access
Approach 1: Gateway Proxy (Simple, Less Private)
The extension routes .fips requests through a public FIPS gateway — a server that runs FIPS and acts as an HTTP proxy for non-FIPS users.
How it works:
User browser (no FIPS)
↓ extension intercepts request to npub1abc.fips
↓ extension resolves npub → IPv6 (pure computation)
↓ extension sends request to gateway: "fetch http://[fdab:...]/ for me"
↓
FIPS Gateway (runs FIPS daemon)
↓ receives proxy request
↓ connects to fdab:... through its local FIPS mesh
↓ fetches the content
↓ returns it to the extension
↓
User sees the FIPS-hosted page
Implementation in the extension:
- Use the
proxyAPI to route*.fipsrequests through a configured gateway URL - The gateway is an HTTP CONNECT proxy or a reverse proxy that accepts
.fipshostnames - Extension resolves the npub locally and passes the resolved address to the gateway (or just passes the hostname and lets the gateway resolve it)
Tradeoffs:
- ✅ Simple to implement — just a proxy configuration
- ✅ Works for all
.fipscontent (HTTP, WebSocket, etc.) - ✅ Gateway can be self-hosted (your own FIPS node on a VPS) or public
- ❌ Gateway operator can see all traffic — breaks end-to-end encryption
- ❌ Trust required in the gateway operator
- ❌ Gateway becomes a centralization point (though users can choose their own)
This is analogous to Tor2Web gateways — they let non-Tor users access .onion sites, but the gateway can see the traffic. It's a tradeoff between accessibility and privacy.
Mitigations:
- Let users run their own gateway (their FIPS node on a VPS or home server)
- Support multiple gateways and let users choose
- Clearly indicate in the UI when you're on "gateway mode" vs "direct FIPS mode"
- Sites can use end-to-end app-level encryption (e.g., the site itself serves over HTTPS with a self-signed cert whose fingerprint is published via Nostr) on top of the FIPS transport
Approach 2: Lightweight Browser FIPS Client (Ambitious, Fully Encrypted)
The extension implements enough of the FIPS protocol in JavaScript to participate in the mesh directly, without a full daemon. It connects to a FIPS bootstrap node via WebSocket and establishes an end-to-end encrypted session to the destination.
How it works:
User browser (no FIPS daemon)
↓ extension intercepts request to npub1abc.fips
↓ extension resolves npub → IPv6 (pure computation)
↓ extension connects to a FIPS bootstrap node via WebSocket
↓ extension runs FSP (FIPS Session Protocol) — Noise XK handshake
↓ extension establishes end-to-end encrypted session to npub1abc
↓ extension sends HTTP request through the encrypted session
↓
FIPS Bootstrap Node (just a regular FIPS peer)
↓ receives the session packet
↓ routes it through the mesh toward npub1abc
↓ (bootstrap node can't read the payload — it's end-to-end encrypted)
↓
Destination node (npub1abc)
↓ receives session, decrypts payload
↓ returns HTTP response through the encrypted session
↓
User sees the FIPS-hosted page — fully end-to-end encrypted
What the extension would need to implement in JS:
- Bech32 decoding (for npub parsing) — trivial
- SHA-256 (for node_addr derivation) — Web Crypto API
- secp256k1 (for keypair generation and signing) — existing JS libraries (e.g., @noble/secp256k1, nostr-tools)
- Noise XK handshake (for end-to-end session establishment) — JS Noise implementation or custom
- FSP session protocol (framing, port multiplexing) — port from Rust
- WebSocket transport (to connect to a FIPS bootstrap node) — native browser API
- HTTP-over-FSP (carry HTTP requests/responses over the FSP session) — custom layer
Tradeoffs:
- ✅ Full end-to-end encryption — the bootstrap node can't read your traffic
- ✅ No trust required in the bootstrap node (it's just a router)
- ✅ True to FIPS's privacy model
- ✅ The extension IS a FIPS node (lightweight, browser-based)
- ❌ Significant implementation effort — Noise protocol, FSP, mesh routing in JS
- ❌ Browser limitations: WebSocket is the only viable transport (no raw UDP/TCP)
- ❌ Can't run a full mesh node in a browser (no TUN, no listening sockets, no UDP)
- ❌ Performance — JS crypto is slower than Rust, though WebCrypto helps
- ❌ The bootstrap node needs to support WebSocket inbound (FIPS does support TCP, and WebSocket is essentially TCP-over-HTTP)
This is the most exciting option. It means the extension is a first-class FIPS client — not just a proxy consumer. The user gets real end-to-end encryption without installing anything. The browser extension becomes the FIPS node, using a bootstrap peer the same way a full FIPS node would.
Key insight: The extension doesn't need to implement the full mesh protocol (FMP with spanning tree, bloom filters, routing). It only needs:
- FSP (session protocol) — to establish end-to-end encrypted sessions
- A single transport — WebSocket to a bootstrap node
- The bootstrap node handles mesh routing — it receives the session packet and forwards it through the mesh using FMP
The extension is a "FIPS client" not a "FIPS router." It doesn't relay traffic for others; it just creates sessions to destinations through a bootstrap peer. This is a much smaller scope than a full FIPS node.
Approach 3: Hybrid — Gateway Now, Browser Client Later
Start with Approach 1 (gateway proxy) for initial release, then evolve to Approach 2 (browser FIPS client) as the project matures.
Phase 1: Extension uses gateway proxy mode. Users can access .fips content immediately. The extension clearly shows "gateway mode" with a warning about reduced privacy.
Phase 2: Extension implements FSP over WebSocket. Users who want full encryption can switch to "direct mode." Gateway mode remains as a fallback.
Phase 3: Extension can optionally become a full FIPS node (with WebRTC for UDP-like transport, service worker for background operation, etc.) — but this is speculative and far future.
The User Experience
Here's how it would look to a user who discovers a FIPS link:
Scenario: Alice publishes a blog post with a FIPS link
- Bob visits Alice's regular blog (clearnet,
https://alice.com/blog) - The blog post contains a link:
<a href="http://npub1qmc3...98.fips/">My FIPS-hosted mirror</a> - Bob has the extension installed but no FIPS daemon
- Bob clicks the link
- The extension:
- Detects the
.fipsURL - Resolves
npub1qmc3...98→fdab:1234:...(pure computation, instant) - Checks for local FIPS node → not found
- Falls back to gateway mode (or browser client mode)
- Fetches the content through the gateway/mesh
- Displays the page in the browser
- Detects the
- Bob sees Alice's FIPS-hosted mirror — he didn't need to install FIPS
What the extension shows Bob:
- Toolbar badge: "FIPS" with a color indicating mode:
- 🟢 Green: Direct FIPS (local daemon, full encryption)
- 🟡 Yellow: Browser client mode (end-to-end encrypted via WebSocket bootstrap)
- 🟠 Orange: Gateway mode (gateway can see traffic)
- Popup on click: Shows the destination npub, the resolved IPv6, the connection mode, and the encryption status
- Page indicator: Small banner on first visit explaining "This site is hosted on the FIPS mesh"
Linkification on regular pages:
The extension can also scan pages for npub addresses and linkify them. If a page contains the text npub1qmc3...98, the extension converts it to a clickable link to http://npub1qmc3...98.fips/. This means FIPS addresses work anywhere — blog posts, Nostr notes, chat messages, forums.
What Makes This Compelling
This is the feature that could drive FIPS adoption. Right now, FIPS has a chicken-and-egg problem:
- Content creators won't host on FIPS if no one can access it
- Users won't install FIPS if there's no content to access
The extension breaks this cycle:
- Content creators host on FIPS (they run a FIPS node — it's their server)
- Regular users install the browser extension (one click, no daemon)
- The extension makes
.fipslinks work transparently - Users who want full privacy can upgrade to running a FIPS node later
- The extension's Nostr signing features (nos2x parity) give users another reason to install it
The extension becomes the FIPS on-ramp. You install it for Nostr signing (like nos2x), and as a side effect, .fips links just work. You don't even need to know what FIPS is — the extension handles it.
Technical Feasibility Assessment
Approach 1 (Gateway) — Feasible Now
| Component | Status |
|---|---|
| npub → IPv6 in JS | Trivial — bech32 + SHA-256 |
proxy API for routing |
Standard browser extension API |
| Gateway server | Needs to be built — an HTTP proxy that connects through FIPS |
| Friendly hostname support | Extension can maintain its own host map, or gateway can resolve |
The gateway server is the main thing that needs to be built. It's essentially:
- An HTTP proxy that accepts requests for
*.fipshostnames - Resolves the hostname (npub computation or FIPS DNS)
- Connects to the destination through its local FIPS mesh
- Returns the response
This could be a simple Rust service built on top of the FIPS library, or even a configuration of an existing proxy (like a custom nginx module or a small Go/Rust HTTP proxy).
Approach 2 (Browser Client) — Feasible But Significant Work
| Component | Status |
|---|---|
| npub → IPv6 in JS | Trivial |
| secp256k1 in JS | Existing libraries (@noble/secp256k1, nostr-tools) |
| Noise XK in JS | Needs implementation or port — no widely-used JS Noise library |
| FSP framing in JS | Needs port from Rust |
| WebSocket transport | Native browser API |
| HTTP-over-FSP | Needs design and implementation |
| Bootstrap node WebSocket support | FIPS supports TCP; WebSocket gateway needed (or FIPS adds WS transport) |
The Noise XK implementation is the hardest part. There are Noise libraries in various languages but a production-quality JS implementation that matches FIPS's exact handshake would need work. The FSP framing is straightforward — it's a binary protocol with length-prefixed messages.
The bootstrap node would need to accept WebSocket connections. FIPS currently supports TCP, and a WebSocket is essentially TCP with an HTTP upgrade handshake. Either:
- FIPS adds a WebSocket transport (small addition to the transport layer)
- A small bridge service converts WebSocket → TCP for the FIPS bootstrap node
Approach 3 (Hybrid) — Most Pragmatic
Start with gateway, ship something that works, then build the browser client for users who want full encryption. This gives the project:
- Immediate value (gateway mode works day one)
- A path to full privacy (browser client mode)
- A reason for users to upgrade from gateway to direct mode
Open Questions
-
Gateway trust model: Should the extension ship with default gateways, or require users to configure their own? Shipping defaults is more convenient but creates trust assumptions.
-
Browser client scope: If we go with Approach 2, should the extension implement the full FSP protocol, or just enough for HTTP GET/POST? WebSocket support in FIPS would be needed either way.
-
Friendly hostnames without a daemon: Should the extension maintain its own host map (synced via Nostr?) for friendly names, or should we focus on npub addresses for daemon-free users?
-
Nostr-based discovery: Could we use Nostr events to publish FIPS service directories? E.g., a NIP where kind:XXXX events announce "I host service Y at npub Z on port P." The extension could index these and provide a FIPS service browser.
-
Gateway as a FIPS feature: Should the gateway proxy be a first-class FIPS feature (built into
fipsctlor as a separatefips-gatewaybinary), or a separate project?