How BitChat Implements Noise Protocol Encryption for Peer-to-Peer Messaging
BitChat adopts the Noise Protocol Framework to provide end-to-end confidentiality, integrity, and authentication for all direct-peer traffic, implementing the XX handshake pattern with Curve25519 key exchange, ChaCha20-Poly1305 AEAD encryption, and SHA-256 hashing.
The permissionlesstech/bitchat repository secures its decentralized messaging layer through the Noise Protocol encryption framework, implementing the official Noise XX pattern for mutual authentication. This approach ensures that every direct peer connection maintains cryptographic protections without relying on centralized certificate authorities or trusted third parties.
Core Components of the Noise Protocol Encryption Stack
The Noise Protocol encryption logic resides primarily in bitchat/Noise/NoiseProtocol.swift, where several specialized state objects manage the cryptographic lifecycle.
NoiseHandshakeState
Located at line 35 in NoiseProtocol.swift, the NoiseHandshakeState class orchestrates the multi-message handshake sequence. It generates and consumes ephemeral keys, performs Diffie-Hellman operations, and derives transport keys through the getTransportCiphers(useExtractedNonce:) method.
NoiseSymmetricState
Found at line 10, NoiseSymmetricState maintains the chaining key and handshake hash while managing a NoiseCipherState instance for symmetric encryption during the handshake phase. It implements HKDF-based key derivation through methods like mixKey and mixKeyAndHash.
NoiseCipherState
Defined at line 30, NoiseCipherState wraps ChaCha20-Poly1305 and tracks a 64-bit nonce counter. It implements a 1024-message sliding-window replay protection scheme via isValidNonce and markNonceAsSeen, validating nonces received in explicit payload mode.
Protocol Infrastructure
The NoiseProtocolName struct at line 15 constructs canonical identifiers such as Noise_XX_25519_ChaChaPoly_SHA256, while NoiseError at line 40 enumerates failure conditions including uninitialized ciphers, replay detection, and invalid public keys.
The XX Handshake Flow
BitChat implements the Noise XX pattern for mutual authentication, requiring three messages to establish a secure session:
- Message 1 (Initiator → Responder): The initiator sends an ephemeral public key
e. - Message 2 (Responder → Initiator): The responder sends its ephemeral key, performs the
eeDiffie-Hellman operation, then transmits its static key encrypted under the derived key, followed by anesDH operation. - Message 3 (Initiator → Responder): The initiator sends its static key encrypted, then performs the
seDH operation.
After the third message, both peers invoke getTransportCiphers(useExtractedNonce:) to split the symmetric state into two NoiseCipherState instances—one for sending and one for receiving. When useExtractedNonce is true, the system enables "nonce-in-payload" mode, extracting the first four bytes of each ciphertext as the nonce for BLE-mesh compatibility.
Security Guarantees of the Noise Implementation
The implementation enforces several critical security properties:
- Mutual authentication: Both peers exchange static keys encrypted after DH steps, with static keys signed using Ed25519 elsewhere in the stack.
- Forward secrecy: Every session derives fresh keys from ephemeral DH operations (
ee,es,se), ensuring past sessions remain secure even if static keys are compromised. - Replay protection:
NoiseCipherStatetracks the highest received nonce and maintains a 1024-message sliding window to prevent replay attacks. - Key separation: The chaining key updates via HKDF after every DH operation, maintaining cryptographic separation between handshake and transport phases.
- Constant-time validation: Public keys undergo validation against low-order points and zero values using constant-time comparisons in
validatePublicKey. - Secure memory handling: Sensitive data including keys and nonce windows are explicitly wiped through
clearSensitiveData.
Integrating Noise Protocol Encryption with BitChat's Transport Layer
The Noise Protocol encryption integrates with the broader application through NoiseEncryptionService in bitchat/Services/NoiseEncryptionService.swift.
Peer Identification
After handshake completion, the 8-byte peer ID appearing in packet headers derives from the first 8 bytes of the static key's SHA-256 fingerprint, as defined in BitFoundation/PeerID.swift.
BLE-Mesh Support
For Bluetooth Low Energy mesh traffic, payloads include an explicit 4-byte nonce prefix by setting useExtractedNonce = true, allowing receivers to validate replay protection without shared state.
Voice and File Transfers
Specialized NoisePayloadType values (such as 0x20 for encrypted files and 0x08 for voice frames) are defined in MessageType.swift and encrypted using the same cipher states managed by NoiseSession.swift.
Implementation Example
The following Swift code demonstrates establishing a Noise XX handshake and encrypting messages:
// 1️⃣ Create a handshake (initiator) using a stored static key
let staticPriv = Curve25519.KeyAgreement.PrivateKey()
let handshake = NoiseHandshakeState(
role: .initiator,
pattern: .XX,
keychain: KeychainManager.shared,
localStaticKey: staticPriv,
remoteStaticKey: nil // unknown at start
)
// 2️⃣ Perform the three-message handshake
let msg1 = try handshake.writeMessage() // -> e
sendToPeer(msg1)
let msg2 = receiveFromPeer()
let _ = try handshake.readMessage(msg2) // <- e, ee, s, es
let msg3 = try handshake.writeMessage(payload: Data()) // -> s, se
sendToPeer(msg3)
// 3️⃣ Handshake complete – obtain transport ciphers
let (sendCipher, recvCipher, handshakeHash) = try handshake.getTransportCiphers(useExtractedNonce: true)
// 4️⃣ Encrypt a regular chat payload
let plaintext = "Hello, peer!".data(using: .utf8)!
let encrypted = try sendCipher.encrypt(plaintext: plaintext)
// 5️⃣ Decrypt on the other side
let decrypted = try recvCipher.decrypt(ciphertext: encrypted)
print(String(data: decrypted, encoding: .utf8)!) // → "Hello, peer!"
Summary
- BitChat implements Noise Protocol encryption using the XX handshake pattern with Curve25519, ChaCha20-Poly1305, and SHA-256 as defined in
bitchat/Noise/NoiseProtocol.swift. - The
NoiseHandshakeStateclass manages the three-message mutual authentication sequence, whileNoiseCipherStateprovides AEAD encryption and sliding-window replay protection. - Transport keys are derived through HKDF operations in
NoiseSymmetricState, ensuring forward secrecy and key separation. - The
NoiseEncryptionServiceexposes encrypted APIs to the transport layer, supporting BLE-mesh nonce extraction and specialized payload types for voice and file transfers.
Frequently Asked Questions
What cryptographic primitives does BitChat's Noise implementation use?
BitChat's Noise Protocol encryption uses Curve25519 (X25519) for elliptic-curve Diffie-Hellman key exchange, ChaCha20-Poly1305 for authenticated encryption, and SHA-256 for hashing. These primitives are combined in the Noise_XX_25519_ChaChaPoly_SHA256 protocol pattern implemented in NoiseProtocolName.
How does BitChat prevent replay attacks in encrypted messages?
The NoiseCipherState class implements a 1024-message sliding-window replay protection scheme through the isValidNonce and markNonceAsSeen methods. For BLE-mesh traffic using explicit nonces, the system extracts the nonce from the first four bytes of the payload to validate message freshness without requiring synchronized state between peers.
What is the purpose of the XX handshake pattern in BitChat?
The XX pattern provides mutual authentication without prior knowledge of the peer's static key. Through the three-message handshake (e → e, ee, s, es → s, se), both parties exchange ephemeral keys for forward secrecy and encrypted static keys for identity verification, as orchestrated by NoiseHandshakeState.
Where does BitChat store the encryption keys used in Noise sessions?
Static private keys are stored in the iOS Keychain via KeychainManager.shared, while ephemeral keys exist only in memory during the handshake process. The NoiseSession class in bitchat/Noise/NoiseSession.swift manages session lifecycles and key rotation, with clearSensitiveData methods ensuring sensitive material is wiped from memory when sessions close.
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 →