Bitchat uses the Nostr protocol as a second transport layer for global reach when Bluetooth is unavailable. Two long-lived transports — BLE mesh and Nostr — are coordinated by aDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/permissionlesstech/bitchat/llms.txt
Use this file to discover all available pages before exploring further.
MessageRouter that prefers a live mesh link, falls back to Nostr, and engages the courier system when neither can deliver promptly. Nostr relay websockets and the geo-relay directory fetch are the only outbound network paths in the app; all other connectivity is peer-to-peer over Bluetooth.
Role in Bitchat
Nostr serves three distinct roles within the Bitchat architecture:Private Message Fallback
Private messages between mutual favorites are delivered via Nostr when neither peer is on the same BLE mesh. Clients re-subscribe with a 24-hour lookback on reconnect, covering the case where both devices were offline simultaneously.
Location Channels
Geohash-scoped public rooms carry regional conversation beyond radio range. Each precision level corresponds to a geographic area, from a city block (7 characters) to a country-sized region (2 characters).
Mesh Bridge
When mesh bridge is enabled, public mesh messages not marked “nearby only” are published to a neighborhood rendezvous geohash on Nostr, relaying for nearby mesh-only peers who lack internet access.
Relay Network
Bitchat ships with a built-in set of four well-known clearnet relay hostnames:| Relay URL | Notes |
|---|---|
wss://relay.damus.io | Built-in |
wss://nos.lol | Built-in |
wss://relay.primal.net | Built-in |
wss://offchain.pub | Built-in |
.onion addresses are accepted — this is intentional, since .onion relays allow routing that bypasses clearnet filtering. Custom relays are normalized on read and persisted in local preferences under nostr.customRelays. They are wiped by the panic wipe.
In addition to the built-in set, the Geo-Relay Directory provides a growing list of geographically distributed relays for routing location channel traffic to the nearest available infrastructure. See Geo-Relay Directory below.
Relay Management
NostrRelayManager is the central actor managing all Nostr relay websocket connections. It implements the NIP-01 client protocol: subscribing with REQ filters, publishing with EVENT, closing subscriptions with CLOSE, and processing EOSE, OK, and NOTICE server responses.
Key behaviors:
- Reconnection with backoff. Failed connections retry with exponential backoff (configurable initial and maximum intervals). Relay failures decay after a cooldown period so a transient outage cannot permanently blacklist a relay for the process lifetime.
- Re-subscription on reconnect. When a relay reconnects, pending
REQsubscriptions are replayed. The standard lookback for DM subscriptions is 24 hours. - Fail-closed with Tor. When Tor is enabled and enforced,
sendEventqueues the event locally and will not touch the network until Tor readiness is confirmed. There is no clearnet fallback. - Inbound verification pipeline. Each relay connection owns a dedicated off-main async pipeline for Schnorr signature verification. Duplicate events are pre-checked (by subscription ID + event ID) before signature work runs, so relay floods cannot drive unbounded CPU usage.
- Panic wipe.
resetForPanicWipe()tears down all sockets, clears subscription replay state, and discards all pending sends. Geohash DM handlers can capture pre-wipe Nostr private keys, so subscription intent is not preserved across a wipe.
NostrRelaySettings manages custom relay configuration, including normalization and deduplication of URLs. Changes post a didChangeNotification that NostrRelayManager observes to reconcile connections immediately.
Location Channels
Location channels are geohash-scoped public rooms carried over Nostr for regional conversation beyond radio range. Geohash precision levels determine the geographic scope of a channel:| Geohash Length | Approximate Area |
|---|---|
| 7 characters | City block (~150 m × 150 m) |
| 6 characters | Neighborhood / district (~1.2 km × 0.6 km) |
| 5 characters | City (~5 km × 5 km) |
| 4 characters | State / province (~40 km × 20 km) |
| 2 characters | Country / large region (~2500 km × 630 km) |
g tag set to the geohash string. Presence heartbeats use kind 20001 with empty content.
Per-cell Nostr identities. Rather than publishing location traffic under the device’s stable Nostr identity, each geohash cell uses an ephemeral key derived from the device seed. This limits the correlation between a user’s private DM identity and their location channel participation.
Geo-Relay Directory
GeoRelayDirectory maintains a list of Nostr relays with approximate GPS coordinates, used to route location channel traffic to geographically nearby infrastructure.
- Source. The directory is fetched as a CSV from Bitchat’s own reviewed copy on GitHub. Upstream changes must pass through a validated pull-request process, so an upstream mutation cannot immediately retarget clients.
- Format. Three-column CSV: relay URL, latitude, longitude. The parser treats one malformed or conflicting row as grounds to reject the entire dataset rather than silently shrinking the directory.
- Validation. Maximum 5,000 rows, 512 KiB. A remote update must retain at least 50% overlap with the current baseline to be accepted, preventing a poisoned update from wholesale replacing the relay list.
- Caching. The parsed directory is cached in Application Support. On startup, the cache is loaded first; if absent, a bundled CSV resource is used. The cache is cleared and rebuilt on every successful remote fetch.
- Tor gating. When Tor is enabled,
GeoRelayDirectorywaits for Tor readiness before fetching. The wait is keyed on the user preference, not on live Tor state — with Tor switched off, the fetch goes direct; with Tor on but not yet ready, the fetch is skipped rather than leaking the IP. - Closest-relay selection.
closestRelays(toGeohash:count:)uses the haversine formula to rank entries by distance to the geohash center, breaking ties by hostname for determinism. Publishers and subscribers select the same relay set from the same directory.
Tor Integration
All outbound internet traffic in Bitchat routes through Arti, an in-process Tor client implemented in Rust and vendored as a Swift package. There is notor binary, no torrc, and no control port. The only interface is a SOCKS5 listener on 127.0.0.1:39050.
Both network call sites — NostrRelayManager (relay websockets) and GeoRelayDirectory (directory CSV fetch) — go through TorURLSession, which configures the SOCKS proxy automatically based on the current Tor preference.
Fail-closed behavior. When Tor is wanted but not ready, requests queue or are deferred rather than falling back to clearnet. torEnforced is compiled to true in release builds unless the BITCHAT_DEV_ALLOW_CLEARNET Swift compiler flag is set (never in release).
User-visible toggle. A “Tor routing” preference in settings defaults to on. Turning it off is a meaningful change in exposure: relay websockets connect directly, and every relay operator — including those carrying private messages — sees the device’s IP address. The settings UI surfaces this warning while the toggle is off.
Known gap. Bridges and pluggable transports (obfs4, snowflake, meek) are not available in the current build. In networks that block public Tor relays and directory authorities, bootstrap will not complete. The BLE mesh is unaffected.
Mesh Bridge
When the mesh bridge capability is enabled, Bitchat acts as a gateway between the local BLE mesh and the Nostr internet transport:- Public mesh messages not marked “nearby only” are signed under the device’s per-cell Nostr identity and published to a neighborhood rendezvous geohash on Nostr (kind 20000 with an
rtag for the cell, rather than agtag — this keeps bridge traffic out of geohash channel subscriptions). - Gateway devices carry signed bridge events and opaque courier drops for nearby mesh-only peers. A gateway cannot validly publish a neighbor’s radio-only message on their behalf — the originating author must sign the bridge event.
- A
teleporttag on a location channel message signals that it originated from a user who manually selected a remote geohash rather than one derived from their physical location. - Bridge courier drops (kind 1401) use a throwaway publisher key, an opaque Noise-sealed envelope, and a day-rotating recipient tag. Only a party holding the recipient’s Noise static key can compute candidate recipient tags to subscribe for.