Noise Session Establishment and Key Exchange Process in BitChat: A Technical Deep Dive

BitChat establishes secure peer-to-peer sessions using the Noise Protocol Framework's XX handshake pattern, implemented through NoiseEncryptionService and NoiseSessionManager, performing three-message Diffie-Hellman exchanges with Curve25519 static keys and Ed25519 signatures to derive symmetric encryption keys.

The permissionlesstech/bitchat repository secures Bluetooth Low Energy (BLE) communications using the Noise Protocol Framework. Understanding the noise session establishment and key exchange process in BitChat reveals how the application achieves forward secrecy and authenticated encryption without centralized servers.

Core Cryptographic Components

BitChat's security architecture centers on two primary services that manage the entire key exchange lifecycle.

NoiseEncryptionService

The NoiseEncryptionService class in bitchat/Services/NoiseEncryptionService.swift serves as the high-level API for all cryptographic operations. It manages long-lived identity keys, coordinates handshake initiation, and handles session lifecycle events including automatic rekeying.

NoiseSessionManager

Contained within the BitFoundation package at localPackages/BitFoundation/Sources/BitFoundation/NoiseSessionManager.swift, this internal manager tracks per-peer handshake states and performs the concrete Diffie-Hellman calculations. It maintains NoiseHandshakeState instances for each active peer connection.

Step-by-Step Noise Session Establishment

The XX handshake pattern requires three messages to establish bidirectional authentication and forward secrecy. Below is the exact sequence implemented in the source code.

1. Static Identity Key Loading

On application startup, NoiseEncryptionService loads or generates two critical key pairs:

  • noiseStaticKey: A long-lived Curve25519 key for Noise protocol operations
  • ed25519SigningKey: An Ed25519 key for signing handshake messages and pre-key bundles

These keys are persisted in the device keychain during initialization (lines 45-85 and 88-108 in NoiseEncryptionService.swift). Every subsequent handshake uses these static keys as the root of trust.

2. Peer ID to Noise Key Mapping

BitChat derives short routing identifiers from static public keys to minimize BLE payload size. In localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift (lines 128-141), the system generates a 16-hex peer ID from the first 8 bytes of the SHA-256 fingerprint of the peer's 64-hex Noise static public key.

This mapping is stored in peerFingerprints and fingerprintToPeerID dictionaries, allowing the transport layer to route incoming handshake messages to the correct cryptographic session.

3. Handshake Initiation and the XX Pattern

When the application attempts to encrypt a message for a new peer, encrypt(_:for:) checks hasEstablishedSession. If no session exists, it calls onHandshakeRequired and throws handshakeRequired, triggering the UI or BLE layer to invoke initiateHandshake(with:) (lines 98-104 and 116-122 in NoiseEncryptionService.swift).

This method forwards the request to NoiseSessionManager, which constructs the first XX handshake message (Message 1) containing:

  • An ephemeral Curve25519 public key (e)
  • A static Curve25519 public key (s) encrypted with the ephemeral key

The raw handshake bytes are returned to the caller for transmission over BLE.

4. Diffie-Hellman Key Exchange

NoiseSessionManager maintains a NoiseHandshakeState for each peer that tracks the expected next message (Message 1 → Message 2 → Message 3). The state machine performs three critical DH calculations:

  • e ↔︎ remote static (s): Ephemeral to remote static key
  • e ↔︎ remote ephemeral (e): Ephemeral to ephemeral key
  • s ↔︎ remote static (s): Static to static key

These shared secrets are mixed with static keys using HKDF to derive the symmetric cipher keys for the session (referenced at lines 140-149 via sessionManager.handleIncomingHandshakeWithResult).

5. Session Finalization

When Message 3 is successfully processed and authenticated, NoiseSessionManager invokes its onSessionEstablished callback. The NoiseEncryptionService.handleSessionEstablished method (lines 224-254) stores the peer's fingerprint, logs the security event, and notifies registered handlers. At this point, the session status changes to established, and the cipher keys are ready for payload encryption.

Encryption and Decryption Operations

Once a session is established, the API simplifies to symmetric encryption calls.

Encrypting Payloads

The encrypt(_:for:) method (lines 68-88 in NoiseEncryptionService.swift) forwards plaintext to sessionManager.encrypt, which uses the cipher keys derived from the completed handshake. If no session exists, it throws NoiseEncryptionError.handshakeRequired, forcing the caller to initiate a new XX handshake.

Decrypting Payloads

Similarly, decrypt(_:from:) (lines 126-136) processes incoming ciphertext through the session manager. Failed decryptions indicate invalid sessions or man-in-the-middle attempts, requiring session clearance and potential re-handshaking.

Advanced Key Management Features

BitChat implements several extensions to the standard Noise protocol for real-world mobile constraints.

Automatic Rekeying

A background rekeyTimer periodically queries the session manager for sessions requiring rotation. When a session expires, initiateAutomaticRekey (lines 558-572 in NoiseEncryptionService.swift) creates a new XX handshake (Message 1) and notifies the transport layer via onRekeyHandshakeReady, ensuring forward secrecy over long-lived connections.

Static-Key Courier Mode

For store-and-forward scenarios when peers are offline, BitChat supports sealCourierPayload and openCourierPayload (lines 46-56). This one-way Noise X handshake uses only the recipient's static key. While this mode lacks forward secrecy, it guarantees message delivery to offline devices.

One-Time Pre-Keys

To enable forward-secret delivery without online handshakes, PrekeyBundle.swift manages temporary static keys. The sender performs a Noise X handshake against the pre-key, which is permanently deleted after use, providing perfect forward secrecy for asynchronous messaging.

Summary

  • BitChat implements the Noise XX handshake pattern through NoiseEncryptionService and NoiseSessionManager for authenticated, forward-secret peer-to-peer communication.
  • Static Curve25519 keys and Ed25519 signing keys are generated at startup (lines 45-108) and serve as the root of trust for all sessions.
  • Three-message handshake performs ephemeral-static and ephemeral-ephemeral DH exchanges to derive symmetric cipher keys.
  • Peer IDs are derived from SHA-256 fingerprints of Noise static keys to enable short BLE routing identifiers.
  • Automatic rekeying (lines 558-572) and one-time pre-keys provide extended forward secrecy and offline messaging capabilities.

Frequently Asked Questions

What Noise handshake pattern does BitChat use?

BitChat uses the Noise XX pattern for online peer-to-peer sessions, which provides mutual authentication and forward secrecy through three handshake messages. For offline store-and-forward scenarios, it falls back to Noise X using only static keys.

How does BitChat establish sessions without internet connectivity?

The protocol operates entirely over Bluetooth Low Energy (BLE). When two devices are in range, they exchange XX handshake messages directly. For offline messaging, BitChat uses one-time pre-keys or courier mode with static-key encryption, allowing messages to be stored and delivered when the recipient comes online.

What happens when a Noise session expires?

A background timer triggers automatic rekeying (lines 558-572 in NoiseEncryptionService.swift). The service initiates a new XX handshake with the peer to establish fresh cipher keys, maintaining forward secrecy over long-running conversations.

How does BitChat verify peer identities?

All handshake messages and pre-key bundles are signed with the sender's Ed25519 signing key. The verifySignature method validates the authenticity of the static Noise key before completing the handshake, preventing man-in-the-middle attacks during session establishment.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →