Bitchat routes all internet-bound traffic — Nostr relay WebSocket connections and the geo-relay directory fetch — through Tor by default. This means relay operators cannot see a device’s IP address, including relays that carry private messages. Tor is provided in-process with no external binary or configuration file; it starts automatically with the app and is on unless the user explicitly turns it off.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.
How Tor Works in Bitchat
Tor is provided by Arti, a pure-Rust implementation of the Tor client, compiled into the app as a Swift package. The package lives atlocalPackages/Arti in the source tree and wraps a statically-linked Rust xcframework.
No tor Binary
There is no
tor process, no torrc configuration file, and no control port. Arti runs entirely inside the app process.SOCKS5 on Loopback
Arti exposes a single SOCKS5 listener on
127.0.0.1:39050. All outbound network calls go through this proxy.In-Process Rust
The xcframework includes slices for iOS device, iOS Simulator, and macOS arm64. The build is reproducible and provenance-tracked.
Default On
The Tor routing preference defaults to enabled. Users must explicitly disable it in settings.
docs/ARTI-BINARY-PROVENANCE.md and enforced by a CI workflow. Binary-only updates without a reviewed Rust source and Cargo.lock diff are not accepted.
Fail-Closed Design
When Tor routing is enabled but Arti has not yet bootstrapped, outbound requests queue rather than fall back to clearnet. There is no silent degradation path. A request either waits for Tor to become ready or fails — it never goes out in the clear while the Tor preference is on. This is intentional. A privacy-preserving app that silently bypasses its own privacy layer under adverse conditions provides a false sense of security. The fail-closed guarantee is enforced at every call site, andtorEnforced is a compile-time constant: it is true in all release builds unless the BITCHAT_DEV_ALLOW_CLEARNET compiler flag is defined (see Dev Bypass).
Key Components
TorManager
TorManager
TorManager owns the Arti client instance and its Application Support data directory. It starts and manages the bootstrap process and exposes the SOCKS port for other components to use.Key properties and methods:| Symbol | Type | Description |
|---|---|---|
awaitReady() | async func | Suspends until Tor is bootstrapped and usable |
isStarting | Bool | true while a bootstrap attempt is in progress |
bootstrapProgress | Float | 0.0–1.0 progress of the current bootstrap attempt |
bootstrapSummary | String | Human-readable status string from Arti |
bootstrapDidStall | Bool | true if the 75-second bootstrap deadline elapsed without completing |
bootstrapDidStall is the state produced when a network blocks Tor’s public relays. It is deliberately distinct from isStarting so the UI can show a clear stalled message rather than “starting tor…” indefinitely. The flag is cleared on each new start or restart attempt.When
bootstrapDidStall becomes true, the app posts a .TorBootstrapDidStall notification and the settings UI shows a specific stalled indicator. If you are in a country that blocks Tor’s public directory authorities or relays, this is the state you will see. The BLE mesh continues to function normally — only the Nostr internet path is affected.TorURLSession
TorURLSession
TorURLSession is a shared URLSession wrapper that configures the SOCKS5 proxy when Tor is active and uses a direct session when it is not. The switch is setProxyMode(useTor:), which is driven by NetworkActivationService based on the current Tor preference and activation state.All HTTP(S) and WebSocket calls in the app go through TorURLSession — there is no second code path to clearnet.NetworkActivationService
NetworkActivationService
NetworkActivationService decides whether Tor may run at all, based on the platform activation policy and the user’s stored preference. Tor starts only when the activation policy permits it and the Tor preference is on.persistedTorPreference(in:) is a nonisolated read of the persisted preference for callers that are off the main actor (for example, early launch code that needs the preference before the main actor is available).Tor Preference Setting
There is one user-visible Tor setting: a Tor routing toggle in the app’s settings screen. It defaults to on. Turning Tor off is a real change in exposure, not a performance tweak. With the preference off:- Every Nostr relay WebSocket connects directly over clearnet.
- The geo-relay directory refresh also goes direct.
- Every relay operator — including relays carrying your private messages — can see your device’s IP address.
What Uses Tor
Exactly two outbound network call sites exist in the app and share extension:| Component | Traffic |
|---|---|
NostrRelayManager | WebSocket connections to Nostr relays (private messages, location channels, bridge courier drops) |
GeoRelayDirectory | CSV refresh of the geo-relay directory used for location channels |
Known Limitations
Dev Bypass
For local development against live relays without waiting for Tor bootstrap:torEnforced becomes false at compile time and direct network access is permitted. Never define this flag in a release scheme or a build you distribute.