# BitChat Tor Integration Architecture: How the Arti SOCKS5 Proxy Works

> Explore BitChat's Tor integration architecture. Discover how the embedded Arti SOCKS5 proxy routes traffic, ensuring fail-closed networking via an in-process Swift wrapper.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: architecture
- Published: 2026-07-31

---

**BitChat routes all internet traffic through an embedded Arti Tor client that exposes a SOCKS5 proxy on `127.0.0.1:39050`, using an in-process Swift wrapper to enforce fail-closed networking without external tor binaries.**

The **permissionlesstech/bitchat** repository implements a privacy-first networking model where Nostr relay connections and directory fetches are forcibly tunneled through Tor. This architecture eliminates the need for users to run a separate Tor daemon by embedding the Arti Rust client directly into the iOS app via Swift Package Manager.

## How BitChat Embeds Arti as an In-Process Tor Client

Unlike traditional Tor apps that depend on a system `tor` binary or control port, BitChat vendors **Arti** as a local Swift package under `localPackages/Arti`. This package wraps a pre-built Rust static library (`localPackages/Arti/Frameworks/arti.xcframework`) and exposes it through Foreign Function Interface (FFI) bindings.

The integration is declared in the root [`Package.swift`](https://github.com/permissionlesstech/bitchat/blob/main/Package.swift):

```swift
.package(path: "localPackages/Arti")

```

Because Arti runs in-process, there is no `torrc` file to manage and no external process to spawn. The app links directly against the Rust static library, creating a completely self-contained Tor client that starts and stops with the application lifecycle.

## The SOCKS5 Proxy Interface

When Arti initializes, it binds a **SOCKS5 listener** on `127.0.0.1:39050`. This hard-coded port serves as the single network egress point for the entire app. According to the project's Tor integration documentation, this localhost endpoint is the only network interface exposed to the rest of the BitChat codebase.

All outbound WebSocket connections to Nostr relays and HTTP requests to the geo-relay directory must flow through this SOCKS5 proxy. The architecture deliberately avoids exposing raw sockets to prevent accidental clearnet leakage.

## TorManager: The Core Swift Controller

The `TorManager` class in [`localPackages/Arti/Sources/TorManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/Arti/Sources/TorManager.swift) orchestrates the Arti lifecycle, bootstrap monitoring, and readiness signaling. It maintains singleton state via `TorManager.shared` and publishes key properties including `isReady`, `isStarting`, `bootstrapProgress`, and `bootstrapDidStall`.

### Starting Arti via FFI

When the app enters the foreground and Tor is permitted, `startIfNeeded()` performs initialization:

```swift
await TorManager.shared.startIfNeeded()

```

This method ensures the filesystem layout exists under `~/Library/Application Support/bitchat/arti`, then invokes the FFI function `arti_start(dataDir, socksPort)` with the hard-coded port `39050`. The underlying Rust client begins bootstrapping into the Tor network immediately.

### Bootstrap Monitoring and Readiness Detection

After starting Arti, `TorManager` runs a readiness probe using `probeSocksOnce()`, which attempts to connect to the SOCKS socket using the `Network` framework. If the socket accepts within 60 seconds, `socksReady` flips to true.

Concurrently, `bootstrapPollLoop` queries the Arti FFI every second:

```swift
// Pseudocode representation of the polling logic
let progress = arti_bootstrap_progress()
let summary = arti_bootstrap_summary()

```

The loop runs for up to 75 seconds, waiting for `bootstrapProgress` to reach 100%. If bootstrap stalls, `bootstrapDidStall` becomes true and the app posts a `.TorBootstrapDidStall` notification, triggering UI error states. Only when both `socksReady` and `bootstrapProgress ≥ 100` does `isReady` become true, unblocking network operations.

### Foreground and Background Lifecycle

BitChat handles app lifecycle transitions strictly to prevent leaks:

- **Backgrounding**: `goDormantOnBackground()` clears readiness flags because Arti does not support true dormancy.
- **Foregrounding**: `ensureRunningOnForeground()` checks state and may trigger `restartArti()` if the client is not ready.

This ensures that Tor is either fully active or explicitly offline—never in a half-open state that might accidentally route traffic outside the proxy.

## Enforcing Tor for All Network Traffic

Unless the compile-time flag `BITCHAT_DEV_ALLOW_CLEARNET` is defined, the `torEnforced` property returns `true`. All network code uses `TorURLSession`, a shared `URLSession` configured via `setProxyMode(useTor:)`:

```swift
var config = URLSessionConfiguration.default
config.connectionProxyDictionary = [
    kCFNetworkProxiesSOCKSProxy: TorManager.shared.socksHost,
    kCFNetworkProxiesSOCKSPort: TorManager.shared.socksPort
]
let session = URLSession(configuration: config)

```

This creates a **fail-closed** model: if Arti cannot bootstrap or the SOCKS proxy is unreachable, outbound connections queue rather than falling back to clearnet. Both public and private Nostr messages route through the same Arti SOCKS5 proxy, ensuring consistent anonymity.

## Network Path Monitoring and Recovery

On platforms supporting the `Network` framework, `TorManager` instantiates an `NWPathMonitor` to observe connectivity changes. When the network path changes while the app is foregrounded and Arti is not ready, `pokeTorOnPathChange()` calls `ensureRunningOnForeground()` to initiate recovery.

This automatic healing handles scenarios where the device switches from Wi-Fi to cellular or encounters network interruptions that invalidate Tor circuits.

## Summary

- BitChat embeds Arti as a vendored Swift package (`localPackages/Arti`) wrapping a Rust static library, eliminating external Tor daemon dependencies.
- The Arti SOCKS5 proxy listens strictly on `127.0.0.1:39050` and serves as the exclusive network egress for all app traffic.
- [`TorManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/TorManager.swift) controls lifecycle via FFI functions `arti_start`, `arti_bootstrap_progress`, and `arti_bootstrap_summary`, enforcing a 75-second bootstrap timeout.
- The app implements a fail-closed policy: connections are proxied through `TorURLSession` or queued, never sent clearnet, unless the `BITCHAT_DEV_ALLOW_CLEARNET` flag is present.
- Network path monitoring via `NWPathMonitor` triggers automatic Tor recovery when connectivity changes occur.

## Frequently Asked Questions

### What port does BitChat's Arti SOCKS5 proxy use?

BitChat's Arti SOCKS5 proxy binds to **port 39050** on localhost (`127.0.0.1`). This port is hard-coded in the source code and defined in the `TorManager` initialization logic.

### How does BitChat handle Tor bootstrap failures?

If Arti fails to reach 100% bootstrap progress within approximately 75 seconds, `TorManager` sets `bootstrapDidStall` to true and posts a `.TorBootstrapDidStall` notification. The app treats this as a hard failure, keeping `isReady` false and preventing network requests from leaving the device until the user resolves the connectivity issue.

### Does BitChat require an external Tor daemon?

No. BitChat does not use the system `tor` binary or a control port. Instead, it embeds Arti as a static Rust library (`arti.xcframework`) vendored under `localPackages/Arti/Frameworks/`, linking it directly into the iOS app process via Swift Package Manager.

### How does BitChat maintain anonymity when the app backgrounds?

When the app backgrounds, `goDormantOnBackground()` clears all readiness flags, effectively pausing network activity. Because Arti does not support true dormant mode, BitChat treats the client as offline during backgrounding. Upon returning to the foreground, `ensureRunningOnForeground()` may execute `restartArti()` to re-establish the SOCKS5 proxy and ensure no clearnet connections occur until the Tor circuit is fully rebuilt.