# Noise Protocol Parameters for BitChat's Bluetooth Mesh Encryption: Complete Implementation Guide

> Discover BitChat's Noise Protocol parameters for Bluetooth mesh encryption. Learn about the fixed Noise_XX_25519_ChaChaPoly_SHA256 configuration and static X25519 key usage in this implementation guide.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: how-to-guide
- Published: 2026-08-08

---

**BitChat implements a fixed Noise_XX_25519_ChaChaPoly_SHA256 configuration for Bluetooth mesh encryption with no runtime algorithm negotiation, using static X25519 keys to derive 8-byte peer IDs from SHA-256 fingerprints.**

The permissionlesstech/bitchat repository uses deterministic Noise Protocol parameters to secure its decentralized Bluetooth mesh network. Every encrypted packet relies on hardcoded cryptographic primitives that eliminate negotiation attacks while ensuring consistent security across all mesh nodes.

## Core Noise Protocol Configuration

BitChat hardcodes the following Noise Protocol parameters according to the Noise specification notation. There is **no runtime negotiation** of algorithms.

| Parameter | Value (Noise notation) | Description |
|---|---|---|
| **Handshake pattern** | `XX` | Mutual-authentication handshake where both peers exchange static keys after an initial DH exchange |
| **DH function** | `25519` | X25519 (Curve25519) Elliptic Curve Diffie-Hellman |
| **Cipher** | `ChaChaPoly` | ChaCha20-Poly1305 AEAD providing confidentiality and integrity |
| **Hash** | `SHA-256` | Transcript hashing and peer ID derivation |
| **Static key length** | 32 bytes | X25519 private keys stored per device |

## Payload Type Identifiers

BitChat defines specific payload type IDs placed after the single-byte inner-type tag inside encrypted Noise payloads. These values are documented in [`docs/PRIVATE-MEDIA-MIGRATION.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/PRIVATE-MEDIA-MIGRATION.md) and [`docs/PUSH-TO-TALK-DESIGN.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/PUSH-TO-TALK-DESIGN.md).

- **`0x20`** — Private-file data chunks
- **`0x21`** — Authenticated-peer-state (handshake-completion marker)
- **`0x08`** — Live-voice frames for push-to-talk functionality

## Peer ID Derivation from Static Keys

Each device generates a static Noise key once and stores it in the device keychain. The **peer ID** derivation follows a deterministic process implemented in [`PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PeerID.swift):

1. Generate or load the 32-byte X25519 static private key
2. Extract the public key for mesh announcement packets
3. Compute `SHA-256(noiseStaticKey)` and extract bytes 0-7 (8 bytes total)
4. Convert to 16 hexadecimal characters for the packet header

This design ensures the same 32-byte X25519 public key appears in every mesh announce packet, with the short peer ID serving as the immutable header identifier.

## Implementation Architecture

The encryption layer spans several Swift files in `localPackages/BitFoundation/Sources/BitFoundation/`:

- **[`NoiseSessionManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseSessionManager.swift)** — Implements the `handshakeXX` routine for initiator and responder roles
- **[`NoiseEncryptionService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseEncryptionService.swift)** — Handles X25519 DH operations, ChaCha20-Poly1305 AEAD construction, and ciphertext generation
- **[`MessagePadding.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessagePadding.swift)** — Ensures padded Noise frames fit within the 256-byte BLE packet limit
- **[`PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PeerID.swift)** — Derives short peer IDs from static key fingerprints

## Swift Implementation Examples

### Generating the Static Noise Key

Devices load a persisted static key generated once during initialization:

```swift
import BitFoundation

// Load the persisted static key from the device keychain
let staticKey = try NoiseKeyStore.shared.loadStaticKey()
// Derive the 32-byte X25519 public key for mesh announcements
let staticPublic = staticKey.publicKey

```

### Deriving the 8-Byte Peer ID

The short peer ID calculation uses SHA-256 truncation as defined in [`docs/PEER-ID-ROTATION.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/PEER-ID-ROTATION.md):

```swift
// Generate 16-hex character peer ID from first 8 bytes of hash
let peerID = PeerID.shortFromNoiseKey(staticPublic)
// peerID appears in packet headers as the sender identifier

```

### Performing the Noise XX Handshake

The initiator creates a session using the remote peer's static public key obtained from announce packets:

```swift
let session = try NoiseSessionManager.shared.initiateXXHandshake(
    localStaticKey: staticKey,
    remoteStaticPublic: remotePublicKey
)

// Post-handshake: session.sharedSecret contains the 32-byte ChaCha20-Poly1305 key

```

### Encrypting Mesh Payloads

Payloads include a 1-byte type tag followed by data, encrypted with AEAD:

```swift
// Prepare private-file payload with type identifier 0x20
let payload = Data([0x20]) + fileChunk

let encrypted = try NoiseEncryptionService.encrypt(
    payload: payload,
    using: session.sharedSecret,
    aad: nil  // Additional authenticated data unused for BLE
)

// Output format: ChaCha20-Poly1305 ciphertext || 16-byte Poly1305 tag

```

### Building BLE Packets

Final packet assembly combines the peer ID with encrypted payload. The total size must fit within 256 bytes:

```swift
var packet = Data()
packet.append(peerID)     // 8-byte sender ID from SHA-256 fingerprint
packet.append(encrypted)  // Encrypted Noise payload
// MessagePadding.swift handles automatic padding to ≤ 256 bytes

```

### Decrypting Received Messages

Receivers decrypt using the established session key:

```swift
let decrypted = try NoiseEncryptionService.decrypt(
    ciphertext: encrypted,
    using: session.sharedSecret,
    aad: nil
)

// decrypted[0] contains the inner-type byte (e.g., 0x20, 0x21, or 0x08)
// remaining bytes contain the original payload data

```

## Security Characteristics

Key design decisions from the BitChat source code include:

- **No algorithm negotiation** — All parameters are compile-time constants, eliminating downgrade attacks
- **Static key immutability** — The 32-byte X25519 key never rotates, ensuring persistent peer identities across sessions
- **Deterministic peer IDs** — The `SHA-256(noiseStaticKey)[0..8]` derivation creates collision-resistant short identifiers that never change
- **Frame size constraints** — [`MessagePadding.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessagePadding.swift) enforces the 256-byte BLE limit including the 16-byte Poly1305 authentication tag

## Summary

- BitChat uses **Noise_XX_25519_ChaChaPoly_SHA256** as the fixed cipher suite for all Bluetooth mesh encryption
- The **XX handshake pattern** provides mutual authentication through static X25519 key exchange after ephemeral DH
- **Peer IDs** are deterministic 8-byte values derived from the first 8 bytes of `SHA-256(noiseStaticKey)`, implemented in [`PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PeerID.swift)
- Payload type identifiers **`0x20`**, **`0x21`**, and **`0x08`** distinguish file data, authentication state, and voice frames inside encrypted payloads
- Implementation resides in [`NoiseSessionManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseSessionManager.swift), [`NoiseEncryptionService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseEncryptionService.swift), and [`PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PeerID.swift) within the BitFoundation package

## Frequently Asked Questions

### What Noise handshake pattern does BitChat use?

BitChat implements the **XX pattern**, which performs mutual authentication by having both peers exchange static keys after an initial ephemeral Diffie-Hellman exchange. This pattern is implemented in [`NoiseSessionManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseSessionManager.swift) through the `handshakeXX` routine, providing forward secrecy and identity hiding during the handshake phase.

### How is the peer ID generated from Noise keys?

The peer ID derives from the first 8 bytes of `SHA-256(noiseStaticKey)`, implemented in [`PeerID.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PeerID.swift) as a 16-hexadecimal character string. This fingerprint-based approach, documented in [`docs/PEER-ID-ROTATION.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/PEER-ID-ROTATION.md), ensures the same device always presents the same identifier in mesh announce packets without requiring persistent network state.

### Can BitChat negotiate different encryption algorithms at runtime?

No. All Noise Protocol parameters are hardcoded with **no runtime negotiation** of algorithms, handshake patterns, or cipher suites. The fixed configuration uses X25519 for DH, ChaCha20-Poly1305 for AEAD, and SHA-256 for hashing across all connections, preventing downgrade attacks and ensuring interoperability.

### Where are the static Noise keys stored in BitChat?

Static X25519 keys are generated once per device and persisted in the **device keychain** via `NoiseKeyStore`. These 32-byte private keys never rotate, ensuring persistent identity across app restarts and mesh reconnections, as the same public key must remain recognizable to existing peers.