What Encryption Methods Does BitChat Use? A Complete Technical Breakdown
BitChat uses the Noise Protocol Framework with Curve25519 key exchange, ChaCha20-Poly1305 AEAD encryption, and SHA-256 hashing, plus XChaCha20-Poly1305 for Nostr compatibility.
The permissionless BitChat messaging protocol implements a full Noise stack for on-device storage and peer-to-peer traffic protection. This analysis examines the specific algorithms, handshake patterns, and source files that comprise its cryptographic architecture.
Noise Protocol Framework Implementation
BitChat builds its security layer on the Noise Protocol Framework, a modern approach to cryptographic protocols that combines DH handshakes with symmetric ciphers. According to the BitChat source code, the implementation resides primarily in bitchat/Noise/NoiseProtocol.swift and closely follows the official Noise specification.
The four core primitives are initialized together at lines 26-29:
let noiseProtocol = NoiseProtocol(
handshakePattern: .XX,
dh: .curve25519,
cipher: .chacha20Poly1305,
hash: .sha256
)
Each primitive serves a distinct purpose in the security model.
Key Exchange: Curve25519 (X25519)
Diffie-Hellman key agreement uses the Curve25519 elliptic curve, specified in NoiseProtocol.swift lines 27-28. This provides:
- 128-bit security level with 32-byte public keys
- Constant-time implementation to resist timing attacks
- Small, fast operations suitable for mobile and embedded contexts
The static keys are managed through the KeychainManager protocol, with ephemeral keys generated per session to ensure forward secrecy.
Symmetric Encryption: ChaCha20-Poly1305
The ChaCha20-Poly1305 AEAD cipher handles all symmetric encryption after handshake completion. Defined at line 28-29 of NoiseProtocol.swift, this construction provides authenticated encryption with associated data—ensuring confidentiality and integrity without separate MAC operations.
Post-handshake encryption follows this pattern:
let (sendCipher, receiveCipher, _) = try handshake.getTransportCiphers(useExtractedNonce: false)
let encrypted = try sendCipher.encrypt(plaintext: messageData)
The NoiseCipherState class manages cipher instances, nonces, and replay protection through a 1024-message sliding window implemented at lines 33-36.
Handshake Pattern: XX (Mutual Authentication)
BitChat selects the Noise XX pattern for its handshake, as declared at lines 26-28. The XX pattern provides:
- Mutual authentication — both parties prove possession of static keys
- Identity hiding — initiator and responder identities remain encrypted until exchanged
- Three-message handshake — efficient for peer-to-peer discovery scenarios
The NoiseHandshakeState.swift file orchestrates this flow, handling key mixing and exposing transport cipher objects upon completion.
let handshake = try NoiseHandshakeState(
role: .initiator,
pattern: .XX,
keychain: myKeychain,
localStaticKey: staticKey,
remoteStaticKey: nil,
prologue: Data()
)
let msg1 = try handshake.writeMessage()
Hash Function: SHA-256
SHA-256 serves as the single cryptographic hash, specified at line 29. It functions in two capacities:
- Handshake transcript hashing — binds protocol messages to prevent tampering
- Key derivation — extracts and expands keys from DH shared secrets per the Noise specification
Replay Protection Mechanism
BitChat implements sliding-window nonce tracking to prevent replay attacks. The NoiseCipherState maintains a 1024-message window at lines 33-36, rejecting any nonce that falls behind the window or duplicates an existing entry.
This protection applies to all transport messages after the handshake completes, ensuring that captured ciphertext cannot be re-injected into later sessions.
Nostr Compatibility: XChaCha20-Poly1305
For interoperability with Nostr protocol requirements, BitChat includes an additional AEAD variant in XChaCha20Poly1305Compat.swift. The XChaCha20-Poly1305 construction extends the nonce size from 96 bits to 192 bits, enabling safer random nonce generation for long-lived keys.
let secretKey = SymmetricKey(size: .bits256)
let nonce = try XChaCha20Poly1305Compat.Nonce()
let sealed = try XChaCha20Poly1305Compat.seal(message, using: secretKey, nonce: nonce)
This compatibility layer operates separately from the main Noise stack, accessed through the bitchat.Nostr module.
Security Properties Delivered
| Property | Implementation Mechanism |
|---|---|
| Mutual authentication | XX pattern with static key signatures |
| Forward secrecy | Per-session ephemeral DH exchanges |
| Integrity & confidentiality | ChaCha20-Poly1305 AEAD |
| Replay resistance | 1024-message sliding-window nonce filter |
| Side-channel resistance | Constant-time public key validation |
Key Source Files
bitchat/Noise/NoiseProtocol.swift— Core Noise primitives and protocol configurationbitchat/Noise/NoiseHandshakeState.swift— Handshake orchestration and key mixingbitchat/Noise/NoiseCipherState.swift— Symmetric encryption and replay protectionbitchat/Nostr/XChaCha20Poly1305Compat.swift— Extended-nonce AEAD for Nostr compatibility
Summary
- BitChat encryption methods rely entirely on the Noise Protocol Framework with XX handshake pattern
- Curve25519 provides elliptic-curve Diffie-Hellman key exchange with 128-bit security
- ChaCha20-Poly1305 delivers authenticated encryption for all transport traffic
- SHA-256 handles handshake hashing and key derivation
- A 1024-message sliding window prevents replay attacks on transport messages
- XChaCha20-Poly1305 in
XChaCha20Poly1305Compat.swiftenables Nostr protocol interoperability
Frequently Asked Questions
Does BitChat use end-to-end encryption?
Yes. BitChat implements true end-to-end encryption through the Noise Protocol Framework. The XX handshake pattern ensures both peers authenticate each other with static keys, and all subsequent traffic encrypts with ChaCha20-Poly1305 keys derived from ephemeral Curve25519 exchanges.
What is the difference between ChaCha20-Poly1305 and XChaCha20-Poly1305 in BitChat?
ChaCha20-Poly1305 uses a 96-bit nonce and serves as the default AEAD within the Noise protocol. XChaCha20-Poly1305 in XChaCha20Poly1305Compat.swift extends the nonce to 192 bits, reducing collision risk when using random nonces—particularly important for Nostr compatibility where long-term keys encrypt multiple messages.
How does BitChat prevent replay attacks?
The NoiseCipherState class implements a 1024-message sliding-window nonce tracker at lines 33-36 of NoiseProtocol.swift. This rejects any message with a nonce already seen or fallen outside the window, ensuring captured ciphertext cannot be replayed in future sessions.
Is BitChat's cryptography suitable for Bluetooth Low Energy (BLE) messaging?
Yes. The selected algorithms—Curve25519 and ChaCha20-Poly1305—were specifically chosen for performance on constrained devices. The compact 32-byte public keys, fast constant-time operations, and minimal handshake overhead make the implementation well-suited for BLE and other low-bandwidth transports.
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 →