How Bitchat Android Integrates with Tor Using the Arti Library for Enhanced Privacy

Bitchat Android routes all messaging traffic through the Tor network by wrapping the Rust-based Arti library in a three-layer JNI bridge, ensuring that no data transmits until the circuit is fully bootstrapped and verified.

Bitchat Android is a permissionless messaging application that leverages the Arti library—an official Rust implementation of the Tor protocol—to provide users with censorship-resistant, anonymous communication. According to the bitchat-android source code, the integration embeds Arti directly via the Guardian Project's arti-mobile-ex patterns, creating a self-contained privacy layer that manages its own SOCKS proxy lifecycle through a reactive Kotlin service.

The Three-Layer Tor Architecture

The Bitchat Android Tor integration consists of three distinct layers that bridge the Rust-based Arti binaries with the Android Java/Kotlin runtime.

Native JNI Bridge

The lowest layer is org.torproject.arti.ArtiNative, which provides the JNI entry points to the compiled Arti binaries. This class exposes native methods including initialize(), startSocksProxy(), and stop(), handling the direct memory interfacing between Kotlin and the Rust Tor implementation.

ArtiProxy Wrapper

The middle layer, info.guardianproject.arti.ArtiProxy, supplies a high-level Java/Kotlin API that mirrors the Guardian Project reference implementation. The ArtiProxy.Builder class (see its build() method at lines [79‑81]) constructs proxy instances configured with SOCKS and DNS ports:

val proxy = ArtiProxy.Builder(application)
    .setSocksPort(9050)
    .setDnsPort(9051)
    .setLogListener { Log.i("Arti", it ?: "") }
    .build()

The start() method (lines [50‑82]) registers the log callback, initializes the native runtime, and launches the SOCKS proxy daemon.

ArtiTorManager Service

The top layer is com.bitchat.android.net.ArtiTorManager, a singleton service that manages the complete lifecycle of the Arti client. This class exposes a reactive StateFlow<TorStatus> that the UI and network stack consume to react to Tor state changes, implements bootstrap monitoring via handleArtiLogLine (lines [51‑35]), and coordinates port binding retries through scheduleRetry() (lines [13‑28]).

Application Bootstrap and Initialization

Tor integration begins in com.bitchat.android.BitchatApplication. During onCreate(), the application initializes the Tor subsystem once per process:

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

The init() method (lines [22‑63]) creates the ArtiProxy builder, configures ports 9050 and 9051, and registers a log listener that forwards Arti log lines into the manager's internal state machine (see the log listener defined in init lines [30‑45]).

Tor State Management and Bootstrapping

When users enable Tor, ArtiTorManager.applyMode() (lines [94‑55]) triggers startArti(), which transitions the proxy through several distinct states.

Bootstrap Monitoring

The manager updates a MutableStateFlow<TorStatus> to STARTING and BOOTSTRAPPING (lines [78‑84]). A watchdog coroutine monitors the bootstrap percentage by parsing log lines through handleArtiLogLine. Once the log line “We have found that guard … is usable.” is received, the manager sets bootstrapPercent = 100 and transitions state to RUNNING (see handleArtiLogLine at lines [90‑100]).

Fail-Closed Routing

While Tor bootstraps, ArtiTorManager publishes an InetSocketAddress("127.0.0.1", currentSocksPort) (see socksAddr assignment at line [43]). Network consumers call awaitSelectedRoute(timeoutMs) (implementation at lines [81‑92]) to block until the proxy is fully functional, guaranteeing that no traffic leaks before Tor is ready.

Network Stack Integration

All HTTP clients are constructed through com.bitchat.android.util.OkHttpProvider. Whenever the Tor mode changes, resetNetworkConnections() (lines [30‑34]) recreates OkHttp instances with a proxy pointing at the local SOCKS address. The UI layer reads the statusFlow to display connection status, such as in ChatHeader.kt at line [138].

If the proxy fails to bind (detected by isBindError at lines [16‑22]), the manager implements a back-off retry schedule through scheduleRetry(), attempting connection with incremented ports up to MAX_RETRY_ATTEMPTS.

Practical Integration Examples

Reacting to Tor Status in Compose

UI components consume the reactive state to display anonymity status:

@Composable
fun TorStatusBanner() {
    val torProvider = remember { ArtiTorManager.getInstance() }
    val status by torProvider.statusFlow.collectAsState()
    if (status.mode == TorMode.ON && status.state == TorState.RUNNING) {
        Text("Tor is active – your traffic is anonymised")
    }
}

Ensuring Secure Request Routing

Before executing sensitive network operations, code must verify Tor readiness:

suspend fun performSecureRequest(client: OkHttpClient, request: Request) {
    val torManager = ArtiTorManager.getInstance()
    if (!torManager.isProxyEnabled()) {
        // Block until Tor is bootstrapped (or timeout after 10 s)
        torManager.awaitSelectedRoute(timeoutMs = 10_000)
    }
    client.newCall(request).execute().use { response ->
        // Handle response …
    }
}

Manual Tor Toggle

Settings screens can toggle the proxy via applyMode():

fun toggleTor(enabled: Boolean) {
    val tor = ArtiTorManager.getInstance()
    tor.applyMode(application, if (enabled) TorMode.ON else TorMode.OFF)
}

Key Implementation Files

File Role
app/src/main/java/com/bitchat/android/net/ArtiTorManager.kt Singleton orchestrating the Arti client, bootstrap monitoring, and reactive state management.
app/src/main/java/info/guardianproject/arti/ArtiProxy.kt High-level wrapper around native Arti; manages SOCKS proxy lifecycle.
app/src/main/java/org/torproject/arti/ArtiNative.kt JNI bridge to Rust Arti binaries.
app/src/main/java/com/bitchat/android/util/OkHttpProvider.kt Factory for OkHttp clients configured to use the local Tor SOCKS proxy.
app/src/main/java/com/bitchat/android/BitchatApplication.kt Application bootstrap initializing the Tor subsystem.

Summary

  • Bitchat Android embeds the Rust-based Arti library directly, eliminating dependency on external Tor apps like Orbot.
  • The architecture separates concerns into ArtiNative (JNI), ArtiProxy (API wrapper), and ArtiTorManager (lifecycle service).
  • The fail-closed design blocks all network requests until awaitSelectedRoute() confirms Tor is bootstrapped, preventing IP leakage.
  • A reactive StateFlow exposes TorStatus to the UI, enabling real-time connection indicators.
  • Automatic retry logic handles port binding conflicts with exponential back-off.

Frequently Asked Questions

How does Bitchat Android prevent traffic leakage before Tor is ready?

The ArtiTorManager implements a fail-closed design where network requests must call awaitSelectedRoute(timeoutMs) before executing. This method suspends coroutines until the bootstrap percentage reaches 100% and the state transitions to RUNNING, ensuring the SOCKS proxy is fully established at 127.0.0.1:9050 before any data transmits.

What is the difference between ArtiNative and ArtiProxy?

ArtiNative (org.torproject.arti.ArtiNative) is the low-level JNI bridge that exposes raw native functions from the compiled Rust Arti binaries. ArtiProxy (info.guardianproject.arti.ArtiProxy) is the high-level Kotlin wrapper that encapsulates JNI complexity, providing a builder pattern for configuration and managing the SOCKS proxy lifecycle through simple start() and stop() methods.

How does the app handle Tor connection failures?

When the native proxy fails to bind to the configured port (detected by isBindError at lines [16‑22]), ArtiTorManager triggers scheduleRetry() with exponential back-off, incrementing the port number and attempting connection up to MAX_RETRY_ATTEMPTS. If all retries exhaust, the StateFlow updates to reflect the failure state, alerting the UI to display connection errors.

Can users toggle Tor without restarting the application?

Yes. Calling ArtiTorManager.applyMode(context, TorMode.ON) or TorMode.OFF at runtime triggers startArti() or stopArti(), respectively. The method automatically invokes resetNetworkConnections() to recreate OkHttp clients with or without the SOCKS proxy configuration, allowing seamless switching between clearnet and Tor routing without process restarts.

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 →