How to Initialize a Noise Session in BitChat's BLE: A Technical Guide
BitChat initializes a Noise XX session by deriving a NoiseSessionIdentifier from the peer's static public key, verifying session existence via NoiseEncryptionService.hasSession(), and invoking initializeSession() to execute the cryptographic handshake, while BLENoiseSessionQueues buffers outbound payloads until the BLE link is established.
BitChat implements end-to-end encryption over Bluetooth Low Energy (BLE) using the Noise protocol framework. According to the permissionlesstech/bitchat source code, the initialization process begins in BLEConnectionScheduler and relies on thread-safe session management through NoiseEncryptionService and BLENoiseSessionQueues.
Understanding the Noise Session Architecture
BitChat's BLE encryption stack separates concerns across four key components:
BLEConnectionScheduler: Orchestrates connection attempts and triggers session initialization when peers are discovered.NoiseEncryptionService: Manages cryptographic state, executes the Noise XX handshake, and handles encryption/decryption.NoiseSessionIdentifier: Immutable value type that uniquely identifies a session based on the peer's static public key.BLENoiseSessionQueues: Thread-safe buffer that stores outbound payloads when a BLE connection is pending.
Step-by-Step Noise Session Initialization Process
Step 1: Construct the NoiseSessionIdentifier
When BitChat discovers a BLE peer, it extracts the peer's 32-byte static Noise public key from the advertisement data. The system then creates a session identifier using the NoiseSessionIdentifier struct defined in localPackages/BitFoundation/Sources/BitFoundation/NoiseSessionIdentifier.swift:
public struct NoiseSessionIdentifier: Hashable {
public let peerPublicKey: Data
public init(peerPublicKey: Data) { self.peerPublicKey = peerPublicKey }
}
This identifier serves as the canonical key for all session lookups and queue mappings.
Step 2: Verify Existing Session State
Before initializing a new handshake, BLEConnectionScheduler checks whether an active session already exists. In bitchat/Services/BLE/BLEConnectionScheduler.swift, the startSessionIfNeeded(with:) method queries the noise service:
let sessionID = NoiseSessionIdentifier(peerPublicKey: peerPublicKey)
if !noiseService.hasSession(sessionIdentifier: sessionID) {
noiseService.initializeSession(with: peerPublicKey)
}
The hasSession(sessionIdentifier:) method returns a boolean indicating whether the internal sessions dictionary in NoiseEncryptionService contains an entry for the given identifier.
Step 3: Execute the Noise XX Handshake
If no session exists, NoiseEncryptionService.initializeSession(with:) creates a new Noise XX handshake state. As implemented in localPackages/BitFoundation/Sources/BitFoundation/NoiseEncryptionService.swift, this method generates the session identifier internally and allocates cryptographic state:
public func initializeSession(with peerPublicKey: Data) {
let identifier = NoiseSessionIdentifier(peerPublicKey: peerPublicKey)
// Perform Noise XX handshake (simplified)
sessions[identifier] = "session"
}
The actual handshake messages are exchanged over BLE, though the transport logic remains decoupled from the cryptographic state machine.
Step 4: Buffer Outbound Payloads During Initialization
While the BLE connection is establishing and the handshake is in progress, application data must wait. The BLENoiseSessionQueues class in bitchat/Services/BLE/BLENoiseSessionQueues.swift provides thread-safe storage using NSLock:
public func addPayload(_ payload: NoisePayload, for sessionIdentifier: NoiseSessionIdentifier) {
lock.lock(); defer { lock.unlock() }
var pending = pendingOutboundPayloads[sessionIdentifier] ?? []
pending.append(payload)
pendingOutboundPayloads[sessionIdentifier] = pending
}
The queues maintain three separate dictionaries—pendingOutboundPayloads, pendingOutboundData, and pendingOutboundPacketInfo—allowing the system to handle different payload types per session. Once the session is active, popAllPayloads(for:) retrieves and clears the queue:
public func popAllPayloads(for sessionIdentifier: NoiseSessionIdentifier) -> [NoisePayload] {
lock.lock(); defer { lock.unlock() }
let payloads = pendingOutboundPayloads[sessionIdentifier] ?? []
pendingOutboundPayloads[sessionIdentifier] = nil
return payloads
}
Complete Implementation Example
The following pattern demonstrates the full initialization flow as implemented in the BitChat codebase:
import Foundation
// Peer discovery context
let peerPublicKey: Data = /* 32-byte static key from BLE advertisement */
let sessionID = NoiseSessionIdentifier(peerPublicKey: peerPublicKey)
// Check and initialize session via BLEConnectionScheduler pattern
if !noiseService.hasSession(sessionIdentifier: sessionID) {
noiseService.initializeSession(with: peerPublicKey)
}
// Queue data while connection is pending
let payload = NoisePayload(/* encrypted data */)
sessionQueues.addPayload(payload, for: sessionID)
// When BLE link becomes ready, drain the queue
let readyPayloads = sessionQueues.popAllPayloads(for: sessionID)
readyPayloads.forEach { packetHandler.send($0) }
Summary
NoiseSessionIdentifieracts as the unique session key derived from the peer's static public key.BLEConnectionScheduler.startSessionIfNeeded(with:)orchestrates the initialization check and handshake trigger.NoiseEncryptionService.hasSession(sessionIdentifier:)prevents duplicate session creation.BLENoiseSessionQueuesprovides thread-safe payload buffering usingNSLockuntil the BLE connection and Noise handshake complete.
Frequently Asked Questions
How does BitChat prevent duplicate Noise sessions?
BitChat prevents duplicate sessions by checking the sessions dictionary inside NoiseEncryptionService using hasSession(sessionIdentifier:) before calling initializeSession(with:). This ensures only one cryptographic handshake occurs per peer public key.
What happens to messages sent before the BLE connection is ready?
Messages sent before the BLE link is established are stored in BLENoiseSessionQueues. The class maintains thread-safe dictionaries (pendingOutboundPayloads, pendingOutboundData) protected by NSLock, and payloads are transmitted immediately after the session becomes active via popAllPayloads(for:).
Where is the NoiseSessionIdentifier defined?
NoiseSessionIdentifier is defined in localPackages/BitFoundation/Sources/BitFoundation/NoiseSessionIdentifier.swift as a simple Hashable struct wrapping the peer's public key data, enabling its use as a dictionary key in session maps and queue collections.
What cryptographic pattern does BitChat use for BLE encryption?
According to the implementation in NoiseEncryptionService, BitChat uses the Noise XX handshake pattern. The initializeSession(with:) method sets up the handshake state that establishes authenticated encryption between the two BLE peers.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →