# How BitChat Encrypts and Routes Private Messages: End‑to‑End Security and Decentralized Delivery

> Discover how BitChat encrypts private messages with XChaCha20Poly1305 and routes them securely through decentralized mesh connections, couriers, or Nostr relays. Learn about BitChat's end-to-end security.

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

---

**BitChat encrypts private messages using X‑ChaCha20‑Poly1305 after establishing a Noise protocol session, then routes them through direct mesh connections, courier peers, or Nostr relays via the MessageRouter.**

The permissionlesstech/bitchat repository implements a zero‑trust messaging layer where every private communication is cryptically sealed before leaving the device. This article examines the exact mechanisms in [`SecureNoiseSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/SecureNoiseSession.swift), [`MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageRouter.swift), and supporting files that enable confidential delivery across intermittent mesh networks.

## End‑to‑End Encryption with Noise and X‑ChaCha20‑Poly1305

### Establishing the Noise Session

Before any payload transmission, BitChat establishes a cryptographic Noise session between the two peers. In [`bitchat/Noise/SecureNoiseSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Noise/SecureNoiseSession.swift), a Diffie‑Hellman exchange produces a shared secret that is converted into a symmetric key. This key remains opaque to intermediary nodes and is never transmitted over the wire.

### Sealing Messages with X‑ChaCha20‑Poly1305

Once the session is active, every private message is encrypted with **X‑ChaCha20‑Poly1305**, a nonce‑extended authenticated encryption construction. The compatibility wrapper in [`bitchat/Nostr/XChaCha20Poly1305Compat.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/XChaCha20Poly1305Compat.swift) handles the sealing operation:

```swift
// Encryption (simplified)
let sealed = try XChaCha20Poly1305Compat.seal(
    plaintext: messageData,
    key: sharedKey,               // derived from Noise DH exchange
    nonce24: random24ByteNonce   // 24‑byte nonce; first 4 bytes are zero
)

```

The resulting ciphertext can only be decrypted by the holder of the recipient’s static Noise public key, ensuring end‑to‑end confidentiality even if the message traverses untrusted couriers or public relays.

## Message Routing and Delivery Mechanisms

### The MessageRouter Transport Logic

In [`bitchat/Services/MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/MessageRouter.swift), the `sendPrivate` method evaluates four transport conditions to determine how to deliver the sealed payload:

- **Connected & secure session** (`transport.canDeliverSecurely`): The router enqueues the message in the outbox, marks it as a *secure transmission*, and calls `transport.sendPrivateMessage()`. The outbox retains the copy until a delivery or read ACK confirms receipt.

- **Connected but no secure session**: The router enqueues the message, sends it via the transport, and invokes `attemptCourierDeposit()`. The envelope remains sealed with the recipient’s static Noise key and is handed to nearby courier peers.

- **Reachable (mesh‑retention) but not connected**: The router enqueues the message, sends via the reachable transport, and attempts a courier deposit if the transport cannot deliver promptly. This provides robustness when the peer is offline.

- **No reachable transport**: The router enqueues the message only and unconditionally attempts a courier deposit. This guarantees eventual delivery via couriers or Nostr relays even when the mesh is completely unavailable.

### Courier Fallback for Offline Delivery

When direct delivery is impossible, the `MessageRouter` invokes `attemptCourierDeposit(messageID:for:)`. This system uses the recipient’s static Noise key to create a sealed envelope that is forwarded to **favorite** couriers (mutual favorites) or any verified peer listed in `CourierDirectory`. Couriers carry the opaque payload until they physically encounter the recipient or establish connectivity.

### Nostr Relay Integration

If a bridge to Nostr relays is configured, the same sealed payload can be posted to a public relay via `bridgeCourierDeposit()` in [`bitchat/Nostr/NostrProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/NostrProtocol.swift). The router treats the relay as a store‑and‑forward intermediary; because the content is already encrypted with the recipient’s Noise key, the relay cannot read the message, preserving confidentiality while enabling asynchronous retrieval.

## Read Receipts and Delivery Confirmation

Read receipts follow the identical encryption and routing path. When a user opens a chat, `PrivateChatManager.markAsRead(from:)` triggers `MessageRouter.sendReadReceipt()`, which encrypts the receipt using the same Noise‑derived symmetric key. The router attempts delivery over any reachable transport, falling back to retry logic if no route exists, ensuring the sender receives confirmation without exposing metadata.

## Summary

- **Noise protocol** sessions in [`SecureNoiseSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/SecureNoiseSession.swift) establish ephemeral shared secrets for symmetric encryption
- **X‑ChaCha20‑Poly1305** provides authenticated encryption with extended nonces via [`XChaCha20Poly1305Compat.swift`](https://github.com/permissionlesstech/bitchat/blob/main/XChaCha20Poly1305Compat.swift)
- The `MessageRouter` selects transport strategies dynamically: direct secure delivery, courier deposit, or Nostr relay bridging
- **Courier peers** store sealed envelopes using the recipient’s static Noise public key, enabling offline message queueing without decryption capability
- **Read receipts** receive identical encryption and routing treatment, maintaining metadata privacy throughout the confirmation flow

## Frequently Asked Questions

### What encryption algorithm does BitChat use for private messages?

BitChat uses **X‑ChaCha20‑Poly1305** with 24‑byte nonces for authenticated encryption. The symmetric key is derived from a Noise protocol session established between peers, as implemented in [`SecureNoiseSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/SecureNoiseSession.swift) and [`XChaCha20Poly1305Compat.swift`](https://github.com/permissionlesstech/bitchat/blob/main/XChaCha20Poly1305Compat.swift).

### How does BitChat deliver messages when the recipient is offline?

When the recipient is unreachable via direct mesh transport, the `MessageRouter` initiates a **courier deposit** via `attemptCourierDeposit()`. Couriers—mutual favorite peers or verified nodes from `CourierDirectory`—hold the sealed envelope until they physically encounter the recipient or connect to Nostr relays on their behalf.

### What is the role of couriers in BitChat routing?

Couriers act as decentralized message carriers for offline peers. They receive encrypted envelopes they cannot decrypt (sealed with the recipient’s static Noise key) and attempt delivery through opportunistic connections or Nostr bridges, ensuring eventual delivery without compromising message confidentiality.

### Are read receipts encrypted the same way as messages?

Yes. Read receipts follow the identical encryption and routing path. When `PrivateChatManager.markAsRead` is invoked, it calls `MessageRouter.sendReadReceipt()`, which encrypts the receipt using the same Noise‑derived key and routes it through available transports with automatic retry logic.