Skip to main content

Documentation 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.

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 a 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.
Bitchat’s Nostr private messages use a proprietary, BitChat-specific envelope format. It reuses NIP-17/NIP-59 kind numbers (kind 14, kind 13, kind 1059) for historical reasons but is not NIP-17, NIP-44, or NIP-59 compatible and will not interoperate with other Nostr clients. See Private Envelopes for the full encryption specification.

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 URLNotes
wss://relay.damus.ioBuilt-in
wss://nos.lolBuilt-in
wss://relay.primal.netBuilt-in
wss://offchain.pubBuilt-in
These four relays are the default targets for private-message envelopes (kind 1059). Because four hardcoded hostnames represent four names a censor could block, user-added custom relays are what keeps the network reachable without a new app build. Custom relays: Up to 8 user-added relay URLs are accepted. .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 REQ subscriptions are replayed. The standard lookback for DM subscriptions is 24 hours.
  • Fail-closed with Tor. When Tor is enabled and enforced, sendEvent queues 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 LengthApproximate Area
7 charactersCity block (~150 m × 150 m)
6 charactersNeighborhood / district (~1.2 km × 0.6 km)
5 charactersCity (~5 km × 5 km)
4 charactersState / province (~40 km × 20 km)
2 charactersCountry / large region (~2500 km × 630 km)
Location channel messages use Nostr kind 20000 (ephemeral event) with a 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, GeoRelayDirectory waits 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 no tor 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.
For local development without Tor overhead, define the Swift compiler flag BITCHAT_DEV_ALLOW_CLEARNET. Never enable it in release builds.

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 r tag for the cell, rather than a g tag — 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 teleport tag 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.
Presence and public bridge traffic expose a coarse area (the rendezvous geohash cell) to relays and participants. This is a documented design trade-off; see Security Considerations for a full analysis of what is observable.

Build docs developers (and LLMs) love