How Bitchat Android Integrates Tor for Private Internet Access: Complete Technical Guide

Bitchat Android integrates Tor by embedding a custom-built Arti client (Rust's native Tor implementation) through the ArtiTorManager singleton, which exposes a local SOCKS proxy and rebuilds all OkHttp clients to route HTTP, WebSocket, and Nostr traffic through the Tor network without external binaries.

Modern messaging apps demand privacy by default. Bitchat Android, an open-source secure messaging client, implements onion-routing directly inside the application using Arti—The Tor Project's official Rust implementation. This architecture delivers true private internet access without requiring users to install separate Tor apps or binaries, fully compliant with Google Play's 16 KB page-size constraints.


Architecture Overview: The ArtiTorManager Singleton

At the heart of Bitchat's Tor integration sits ArtiTorManager, a Kotlin singleton that orchestrates the entire Tor lifecycle. Unlike traditional Tor integrations that spawn external processes, this manager embeds Arti directly via the info.guardianproject.arti library imported in app/src/main/java/com/bitchat/android/net/ArtiTorManager.kt.

The manager coordinates five critical responsibilities:

  1. Tor process bootstrap – Initializes and monitors the Arti runtime
  2. SOCKS proxy exposure – Publishes a local proxy address immediately upon activation
  3. State publication – Emits TorStatus updates through a MutableStateFlow
  4. Lifecycle resilience – Handles restarts, bind retries, and inactivity detection
  5. Client reconstruction – Triggers OkHttpProvider to rebuild network stacks on state changes

This fail-closed design guarantees that when Tor mode is enabled, the SOCKS proxy address is published before the Tor circuit finishes bootstrapping—preventing any plaintext traffic from leaking during the transition period.


Bootstrapping the Arti Tor Process

The initialization sequence begins when BitchatApplication calls ArtiTorManager.getInstance().init(this) in onCreate(). This single setup call prepares the manager to respond to user preference changes throughout the app lifecycle.

When a user enables Tor via TorPreferenceManager, the manager invokes applyMode(), which chains into startArti(). The Arti library then performs asynchronous bootstrap logging, with progress percentages streamed through statusFlow for real-time UI updates.

// BitchatApplication.kt
class BitchatApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // Initialise the Tor manager once for the whole process
        ArtiTorManager.getInstance().init(this)
    }
}

The bootstrap process handles mobile-specific failure modes: port binding conflicts trigger automatic retry with alternative ports, and bootstrap inactivity exceeding AppConstants.Tor timeout thresholds initiates supervised restart sequences.


SOCKS Proxy Configuration and Fail-Closed Networking

Bitchat's privacy guarantees depend on immediate proxy publication. The default SOCKS port is defined in AppConstants.Tor.DEFAULT_SOCKS_PORT (found in app/src/main/java/com/bitchat/android/util/AppConstants.kt), though the manager dynamically selects available ports when conflicts occur.

The critical architectural decision: proxy availability precedes circuit readiness. Applications attempting network calls receive the proxy address instantly, but connections block until Tor establishes its first circuit. This prevents the common leak pattern where apps "fall back" to direct connections during Tor startup.

OkHttpProvider in app/src/main/java/com/bitchat/android/net/OkHttpProvider.kt implements the client-side integration. It maintains singleton HTTP and WebSocket clients that are atomically rebuilt whenever ArtiTorManager.statusFlow emits a state change:

suspend fun fetchViaTor(url: String): String {
    // OkHttpProvider always uses the current SOCKS proxy, if any
    val client = OkHttpProvider.httpClient()
    val request = Request.Builder().url(url).build()
    client.newCall(request).execute().use { response ->
        if (!response.isSuccessful) error("Unexpected code $response")
        return response.body?.string().orEmpty()
    }
}

For operations requiring guaranteed circuit establishment, awaitSelectedRoute() provides suspendable waiting with configurable timeout:

suspend fun safeFetch(url: String): String {
    // Wait up to 10 seconds for Tor to finish bootstrapping
    val torReady = ArtiTorManager.getInstance().awaitSelectedRoute(10_000)
    require(torReady) { "Tor proxy not ready" }
    return fetchViaTor(url)
}

State Flow Architecture for UI Integration

ArtiTorManager exposes statusFlow: MutableStateFlow<TorStatus> as the single source of truth for Tor connectivity. This Kotlin Flow emits immutable state objects containing:

  • Current TorMode (ON/OFF/STARTING/ERROR)
  • Bootstrap completion percentage
  • Error messages for debugging
  • Active proxy address

UI components consume this flow using Compose's collectAsState():

val torManager = remember { ArtiTorManager.getInstance() }
val torStatus by torManager.statusFlow.collectAsState()

Switch(
    checked = torStatus.mode == TorMode.ON,
    onCheckedChange = {
        // Persisted via TorPreferenceManager; manager reacts automatically
        TorPreferenceManager.setMode(application, if (it) TorMode.ON else TorMode.OFF)
    }
)

The AboutSheet.kt, ChatHeader.kt, and location channel sheets all implement this pattern, providing consistent Tor status visibility without direct manager coupling.


Nostr Relay Integration

Bitchat's decentralized messaging relies on Nostr protocol relays. NostrRelayManager in app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt registers listeners on ArtiTorManager.statusFlow to reset WebSocket connections when Tor mode changes.

This ensures that relay traffic—which carries message content and metadata—immediately migrates to Tor routes upon user activation, without requiring manual reconnection or app restart. The WebSocket client obtained through OkHttpProvider.webSocketClient() inherits the same SOCKS proxy configuration as standard HTTP clients, providing uniform privacy coverage.


Lifecycle Resilience on Mobile Networks

Android's aggressive background restrictions and intermittent connectivity demand robust Tor process management. ArtiTorManager implements:

  • Bind retry logic – Detects BindException on SOCKS port allocation and iterates through alternative ports
  • Inactivity watchdog – Monitors bootstrap progress; triggers restart if stall duration exceeds configured threshold
  • Graceful degradation – Preserves error state in statusFlow for user-visible diagnostics rather than silent failure

These mechanisms ensure Tor availability survives airplane mode transitions, VPN conflicts, and network handoffs without user intervention.


No External Dependencies: Google Play Compliance

Traditional Tor implementations require tor binaries or Orbot integration, complicating distribution and violating Play Store policies on executable code. Bitchat's pure-Kotlin/Rust Arti integration satisfies:

  • 16 KB page-size constraint – No native executable pages requiring special alignment
  • Single APK distribution – All Tor functionality self-contained
  • Automatic updates – Arti library updates through standard Gradle dependency management

This architecture eliminates the "install Orbot first" friction that degrades user experience in other privacy-focused applications.


Summary

  • ArtiTorManager (app/src/main/java/com/bitchat/android/net/ArtiTorManager.kt) provides singleton orchestration of the embedded Arti Tor client
  • Fail-closed SOCKS proxy publishes immediately on Tor activation, blocking connections until circuit establishment
  • OkHttpProvider atomically rebuilds HTTP/WebSocket clients to route all traffic through the current proxy
  • statusFlow delivers reactive Tor state to Compose UI components with bootstrap progress and errors
  • Nostr relay traffic inherits Tor routing through shared OkHttpProvider client reconstruction
  • Zero external binaries achieves Google Play compliance while maintaining full Tor functionality

Frequently Asked Questions

Does Bitchat require Orbot or a separate Tor app?

No. Bitchat embeds Arti—the official Rust implementation of Tor—directly through the info.guardianproject.arti library. The ArtiTorManager class handles all Tor functionality internally without external dependencies.

What happens if Tor fails to bootstrap?

The statusFlow emits TorStatus with error details, and the UI displays the failure state. Internally, ArtiTorManager implements retry logic with alternative port selection and inactivity-triggered restarts. Applications calling awaitSelectedRoute() receive false on timeout, allowing graceful degradation.

Is WebSocket traffic (Nostr relays) also routed through Tor?

Yes. NostrRelayManager listens to ArtiTorManager.statusFlow and resets connections when Tor mode changes. All WebSocket clients are obtained through OkHttpProvider.webSocketClient(), which configures the SOCKS proxy identically to HTTP clients.

How do I verify my connection is using Tor?

Collect ArtiTorManager.getInstance().statusFlow and check torStatus.mode == TorMode.ON. The bootstrap percentage indicates circuit establishment progress. For programmatic verification, awaitSelectedRoute() confirms operational Tor routing before sensitive operations.

Why Arti instead of the traditional C Tor implementation?

Arti provides memory-safe Rust implementation suitable for mobile embedding, eliminates native binary distribution restrictions, and integrates cleanly with Kotlin coroutines through the Guardian Project's Android bindings.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →