# How BitChat Implements Noise Protocol Encryption for Peer-to-Peer Messaging

> Discover how BitChat secures peer-to-peer messaging using Noise Protocol encryption. Learn about its XX handshake, Curve25519, ChaCha20-Poly1305, and SHA-256 implementation for ultimate privacy.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-07-27

---

**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`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Noise/NoiseProtocol.swift), where several specialized state objects manage the cryptographic lifecycle.

### NoiseHandshakeState

Located at line 35 in [`NoiseProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/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:

1. **Message 1 (Initiator → Responder):** The initiator sends an ephemeral public key `e`.
2. **Message 2 (Responder → Initiator):** The responder sends its ephemeral key, performs the `ee` Diffie-Hellman operation, then transmits its static key encrypted under the derived key, followed by an `es` DH operation.
3. **Message 3 (Initiator → Responder):** The initiator sends its static key encrypted, then performs the `se` DH 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:** `NoiseCipherState` tracks 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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/MessageType.swift) and encrypted using the same cipher states managed by [`NoiseSession.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseSession.swift).

## Implementation Example

The following Swift code demonstrates establishing a Noise XX handshake and encrypting messages:

```swift
// 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`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Noise/NoiseProtocol.swift).
- The `NoiseHandshakeState` class manages the three-message mutual authentication sequence, while `NoiseCipherState` provides AEAD encryption and sliding-window replay protection.
- Transport keys are derived through HKDF operations in `NoiseSymmetricState`, ensuring forward secrecy and key separation.
- The `NoiseEncryptionService` exposes 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`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Noise/NoiseSession.swift) manages session lifecycles and key rotation, with `clearSensitiveData` methods ensuring sensitive material is wiped from memory when sessions close.