# How Message Routing Works in BitChat: A Deep Dive Into Its Three-Layer Architecture

> Discover how BitChat message routing uses a three-layer architecture for reliable, private delivery, even offline. Explore transport selection, BLE mesh, and courier fallback.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-08-01

---

**BitChat routes messages through three overlapping layers—transport selection, BLE mesh source-routing, and courier fallback—to ensure reliable, privacy-preserving delivery even when internet connectivity fails.**

Understanding message routing in BitChat requires examining how the open-source Swift application balances immediacy against resilience. The permissionlesstech/bitchat repository implements a sophisticated router that automatically selects between direct transports, computes multi-hop Bluetooth Low Energy (BLE) routes, and falls back to sealed courier deposits when no path exists. This architecture enables offline-first messaging that survives network partitions while keeping content encrypted end-to-end.

## Peer Identification and Routing Data

Every peer in BitChat is identified by a **routing ID**—an 8-byte value derived from either a short Nostr-DM identifier or a Noise public key. This compact identifier powers all mesh-level forwarding decisions.

The conversion happens in [`bitchat/Nostr/PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/PeerID.swift) at line 91:

```swift
let peerID = PeerID(str: "nostr:deadbeef")
let routingData: Data = peerID.routingData  // 8-byte mesh routing identifier

```

The `routingData` property strips away cryptographic material, exposing only the minimum information the BLE mesh requires to forward packets. This design choice limits metadata exposure on the wireless medium while enabling efficient routing table lookups.

## Transport Selection and Priority Logic

The `MessageRouter` class in [`bitchat/Services/MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/MessageRouter.swift) implements a four-tier priority system for transport selection. At line 92, `sendPrivate(_:to:recipientNickname:messageID:)` evaluates available transports in strict precedence:

1. **Connected & secure** — A transport with an active, end-to-end encrypted session to the target peer
2. **Connected but insecure** — A transport with connectivity but without cryptographic authentication
3. **Reachable** — A transport reporting the peer as reachable (typically via BLE mesh observation)
4. **Courier fallback** — Depositing sealed copies on trusted intermediary peers or Tor bridges

```swift
let router = MessageRouter(transports: [bleTransport, nostrTransport])
router.sendPrivate(
    "Hello, world!",
    to: PeerID(str: "nostr:deadbeef"),
    recipientNickname: "Alice",
    messageID: UUID().uuidString
)

```

When a secure session exists (`canDeliverSecurely`), the router immediately enqueues the message and flags it as a **secure transmission**. If only the mesh reports reachability, the router defers to BLE source-routing. Otherwise, it triggers courier logic.

## BLE Mesh Source-Routing

BitChat implements **source routing** for its BLE mesh layer, meaning the sender pre-computes the full path rather than relying on per-hop forwarding tables. This approach suits resource-constrained mobile devices operating in intermittent connectivity environments.

At [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift) line 1001, `computeRoute(to:)` queries the local topology:

```swift
let route = bleService.computeRoute(to: peerID)  // -> [Data]?

```

The returned array contains routing IDs for each intermediate hop. If a valid route exists, line 1124's `applyRouteIfAvailable(_:to:)` performs three critical operations:

1. Injects the computed route into the `BitchatPacket` headers
2. Upgrades the packet to **protocol version 2**
3. Re-signs the packet with the sender's private key

```swift
var packet = BitchatPacket(...)
packet = bleService.applyRouteIfAvailable(packet, to: peerID)

```

Protocol version 2 distinguishes routed packets from direct transmissions and includes hop-limit fields to prevent indefinite forwarding loops. The re-signing step ensures route injection cannot be performed by attackers without the sender's keys.

## Courier Fallback and Bridge Deposits

When no transport can deliver promptly, BitChat's **courier system** provides asynchronous reliability. The router maintains an outbox of pending messages and attempts multiple deposit strategies:

- **Bridge deposits** — Posting sealed envelopes to Nostr relays when bridge mode is enabled
- **Peer couriers** — Selecting up to three trusted peers (mutual favorites) via `eligibleCouriers`
- **Tor bridge integration** — Depositing on Tor hidden services when configured

At line 101, `courierBecameAvailable(_:)` enables opportunistic retry:

```swift
func meshDidConnect(_ peerID: PeerID) {
    router.courierBecameAvailable(peerID)
}

```

When a new peer connects to the mesh, the router re-evaluates pending courier deposits. This handles scenarios where a courier was offline when the message originated but later rejoins the network.

The courier system enforces resource limits through `maxMessagesPerPeer` and `maxCouriersPerMessage`, preventing spam amplification and memory exhaustion attacks.

## Delivery Lifecycle and Acknowledgments

Message routing concludes with explicit delivery confirmation. When a recipient generates a delivery or read acknowledgment, the router invokes `clearRetainedMessage` to remove the pending copy from its outbox. This state transition updates the UI, removing the "sending" indicator and confirming successful transit through whatever path the router ultimately utilized.

## Summary

- **Peer identification** uses 8-byte routing data derived from Nostr or Noise keys via `PeerID.routingData` in [`bitchat/Nostr/PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/PeerID.swift)
- **Transport selection** follows a strict priority: connected secure → connected insecure → reachable → courier, implemented in `MessageRouter.sendPrivate`
- **BLE source-routing** pre-computes paths through `BLEService.computeRoute(to:)` and applies them with `applyRouteIfAvailable`, upgrading to protocol version 2
- **Courier fallback** deposits sealed envelopes on trusted peers and bridges, with opportunistic retry when couriers become available
- **Resource limits** and **re-signing** protect against amplification attacks and route tampering

## Frequently Asked Questions

### What is the 8-byte routing ID in BitChat?

The 8-byte routing ID is a compressed peer identifier derived from either a Nostr public key or a Noise public key. Implemented in `PeerID.routingData` at [`bitchat/Nostr/PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/PeerID.swift) line 91, it provides the minimal information needed for BLE mesh forwarding without exposing full cryptographic identities to the wireless medium.

### How does BitChat route messages when the internet is unavailable?

BitChat falls back to BLE mesh source-routing. The `MessageRouter` checks if the target peer is reachable via mesh observation, then `BLEService.computeRoute(to:)` calculates a multi-hop path through observed peers. If no route exists, the message enters the courier outbox for eventual delivery when connectivity returns.

### What is the courier system in BitChat?

The courier system provides asynchronous message delivery by depositing sealed, encrypted envelopes on trusted intermediary peers or Tor bridges. Located in `MessageRouter` lines 92-101, it selects up to three mutual-favorite peers and retries deposits when new couriers connect, ensuring messages survive extended network partitions.

### Why does BitChat use source routing instead of hop-by-hop forwarding?

Source routing pre-computes the entire path at the sender, reducing per-hop state on resource-constrained mobile devices. This design matches BitChat's BLE mesh environment where topology changes frequently and intermediate nodes lack persistent storage or stable power. The `applyRouteIfAvailable` method in [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) implements this by embedding the route directly into packet headers.