# How BitChat Implements End-to-End Encryption Using the Noise Protocol

> Discover how BitChat implements end-to-end encryption with the Noise Protocol. Learn about its secure handshake, authenticated encryption, and session management for a robust communication platform.

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

---

**BitChat implements end-to-end encryption using a self-contained Noise Protocol Framework that employs the XX handshake pattern with Curve25519 ephemeral keys, ChaCha20-Poly1305 authenticated encryption, and SHA-256 key derivation, managed through a session-based architecture that supports mutual authentication, automatic rekeying, and store-and-forward messaging.**

BitChat, an open-source peer-to-peer messaging application maintained by permissionlesstech, secures all communications through a comprehensive implementation of the Noise Protocol Framework. The architecture combines low-level cryptographic primitives with high-level session management to provide forward-secret, mutually-authenticated channels over Bluetooth Low Energy (BLE) and other transports.

## Core Cryptographic Components

The implementation spans three primary Swift files that separate concerns between raw cryptography, session state, and application-facing APIs.

### NoiseProtocol.swift

The file [`bitchat/Noise/NoiseProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Noise/NoiseProtocol.swift) contains the complete cryptographic implementation, including the `NoiseCipherState` class (lines 33-88) which handles ChaCha20-Poly1305 encryption with nonce tracking and sliding-window replay protection. This component also defines `NoiseSymmetricState` for key derivation during handshakes and `NoiseHandshakeState` (line 541) to drive the full handshake pattern logic.

### NoiseEncryptionService.swift

Located at [`bitchat/Services/NoiseEncryptionService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NoiseEncryptionService.swift), this class provides the public API used by the rest of the application. It manages the device's static identity keys, creates and maintains the session manager, and exposes methods including `initiateHandshake`, `encrypt`, `decrypt`, and envelope helpers (lines 12-110).

### NoiseSession.swift

The [`bitchat/Noise/NoiseSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Noise/NoiseSession.swift) file defines `NoiseSessionManager`, which maintains a mapping of `PeerID` to `NoiseSession` objects. Each session wraps a `NoiseHandshakeState` and provides lifecycle management for re-keying, recovery, and rate-limiting enforcement.

## The XX Handshake Pattern

BitChat uses the **Noise XX pattern** for mutual authentication, which proceeds through three distinct message exchanges documented in the file header of [`NoiseProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseProtocol.swift) (lines 38-45):

1. **Initiator → Responder**: The initiator transmits its ephemeral public key (`e`).
2. **Responder → Initiator**: The responder sends its ephemeral key (`e`), performs the `ee` Diffie-Hellman operation, and transmits its static key (`s`) encrypted with the `es` DH result.
3. **Initiator → Responder**: The initiator sends its static key (`s`) encrypted using the `se` DH result.

The state machine inside `NoiseHandshakeState.writeMessage` and `readMessage` performs the necessary DH calculations and updates the symmetric state accordingly. The protocol identifier string follows the standard format: `Noise_XX_25519_ChaChaPoly_SHA256`.

## Session Lifecycle Management

The `NoiseEncryptionService` orchestrates a complete lifecycle for encrypted sessions:

- **Creation**: During initialization (lines 54-63), the service loads or generates the device's static Curve25519 key pair and Ed25519 signing key from the keychain, then instantiates a `NoiseSessionManager`.
- **Handshake Initiation**: The `initiateHandshake(with:)` method (lines 998-1018) requests the session manager to create a new `NoiseHandshakeState` with `role: .initiator` and `pattern: .XX`, returning the initial handshake bytes for transmission.
- **Handshake Processing**: Incoming handshake data routes through `processHandshakeMessageWithResult` (lines 813-846), which forwards bytes to the appropriate `NoiseHandshakeState`. Upon completion, the session marks itself as established and fires completion callbacks.
- **Data Transfer**: Established sessions use `encrypt(_:for:)` (lines 869-889) and `decrypt(_:from:)` (lines 921-931) to AEAD-seal payloads using the derived `NoiseCipherState`.
- **Automatic Rekeying**: A background `rekeyTimer` runs every minute to check `sessionManager.getSessionsNeedingRekey()` and triggers `initiateAutomaticRekey` (lines 1069-1084) to establish new session keys.
- **Teardown**: The `clearSession(for:)` method removes specific sessions, while `clearEphemeralStateForPanic()` wipes all session state during security events.

## Identity Keys and Fingerprinting

BitChat persists long-term identity keys in the iOS keychain through helper methods like `generateAndSaveNoiseKey` and `generateAndSaveSigningKey` (lines 664-685). Each peer is identified by a fingerprint calculated as the SHA-256 hash of its static public key using `remoteStaticKey.rawRepresentation.sha256Fingerprint()`.

The `NoiseEncryptionService` maintains bidirectional lookup dictionaries (`peerFingerprints` and `fingerprintToPeerID`) for fast peer identification during handshake processing (lines 1023-1043).

## Store-and-Forward Envelopes

When peers are offline, BitChat supports asynchronous messaging through two one-way envelope types implemented using the Noise X pattern:

- **Courier Envelopes**: Use `sealCourierPayload` and `openCourierPayload` (lines 446-474) to encrypt data to a known static key without forward secrecy.
- **Pre-key Envelopes**: Implemented via `sealPrekeyPayload` and `openPrekeyPayload` (lines 992-1020), these provide forward secrecy by encrypting to a one-time pre-key that is consumed after decryption.

Both envelope types use `NoiseHandshakeState` with pattern `.X` and include domain-separated prologues (`bitchat-courier-v1` or `bitchat-prekey-v1`) to prevent cross-protocol attacks.

## Security Features and Validation

The implementation includes multiple defensive layers:

- **Rate Limiting**: `NoiseRateLimiter` gates handshake attempts and message traffic through `allowHandshake` and `allowMessage` checks.
- **Message Size Validation**: `NoiseSecurityValidator.validateMessageSize` enforces a 64 KiB payload limit to prevent denial-of-service.
- **Replay Protection**: `NoiseCipherState` implements a sliding-window nonce tracking mechanism (lines 165-188) to reject duplicate or out-of-order messages.
- **Structured Error Handling**: Detailed enums including `NoiseEncryptionError`, `NoiseSessionError`, and `NoiseSecurityError` encode specific failure modes for debugging and user feedback.

## Implementation Examples

### Initializing the Encryption Service

```swift
let keychain = KeychainManager()               // Conforms to KeychainManagerProtocol
let noiseService = NoiseEncryptionService(keychain: keychain)
// Loads or generates static Noise and Ed25519 keys (lines 45-86)

```

### Initiating a Secure Handshake

```swift
do {
    let handshakeData = try noiseService.initiateHandshake(with: peerID)
    transport.send(handshakeData)               // BLEService or other transport
} catch {
    // Handle handshakeRequired, rate-limit, or other errors
}

```

### Encrypting Application Data

```swift
do {
    let encrypted = try noiseService.encrypt(messageData, for: peerID)
    transport.send(encrypted)
} catch NoiseEncryptionError.handshakeRequired {
    // Trigger a new handshake before retrying
}

```

### Decrypting Incoming Packets

```swift
do {
    let plaintext = try noiseService.decrypt(packetData, from: peerID)
    // Pass plaintext to higher-level message parser
} catch {
    // May indicate tampering, replay, or missing session
}

```

### Sending Offline Messages

```swift
let courierPayload = try noiseService.sealCourierPayload(
    data, 
    recipientStaticKey: otherPeerStaticKey
)
// Creates one-way envelope using X pattern (lines 446-456)

```

## Summary

- **BitChat implements a complete Noise Protocol stack** in [`NoiseProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseProtocol.swift) featuring the XX handshake, ChaCha20-Poly1305 encryption, and SHA-256 key derivation.
- **Session management** through `NoiseSessionManager` handles per-peer state, automatic rekeying every minute, and secure teardown.
- **Mutual authentication** derives from the XX pattern's three-phase handshake that exchanges and validates static public keys.
- **Store-and-forward messaging** uses Courier and Pre-key envelopes with domain-separated prologues to support offline communication.
- **Security controls** include rate limiting, 64 KiB message size validation, sliding-window replay protection, and comprehensive error handling.

## Frequently Asked Questions

### What Noise handshake pattern does BitChat use for peer authentication?

BitChat uses the **Noise XX pattern** implemented in `NoiseHandshakeState` (line 541 of [`NoiseProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseProtocol.swift)). This pattern provides mutual authentication and forward secrecy through a three-message exchange where both parties transmit ephemeral keys first, followed by encrypted static keys.

### How does BitChat encrypt messages after the initial handshake completes?

After handshake completion, BitChat uses the derived keys to initialize a `NoiseCipherState` object (lines 33-88). The `NoiseEncryptionService` then routes calls to `encrypt(_:for:)` and `decrypt(_:from:)` through the session manager, which applies ChaCha20-Poly1305 AEAD encryption with automatic nonce tracking and replay protection via a sliding window mechanism.

### What are Courier and Pre-key envelopes in BitChat?

**Courier envelopes** (`sealCourierPayload`, lines 446-474) allow one-way encryption to a known static key without forward secrecy for basic store-and-forward scenarios. **Pre-key envelopes** (`sealPrekeyPayload`, lines 992-1020) provide forward secrecy by encrypting to a one-time pre-key that is consumed after decryption. Both use the Noise X pattern with domain-specific prologues to prevent cross-protocol attacks.

### How does BitChat prevent replay attacks and ensure message freshness?

Replay protection is built into `NoiseCipherState` through a sliding-window nonce tracking system (lines 165-188) that rejects duplicate or out-of-order messages. Additionally, the `NoiseRateLimiter` class gates incoming handshakes and message traffic to prevent flooding attacks, while `NoiseSecurityValidator` enforces strict 64 KiB message size limits.