BitChat Encryption Methods for Live Sessions: Noise XX Protocol Implementation

BitChat secures live peer-to-peer sessions using the Noise XX handshake pattern with X25519 elliptic curve keys, ChaCha20-Poly1305 authenticated encryption, and SHA-256 hashing.

BitChat is an open-source, permissionless messaging framework that prioritizes cryptographic privacy for real-time communication. According to the permissionlesstech/bitchat source code, the application implements the Noise Protocol Framework's XX pattern to establish secure sessions between peers, combining modern elliptic curve cryptography with authenticated symmetric encryption.

The Noise XX Cryptographic Suite

The implementation in bitchat/Services/NoiseEncryptionService.swift defines a complete cryptographic stack for live session establishment and message protection.

X25519 Elliptic Curve Key Exchange

BitChat uses X25519 (Curve25519) for all key exchange operations within the Noise XX handshake. The static identity keys are defined and loaded in NoiseEncryptionService.swift at lines 54-61, where each peer maintains a long-term Curve25519 key pair stored in the system keychain. During the three-message handshake, both initiator and responder prove possession of their static keys while simultaneously generating ephemeral X25519 key pairs to ensure forward secrecy.

ChaCha20-Poly1305 Authenticated Encryption

All symmetric encryption operations utilize ChaCha20-Poly1305, the default authenticated encryption with associated data (AEAD) cipher for Noise XX. In NoiseEncryptionService.swift at lines 68-82, the encrypt and decrypt methods funnel through NoiseSessionManager, which applies ChaCha20-Poly1305 to all payload data. This construction provides both confidentiality and integrity verification for every message transmitted during live sessions.

SHA-256 Hashing and Peer Identification

BitChat employs SHA-256 for multiple cryptographic functions within the Noise implementation. The hash algorithm generates peer identity fingerprints at lines 24-28 of NoiseEncryptionService.swift, derives the 8-byte routing ID from static key material in localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift, and serves as the underlying hash function for HKDF key derivation during the handshake process.

How the Noise XX Handshake Works in BitChat

The Noise XX pattern implemented in BitChat provides mutual authentication through a three-message handshake where both parties present static X25519 public keys. As documented in the code comments at lines 35-36 of NoiseEncryptionService.swift, this pattern enables two peers with pre-existing knowledge of each other's public keys to establish an encrypted session without requiring certificates or a central authority.

The handshake achieves forward secrecy by combining static long-term keys with ephemeral X25519 keys generated fresh for each session. Even if a static private key is compromised later, past session contents remain protected because the symmetric ChaCha20-Poly1305 keys incorporate ephemeral contributions from both peers.

Implementing BitChat Encryption in Swift

The NoiseEncryptionService class provides the primary Swift interface for establishing encrypted sessions. The service requires a keychain instance for secure key storage and exposes methods for handshake initiation and message encryption.

import Bitchat

// 1️⃣ Create the service (keychain is injected by the app)
let noiseService = NoiseEncryptionService(keychain: myKeychain)

// 2️⃣ Initiate a handshake with a peer (PeerID is the 8‑byte routing ID)
do {
    let handshakeData = try noiseService.initiateHandshake(with: peerID)
    // Send `handshakeData` over BLE or other transport
} catch {
    // Handle errors such as rate‑limit or invalid peer ID
}

// 3️⃣ Encrypt a message once the session is established
do {
    let plaintext = Data("Hello, world!".utf8)
    let encrypted = try noiseService.encrypt(plaintext, for: peerID)
    // Transmit `encrypted` payload
} catch NoiseEncryptionError.handshakeRequired {
    // Trigger a new handshake before sending
}

// 4️⃣ Decrypt an incoming message
do {
    let (decrypted, _) = try noiseService.decryptWithSessionGeneration(
        incomingData,
        from: peerID
    )
    // Use `decrypted` as the original message
} catch {
    // Session not established or message too large
}

The API design separates concerns between session management and transport:

  • initiateHandshake(with:) – Begins the Noise XX three-message handshake sequence
  • encrypt(_:for:) – Encrypts data using the established session's ChaCha20-Poly1305 key
  • decryptWithSessionGeneration(_:from:) – Decrypts data and returns the session generation counter for replay protection

Session Re-keying and Lifecycle Management

BitChat implements automatic session re-keying to limit the volume of data encrypted under a single key. The timer-driven re-key logic at lines 80-87 of NoiseEncryptionService.swift periodically rotates the ChaCha20-Poly1305 encryption keys while maintaining the same X25519 static identity keys. This practice follows Noise protocol best practices for long-lived sessions, ensuring that key compromise results in minimal data exposure.

Security Properties and Guarantees

The docs/privacy-assessment.md file explicitly confirms the cryptographic suite for mesh sessions: "Noise XX with X25519, ChaCha20-Poly1305, and SHA-256." This combination delivers three critical security properties:

  • Mutual Authentication: Each peer cryptographically proves possession of its static private key during the handshake
  • Forward Secrecy: Ephemeral key contributions ensure session keys cannot be recovered from future static key compromises
  • Integrity and Confidentiality: ChaCha20-Poly1305 provides authenticated encryption resistant to tampering and eavesdropping

The implementation is validated by the test suite in bitchatTests/Noise/NoiseProtocolTests.swift, which exercises the complete Noise XX handshake flow and message encryption cycles.

Summary

  • BitChat implements the Noise XX handshake pattern for live peer-to-peer sessions, using X25519 for key exchange, ChaCha20-Poly1305 for symmetric encryption, and SHA-256 for hashing.
  • The NoiseEncryptionService.swift file contains the core implementation, managing static key storage, session lifecycle, and automatic re-keying at lines 54-87.
  • The protocol provides mutual authentication without certificates, forward secrecy through ephemeral keys, and authenticated encryption for all messages.
  • Developers interact with the system through NoiseEncryptionService, utilizing initiateHandshake(with:), encrypt(_:for:), and decryptWithSessionGeneration(_:from:) methods.
  • Session security is maintained through timer-driven re-keying that periodically rotates symmetric encryption material while preserving established session contexts.

Frequently Asked Questions

What is Noise XX and why does BitChat use it for live sessions?

Noise XX is a specific handshake pattern from the Noise Protocol Framework that provides mutual authentication using static X25519 keys combined with ephemeral key exchange. BitChat selects this pattern because it enables two peers to authenticate each other and establish encrypted sessions without requiring a centralized certificate authority or prior shared secrets. As documented in docs/WHITEPAPER.md, this approach aligns with BitChat's permissionless philosophy while providing modern cryptographic guarantees.

How does BitChat handle key rotation during active sessions?

BitChat implements automatic re-keying through timer-driven logic in NoiseEncryptionService.swift at lines 80-87. The system periodically generates new ChaCha20-Poly1305 encryption keys using the existing X25519 session state, limiting the amount of data encrypted under any single symmetric key. This re-keying occurs transparently to the application layer and maintains session continuity while reducing exposure from potential key compromise.

Where does BitChat store the static X25519 identity keys?

Static X25519 keys are persisted in the system keychain through dependency injection, as evidenced by the NoiseEncryptionService initializer requiring a keychain object. The key material is loaded at lines 54-61 of NoiseEncryptionService.swift, ensuring that long-term identity keys remain in secure hardware-backed storage when available on the device, rather than application memory or insecure disk locations.

How does BitChat verify peer identities without a central authority?

BitChat derives PeerID values from SHA-256 fingerprints of the static X25519 public keys, as implemented in localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift. The 8-byte routing ID serves as a compact, collision-resistant identifier derived from the cryptographic public key itself. During the Noise XX handshake, peers exchange and verify static public keys, confirming identity through cryptographic proof rather than centralized validation, with the SHA-256 fingerprint providing a human-verifiable reference.

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 →