# BitChat Noise Protocol Integration: Session Establishment and Forward Secrecy Implementation

> Discover BitChat's Noise Protocol integration for secure session establishment and forward secrecy using Curve25519 keys, ephemeral pre-keys, and auto re-keying.

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

---

**BitChat implements the Noise XX handshake pattern with Curve25519 static identity keys to establish mutually authenticated sessions, while providing forward secrecy through ephemeral pre-key envelopes and automatic session re-keying.**

BitChat, developed in the permissionlesstech/bitchat repository, leverages the Noise Protocol Framework to secure peer-to-peer messaging channels without trusted third parties. The `NoiseEncryptionService` class in [`bitchat/Services/NoiseEncryptionService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NoiseEncryptionService.swift) serves as the primary façade for cryptographic operations, managing persistent identity keys, per-peer session state machines, and forward-secret courier delivery. This architecture combines static-key authentication with ephemeral key rotation to protect both real-time and asynchronous communications.

## How BitChat Implements the Noise XX Handshake Pattern

The Noise Protocol Framework provides the foundation for BitChat's end-to-end encryption, utilizing specific handshake patterns tailored to different communication phases.

### Static Identity Key Management

Every BitChat client maintains persistent cryptographic identities stored in the iOS Keychain. According to lines 54-86 in [`NoiseEncryptionService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseEncryptionService.swift), the initializer generates or loads a **Curve25519 static identity key** (used for Noise static key operations) and an **Ed25519 signing key** for message authentication.

These keys persist across app launches, enabling consistent peer identification while remaining protected by the operating system's secure enclave.

### Session Initialization and Handshake Flow

The `NoiseSessionManager` coordinates per-peer session state. Lines 31-33 instantiate this manager with the static identity key and configuration timers. To establish a new session, the service calls `initiateHandshake(with:)` (lines 96-104), which generates raw Noise XX initiation bytes.

When the peer responds, `processHandshakeMessageWithResult` (lines 16-18) handles the incoming handshake bytes. Upon successful transcript completion, `handleSessionEstablished` (lines 23-30) derives a fingerprint from the remote static key and notifies registered authentication handlers.

## Forward Secrecy Mechanisms in BitChat

Beyond the initial handshake, BitChat implements multiple layers of forward secrecy to protect historical messages even if long-term keys are compromised.

### One-Way X-Pattern Envelopes for Courier Delivery

For store-and-forward messaging (courier mode), BitChat uses **Noise X pattern** envelopes encrypted to static keys. The `sealCourierPayload` and `openCourierPayload` methods (lines 46-62) create these envelopes. While this provides encryption, it lacks forward secrecy because the static key remains constant.

### Pre-Key Bundles for True Forward Secrecy

To achieve true forward secrecy for asynchronous messaging, BitChat implements one-time **pre-key envelopes**. Each envelope encrypts to an ephemeral pre-key that is destroyed immediately after decryption.

The `sealPrekeyPayload` method (lines 92-106) constructs envelopes with a dedicated `prekeyPrologue` and X pattern, ensuring only the holder of the specific pre-key can decrypt the content:

```swift
// Assume we already have a PrekeyBundle from the remote peer
let prekey = remoteBundle.prekeys.first!

// Seal a payload to the ephemeral pre-key
let sealed = try noiseService.sealPrekeyPayload(payloadData,
                                                recipientPrekey: prekey)

// Transmit `sealed` via a courier (store-and-forward)

```

On receipt, `openPrekeyPayload` (lines 119-136) decrypts the message and marks the pre-key as consumed:

```swift
// On receipt, open it and consume the pre-key
let (plain, senderKey, consumed) = try noiseService.openPrekeyPayload(sealed,
                                                                     prekeyID: prekey.id)

print("Decrypted:", String(data: plain, encoding: .utf8)!)
print("Sender static key:", senderKey.hexString)
print("Pre-key was consumed:", consumed)

```

### Automatic Session Re-Keying

BitChat maintains forward secrecy for active sessions through periodic re-keying. Lines 58-60 configure a `rekeyTimer` that fires every 60 seconds, triggering `checkSessionsForRekey`. When a session exceeds its key lifetime, `initiateAutomaticRekey` (lines 70-78) generates new handshake initiation bytes:

```swift
// The service runs a background timer (every 60 s) that calls:
noiseService.checkSessionsForRekey()   // internal call

// When a session needs rotation, the service emits:
noiseService.onRekeyHandshakeReady = { peer, initiation in
    // Send the new handshake initiation to the peer
}

```

This automatic rotation ensures that session keys are refreshed without user intervention, limiting the window of exposure if keys are compromised.

## Complete Session Establishment Workflow

Combining these components, a typical secure messaging flow involves identity verification, handshake completion, and encrypted payload transmission:

```swift
let noiseService = NoiseEncryptionService(keychain: myKeychain)

// 1️⃣ Initiate the handshake (raw XX initiation bytes)
let initData = try noiseService.initiateHandshake(with: peerID)

// Send `initData` over BLE to the peer…

// 2️⃣ Process the peer's response (handled by the BLE layer)
let response = try noiseService.processHandshakeMessage(with: peerID, message: incomingData)

// Once the handshake completes, the session is ready (lines 81-88 guard encryption):
if noiseService.hasEstablishedSession(with: peerID) {
    // 3️⃣ Encrypt a payload
    let ciphertext = try noiseService.encrypt(messageData, for: peerID)
    // send `ciphertext`
}

```

## Key Source Files and Architecture

| File | Purpose |
|------|---------|
| [`bitchat/Services/NoiseEncryptionService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NoiseEncryptionService.swift) | Core high-level API for Noise XX handshake, session management, encryption/decryption, forward-secret pre-key envelopes, and automatic re-keying. |
| [`bitchat/Services/NoiseSessionManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NoiseSessionManager.swift) | Low-level state machine tracking per-peer sessions, handling handshake transcripts, and performing re-key operations. |
| [`bitchat/Services/NoiseHandshakeState.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NoiseHandshakeState.swift) | Wraps the generic Noise protocol library; creates initiator/responder states for XX and X patterns. |
| [`bitchat/Services/NoiseRateLimiter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NoiseRateLimiter.swift) | Rate-limits handshake and message attempts to mitigate DoS attacks. |
| [`bitchatTests/Services/NoiseEncryptionServiceTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/NoiseEncryptionServiceTests.swift) | Unit tests validating handshake flow, session replacement, re-key, and forward-secret envelope handling. |

## Summary

- **BitChat uses Noise XX** for mutual authentication during session establishment, binding Curve25519 static identity keys to each session.
- **Forward secrecy** is achieved through two mechanisms: one-time pre-key envelopes that are consumed after use (X pattern with ephemeral keys), and automatic re-keying timers that rotate session keys every 60 seconds.
- **Static identity persistence** uses Curve25519 and Ed25519 key pairs stored in the iOS Keychain, initialized in `NoiseEncryptionService` lines 54-86.
- **Asynchronous messaging** relies on `sealPrekeyPayload` and `openPrekeyPayload` to encrypt messages to ephemeral pre-keys, preventing decryption by future key compromises.
- **Session lifecycle** is managed by `NoiseSessionManager`, with handshake initiation at lines 96-104 and processing at lines 16-18 of the main service file.

## Frequently Asked Questions

### How does BitChat achieve forward secrecy for offline messages?

BitChat implements forward secrecy for store-and-forward messaging using **pre-key envelopes**. The `sealPrekeyPayload` method encrypts messages to a one-time ephemeral pre-key from the recipient's bundle. When `openPrekeyPayload` decrypts the message, it immediately consumes and deletes the pre-key, ensuring that even if the recipient's long-term static key is later compromised, the historical message cannot be decrypted.

### What is the difference between courier envelopes and pre-key envelopes in BitChat?

**Courier envelopes** use the Noise X pattern with static keys (`sealCourierPayload` / `openCourierPayload`, lines 46-62), providing encryption but no forward secrecy since the static key persists. **Pre-key envelopes** (lines 92-106 and 119-136) use ephemeral pre-keys that are destroyed after use, providing true forward secrecy for asynchronous delivery where the recipient is offline.

### How often does BitChat rotate session keys?

BitChat runs a background timer every **60 seconds** (configured at lines 58-60) that calls `checkSessionsForRekey`. If a session has exceeded its key lifetime, `initiateAutomaticRekey` (lines 70-78) triggers a new Noise handshake to establish fresh session keys, limiting the window of vulnerability for active conversations.

### Where are the static identity keys stored in BitChat?

The **Curve25519 static identity key** and **Ed25519 signing key** are persisted in the **iOS Keychain** during `NoiseEncryptionService` initialization (lines 54-86). This provides hardware-backed security and ensures that identity keys survive app restarts while remaining inaccessible to other applications or unauthorized users.