# How BitChat Implements the Noise Protocol for End-to-End Encrypted P2P Communication

> Discover how BitChat uses the Noise Protocol for secure end-to-end encrypted P2P communication. Learn about its XX handshake, Curve25519, ChaCha20-Poly1305, and SHA-256 implementation.

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

---

**BitChat implements a complete Noise Protocol Framework using the XX handshake pattern with Curve25519 elliptic curve Diffie-Hellman, ChaCha20-Poly1305 authenticated encryption, and SHA-256 key derivation to secure every peer-to-peer channel with mutual authentication and forward secrecy.**

BitChat leverages a self-contained Swift implementation of the Noise Protocol Framework to secure all peer-to-peer (P2P) messaging without external cryptographic dependencies. The architecture centers on three core files—[`bitchat/Noise/NoiseProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Noise/NoiseProtocol.swift), [`bitchat/Services/NoiseEncryptionService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NoiseEncryptionService.swift), and [`bitchat/Noise/NoiseSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Noise/NoiseSession.swift)—which handle handshake negotiations, symmetric encryption, and session lifecycle management. This approach follows the official Noise specification while adding BitChat-specific features such as pre-key envelopes, courier envelopes, and automatic rekey timers.

## Core Cryptographic Architecture

The implementation splits responsibilities across distinct layers to separate low-level cryptographic primitives from high-level session management.

### Low-Level Primitives in NoiseProtocol.swift

The file [`bitchat/Noise/NoiseProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Noise/NoiseProtocol.swift) contains the full cryptographic stack. **`NoiseProtocolName`** constructs the protocol identifier string `Noise_XX_25519_ChaChaPoly_SHA256`, defining the specific cipher suite used throughout the application.

**`NoiseCipherState`** (lines 33-88) handles symmetric encryption using ChaCha20-Poly1305, tracks nonces, and implements a sliding-window replay-protection mechanism (lines 165-188) to reject duplicate or out-of-order messages. **`NoiseSymmetricState`** manages the handshake transcript hash and key derivation during the initial negotiation.

**`NoiseHandshakeState`** (line 541) drives the complete handshake state machine for patterns including XX, IK, NK, and X. It executes Diffie-Hellman calculations, processes message writes and reads, and validates public keys according to the Noise specification.

### High-Level Service Interface

**`NoiseEncryptionService`** in [`bitchat/Services/NoiseEncryptionService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NoiseEncryptionService.swift) (lines 12-110) provides the public API that the rest of the application uses. It loads static identity keys from the iOS Keychain, instantiates the `NoiseSessionManager`, and exposes methods for handshake initiation, encryption, decryption, and envelope creation.

### Session State Management

**`NoiseSessionManager`** maintains a map of `PeerID → NoiseSession`, where each `NoiseSession` wraps a `NoiseHandshakeState`. This component handles session creation, lookup, rekeying, and teardown. When the app receives encrypted data, the session manager routes it to the appropriate cryptographic context based on the peer identifier.

## The XX Handshake Pattern

BitChat uses the **Noise XX pattern** for its standard P2P communication, providing mutual authentication and forward secrecy through a three-message handshake.

### Three-Message Handshake Flow

The handshake documented in [`NoiseProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseProtocol.swift) (lines 38-45) proceeds as follows:

1. **Initiator → Responder**: Sends an ephemeral public key (`e`).
2. **Responder → Initiator**: Sends an ephemeral public key (`e`), performs ephemeral-ephemeral DH (`ee`), encrypts its static public key (`s`), and sends ephemeral-static DH (`es`).
3. **Initiator → Responder**: Sends its static public key (`s`) encrypted with the responder's static key (`se` DH).

Upon completion, both parties derive shared symmetric keys for the transport phase.

### State Machine Implementation

The actual state machine lives inside **`NoiseHandshakeState.writeMessage`** and **`readMessage`**. These methods perform the necessary DH operations (`ee`, `es`, `se`), update the `NoiseSymmetricState` with handshake transcript hashes, and transition between handshake states until the session is established.

## Session Lifecycle and Key Management

BitChat's session management ensures long-lived connections remain secure through automatic key rotation and persistent identity verification.

### Identity Key Persistence

The service generates and stores two key pairs in the iOS Keychain:

- A **static Noise key** (Curve25519) for Noise handshakes.
- An **Ed25519 signing key** for message authentication.

The method `generateAndSaveNoiseKey` (lines 664-685) creates these keys if they don't exist, while `generateAndSaveSigningKey` handles the Ed25519 material.

### Automatic Rekeying

A background `rekeyTimer` (lines 1069-1084) runs every minute to check `sessionManager.getSessionsNeedingRekey()`. When sessions exceed their key lifetime, the service triggers `initiateAutomaticRekey` to perform a new XX handshake transparently, ensuring forward secrecy is maintained over time.

### Peer Fingerprinting

BitChat identifies peers using the SHA-256 hash of their static public key (`remoteStaticKey.rawRepresentation.sha256Fingerprint()`). The service maintains two dictionaries—`peerFingerprints` and `fingerprintToPeerID`—for fast bidirectional lookup (lines 1023-1043).

## Asynchronous Messaging Envelopes

When peers are offline, BitChat supports store-and-forward messaging through one-way Noise envelopes that don't require bidirectional communication.

### Courier Envelopes (X Pattern)

**Courier envelopes** use the `Noise X` pattern (initiator-only) to encrypt data to a known static public key without performing a handshake. This provides confidentiality but no forward secrecy. The implementation in `sealCourierPayload` and `openCourierPayload` (lines 446-474) uses a prologue string `bitchat-courier-v1` to domain-separate these messages from standard handshakes.

### Pre-Key Envelopes

**Pre-key envelopes** also use the `Noise X` pattern but incorporate one-time pre-keys to achieve forward secrecy. The sender encrypts to the recipient's static key and a one-time ephemeral pre-key; once decrypted, the pre-key is consumed and cannot be reused. Implementation resides in `sealPrekeyPayload` and `openPrekeyPayload` (lines 992-1020) with the prologue `bitchat-prekey-v1`.

## Security Controls

Multiple defense mechanisms protect against common attacks on encrypted messaging systems.

### Replay Protection

**`NoiseCipherState`** implements a sliding-window nonce tracking system (lines 165-188) that rejects any message with a nonce below the current window or duplicate to previously received nonces. This prevents replay attacks where an adversary resends captured ciphertext.

### Rate Limiting and Validation

**`NoiseRateLimiter`** gates handshake attempts and message traffic through `allowHandshake` and `allowMessage` checks. **`NoiseSecurityValidator.validateMessageSize`** enforces a 64 KiB maximum payload size to prevent denial-of-service via memory exhaustion.

## Implementation Examples

### Initializing the Encryption Service

```swift
let keychain = KeychainManager()               // Conforms to KeychainManagerProtocol
let noiseService = NoiseEncryptionService(keychain: keychain)

```

This loads or generates the static Noise key and Ed25519 signing key (lines 45-86), making the service ready for P2P communication.

### Initiating a 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
}

```

The method calls `sessionManager.initiateHandshake` (lines 997-1018), constructing a `NoiseHandshakeState` with role `.initiator` and pattern `.XX`, and returns the raw handshake bytes for transport.

### Encrypting and Decrypting Messages

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

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

```

Encryption validates session existence (line 882) before delegating to `sessionManager.encrypt`. Decryption uses `sessionManager.decryptWithSessionGeneration` (lines 931-964) to handle both established sessions and handshake messages.

### Sending Store-and-Forward Messages

```swift
// Courier envelope (no forward secrecy)
let courierPayload = try noiseService.sealCourierPayload(data, recipientStaticKey: otherPeerStaticKey)

// Pre-key envelope (with forward secrecy)
let prekeyPayload = try noiseService.sealPrekeyPayload(data, recipientStaticKey: otherPeerStaticKey, prekey: oneTimePrekey)

```

These methods create one-way envelopes using the X pattern with appropriate prologue strings to distinguish envelope types.

## Summary

- **BitChat implements the complete Noise_XX_25519_ChaChaPoly_SHA256 protocol** in [`NoiseProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseProtocol.swift), providing mutual authentication and forward secrecy for P2P channels.
- **`NoiseEncryptionService`** abstracts keychain access, session management, and cryptographic operations behind a clean Swift API used by the transport layer.
- **Automatic rekeying** and **sliding-window replay protection** ensure long-lived sessions remain secure against key compromise and message replay.
- **Courier and Pre-key envelopes** enable encrypted store-and-forward messaging when peers are offline, with Pre-key envelopes providing forward secrecy through one-time keys.
- All cryptographic state is managed through **`NoiseSessionManager`** and **`NoiseSession`**, which track per-peer handshake states and enforce rate limits and message size validation.

## Frequently Asked Questions

### What Noise pattern does BitChat use for standard P2P communication?

BitChat uses the **Noise XX pattern** for standard peer-to-peer channels. This three-message handshake provides mutual authentication (both parties learn each other's static public keys) and forward secrecy (ephemeral keys are deleted after the handshake). The implementation resides in `NoiseHandshakeState` within [`bitchat/Noise/NoiseProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Noise/NoiseProtocol.swift).

### How does BitChat provide forward secrecy in store-and-forward messaging?

For asynchronous messaging, BitChat offers two envelope types. **Courier envelopes** use the X pattern without forward secrecy, encrypting directly to a static key. **Pre-key envelopes** also use the X pattern but include a one-time ephemeral pre-key; once the recipient decrypts the message, the pre-key is consumed and deleted, ensuring the encrypted data cannot be decrypted later even if the static key is compromised.

### What happens when a Noise session requires rekeying?

A background timer (`rekeyTimer`) checks for sessions needing rotation every minute. When triggered, `NoiseEncryptionService` calls `initiateAutomaticRekey` to perform a new XX handshake transparently between peers. This derives new symmetric keys without user intervention, maintaining forward secrecy over long-lived connections.

### How are peer identities verified in BitChat's Noise implementation?

BitChat generates a **fingerprint** for each peer by taking the SHA-256 hash of their static Curve25519 public key. These fingerprints are stored in bidirectional lookup dictionaries (`peerFingerprints` and `fingerprintToPeerID`) within the encryption service. During the XX handshake, the static public keys are transmitted encrypted and authenticated, allowing each peer to verify the other's identity against previously stored fingerprints or out-of-band verification.