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

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 at localPackages/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.
Arti’s data directory (guard state, cached consensus, etc.) is stored under Application Support. Binary provenance, rebuild steps, and verification hashes are maintained in 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, and torEnforced 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 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:
SymbolTypeDescription
awaitReady()async funcSuspends until Tor is bootstrapped and usable
isStartingBooltrue while a bootstrap attempt is in progress
bootstrapProgressFloat0.0–1.0 progress of the current bootstrap attempt
bootstrapSummaryStringHuman-readable status string from Arti
bootstrapDidStallBooltrue 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 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 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.
The settings UI displays this warning while the toggle is in the off position.
When Tor routing is disabled, relay operators can see your device’s IP address, including for private messages sent over Nostr. This correlates your network location with your Nostr public key and the timing of your messages. Re-enable Tor routing unless you have a specific reason not to.

What Uses Tor

Exactly two outbound network call sites exist in the app and share extension:
ComponentTraffic
NostrRelayManagerWebSocket connections to Nostr relays (private messages, location channels, bridge courier drops)
GeoRelayDirectoryCSV refresh of the geo-relay directory used for location channels
No other outbound network exists in the app or the share extension. There are no analytics endpoints, no telemetry calls, and no CDN assets fetched at runtime.

Known Limitations

No bridges or pluggable transports. Arti is compiled with default-features = false and only tokio and rustls features enabled. The pt-client and bridge-client features are absent. There are no bridge lines and no configurable directory authorities.In countries that block Tor by filtering public relays and directory authorities (for example, via IP blocklists or DPI), Arti’s bootstrap will never complete. The app reports this with the bootstrapDidStall state instead of appearing to start forever, but there is no circumvention path: obfs4, Snowflake, and meek are all unavailable.Closing this gap requires enabling pluggable-transport features in the Rust build, plumbing bridge configuration through the FFI and a settings surface, and rebuilding the xcframework with a provenance-manifest update. It is the largest remaining gap in censorship resilience for the internet transport path.The BLE mesh is entirely unaffected — it uses no internet infrastructure and continues to work normally regardless of Tor status.

Dev Bypass

For local development against live relays without waiting for Tor bootstrap:
// Define this Swift compiler flag in your local scheme only.
// It is never set in Configs/ or the project file — release builds always enforce Tor.
BITCHAT_DEV_ALLOW_CLEARNET
With this flag defined, 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.

Build docs developers (and LLMs) love