How BitChat Implements TOR Integration for Network Privacy
BitChat embeds a lightweight Tor client (Arti) directly into the app and routes all external traffic through a local SOCKS5 proxy, implementing a fail-closed architecture that prevents IP leaks unless explicitly disabled.
The permissionlesstech/bitchat repository demonstrates a production-grade approach to anonymous networking by integrating the Arti Tor client as an in-process library rather than relying on external binaries. This TOR integration in BitChat ensures that all external requests are anonymized by default while providing graceful degradation controls and comprehensive bootstrap monitoring.
Embedded Arti Architecture
BitChat bundles the Arti Tor client as a Rust static library compiled into an XCFramework located at localPackages/Arti. Unlike traditional Tor implementations that require a separate tor binary and configuration files, this embedded approach runs Arti in-process with no external dependencies.
The client exposes a SOCKS5 proxy listener on the fixed local endpoint 127.0.0.1:39050. Swift code interacts with the Rust core through Foreign Function Interface (FFI) bindings, calling functions such as arti_start and arti_is_running to manage the Tor daemon state.
TorManager: Lifecycle and Bootstrap Monitoring
The TorManager singleton (TorManager.shared) in localPackages/Arti/Sources/TorManager.swift serves as the central controller for the Arti instance. It manages the Tor data directory under the app's Application Support folder, monitors connection health, and exposes readiness state through published properties.
Key responsibilities include:
- Bootstrap tracking: The manager monitors
bootstrapProgressand setsbootstrapDidStallif the connection fails to establish within 75 seconds. - State exposure: Properties
isReady,bootstrapProgress, andbootstrapDidStallallow the UI to display connection status. - Lifecycle control: Handles graceful shutdown and restart via
shutdownCompletely()andstartIfNeeded().
// Checking Tor readiness before sensitive operations
let ready = await TorManager.shared.awaitReady(timeout: 75)
guard ready else {
// Handle timeout or stall detection
return
}
NetworkActivationService: Orchestrating Tor Access
The NetworkActivationService in bitchat/Services/NetworkActivationService.swift coordinates when Tor may activate based on user preferences, network reachability, and app policy. It reads the user-visible toggle from UserDefaults using the key networkActivationService.userTorEnabled and persists the userTorEnabled setting.
When network conditions permit (location permission granted, mutual favorites established, or presence in a location channel) and the device has reachable connectivity, the service calls torController.setAutoStartAllowed(true) and applyTorState(torDesired:). This triggers TorManager.startIfNeeded() if the user has enabled Tor, or initiates shutdown if disabled.
// User toggles Tor in Settings
Toggle("Use Tor for network", isOn: $networkActivationService.userTorEnabled)
.onChange { enabled in
NetworkActivationService.shared.setUserTorEnabled(enabled)
}
TorURLSession: Proxy-Aware Networking
The TorURLSession class in localPackages/Arti/Sources/TorURLSession.swift provides a centralized URLSession that automatically switches between direct connections and SOCKS5-proxied connections based on the Tor preference. The proxy configuration mirrors the TorManager defaults, targeting 127.0.0.1:39050.
When Tor restarts, TorURLSession recreates its underlying session to bind to the new proxy port, ensuring that requests do not leak onto clearnet during transitions.
// Making requests through the Tor-enabled session
let url = URL(string: "https://example.com/resource")!
let (data, response) = try await TorURLSession.shared.session.data(for: URLRequest(url: url))
Fail-Closed Security Model
BitChat implements a fail-closed networking model that prevents IP leaks. The TorManager.torEnforced property forces all network calls to wait for TorManager.isReady. If Tor is not ready, requests are queued rather than falling back to clearnet.
The compile-time flag BITCHAT_DEV_ALLOW_CLEARNET disables this enforcement for local development. In production builds without this flag, the system guarantees that no traffic leaves the device unproxied unless the user explicitly disables Tor in settings.
// Programmatically forcing a Tor restart after network changes
await TorManager.shared.ensureRunningOnForeground()
Summary
- BitChat embeds Arti as an in-process Rust library via XCFramework, eliminating external binary dependencies.
- The
TorManagersingleton monitors bootstrap progress with a 75-second deadline and exposes readiness viaisReadyandbootstrapDidStall. NetworkActivationServicecoordinates Tor activation based on user preferences stored undernetworkActivationService.userTorEnabledand current network policy.TorURLSessionprovides a proxy-awareURLSessionthat routes traffic through127.0.0.1:39050when Tor is active.- Fail-closed architecture ensures requests wait for Tor readiness or fail rather than leaking onto clearnet, unless the
BITCHAT_DEV_ALLOW_CLEARNETflag is set.
Frequently Asked Questions
How does BitChat handle Tor bootstrap failures?
If Arti fails to establish a connection within 75 seconds, TorManager sets bootstrapDidStall to true and notifies the UI. The app queues network requests rather than falling back to clearnet, maintaining the fail-closed guarantee unless the user explicitly disables Tor routing in settings.
Can developers bypass Tor for local testing?
Yes. The BITCHAT_DEV_ALLOW_CLEARNET compile-time flag disables torEnforced, allowing clearnet connections during development. Production builds should never include this flag to ensure the fail-closed privacy guarantee remains active.
Where does BitChat store the user's Tor preference?
The NetworkActivationService persists the Tor setting using UserDefaults with the key networkActivationService.userTorEnabled. This boolean drives the userTorEnabled property and determines whether applyTorState(torDesired:) triggers startIfNeeded() or shutdown.
What happens to network requests when Tor is restarting?
TorURLSession recreates its underlying URLSession instance when Tor restarts to bind to the new SOCKS5 port. During the transition, the fail-closed mechanism ensures requests await isReady or timeout rather than transmitting over clearnet, preventing IP leaks during circuit re-establishment.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →