5.9 KiB
Bringing the Noise: Secure Communication in BitChat
Overview
BitChat implements the Noise Protocol Framework for end-to-end encryption, providing forward secrecy, identity hiding, and cryptographic authentication. This document details our Swift implementation and its integration with BitChat's decentralized mesh network.
The Noise Protocol Framework
Why Noise?
The Noise Protocol Framework offers:
- Forward Secrecy: Past messages remain secure even if keys are compromised
- Identity Hiding: Peer identities are encrypted during handshake
- Simplicity: Clean, auditable protocol with minimal complexity
- Performance: Efficient for resource-constrained mobile devices
- Flexibility: Supports various handshake patterns
The XX Pattern
BitChat uses the Noise XX pattern:
XX:
-> e
<- e, ee, s, es
-> s, se
This three-message pattern provides:
- Mutual authentication
- Identity encryption (identities revealed only after initial key exchange)
- Resistance to key-compromise impersonation
Implementation Architecture
Core Components
NoiseEncryptionService
The main service managing all Noise operations:
final class NoiseEncryptionService {
private let staticIdentityKey: Curve25519.KeyAgreement.PrivateKey
private let sessionManager: NoiseSessionManager
private let channelEncryption = NoiseChannelEncryption()
}
NoiseSession
Individual session state for each peer:
final class NoiseSession {
private var handshakeState: NoiseHandshakeState?
private var sendCipher: NoiseCipherState?
private var receiveCipher: NoiseCipherState?
private let remoteStaticKey: Curve25519.KeyAgreement.PublicKey?
}
NoiseSessionManager
Thread-safe session management:
final class NoiseSessionManager {
private var sessions: [String: NoiseSession] = [:]
private let sessionsQueue = DispatchQueue(label: "noise.sessions", attributes: .concurrent)
}
Handshake Flow
-
Initiator sends ephemeral key
let ephemeralKey = Curve25519.KeyAgreement.PrivateKey() let message = ephemeralKey.publicKey.rawRepresentation -
Responder sends ephemeral + encrypted static
// Generate ephemeral, perform DH, encrypt static key let encryptedStatic = encrypt(staticKey, using: sharedSecret) -
Initiator sends encrypted static
// Complete handshake, derive session keys let (sendKey, recvKey) = deriveSessionKeys(transcript)
Session Management
Sessions are managed with automatic cleanup and rekey support:
// Session lookup by peer ID
func getSession(for peerID: String) -> NoiseSession?
// Automatic session removal on disconnect
func removeSession(for peerID: String)
// Rekey detection
func getSessionsNeedingRekey() -> [(String, Bool)]
Integration with BitChat
Peer ID Rotation
Noise sessions persist across peer ID rotations through fingerprint mapping:
// Identity announcement after handshake
struct NoiseIdentityAnnouncement {
let peerID: String
let publicKey: Data
let nickname: String
let previousPeerID: String?
let signature: Data
}
Message Encryption
All messages are encrypted using established Noise sessions:
// Encrypt message
let encrypted = try noiseService.encrypt(messageData, for: peerID)
// Decrypt message
let decrypted = try noiseService.decrypt(encryptedData, from: peerID)
Security Properties
Forward Secrecy
- Ephemeral keys are generated for each handshake
- Past sessions cannot be decrypted with current keys
- Automatic rekey after 1 hour or 10,000 messages
Authentication
- Static keys provide long-term identity
- Handshake ensures mutual authentication
- MAC tags prevent message tampering
Privacy
- Peer identities encrypted during handshake
- Metadata minimization through padding
- No persistent session identifiers
Implementation Details
Cryptographic Primitives
- DH: X25519 (Curve25519)
- Cipher: ChaChaPoly (AEAD)
- Hash: SHA-256
- KDF: HKDF-SHA256
Error Handling
enum NoiseError: Error {
case handshakeFailed
case invalidMessage
case sessionNotEstablished
case decryptionFailed
}
Performance Optimizations
Connection Pooling
- Reuse established sessions
- Lazy handshake initiation
- Session caching with TTL
Message Batching
- Combine small messages
- Reduce encryption overhead
- Optimize for BLE MTU
Memory Management
- Bounded session cache
- Automatic cleanup of stale sessions
- Efficient key rotation
Protocol Version Negotiation
BitChat implements protocol version negotiation to ensure compatibility between different client versions:
Version Negotiation Flow
- Version Hello: Upon connection, peers exchange supported protocol versions
- Version Agreement: Peers agree on the highest common version
- Graceful Fallback: Legacy peers without version negotiation assume protocol v1
Message Types
case versionHello = 0x20 // Announce supported versions
case versionAck = 0x21 // Acknowledge and agree on version
Backward Compatibility
- Peers that don't send version negotiation messages are assumed to support v1
- Future protocol versions can be added to
ProtocolVersion.supportedVersions - Incompatible peers receive a rejection message and disconnect gracefully
Future Enhancements
Post-Quantum Readiness
- Hybrid handshake patterns
- Kyber integration plans
- Graceful algorithm migration
Advanced Features
- Multi-device support
- Session backup/restore
- Group messaging primitives
Conclusion
BitChat's Noise implementation provides encryption while maintaining the simplicity and performance required for a peer-to-peer messaging application. The protocol's elegant design ensures that people's communications remain private, authenticated, and forward-secure without sacrificing usability.