BitChat Security Model: How Persistent Identifiers and BLE Header Metadata Protect Privacy

BitChat isolates device identity from transport layer by deriving a persistent identifier from the Noise public key and exposing only a minimal nickname plus two public keys in BLE advertisements, with full payload encryption protecting all subsequent communication.

The BitChat security model treats Bluetooth Low Energy (BLE) as an inherently observable transport while ensuring that no sensitive data leaks beyond what is cryptographically necessary for mesh discovery. This article examines how the open-source messenger balances discoverability with privacy through careful handling of persistent identifiers and BLE header metadata.


Persistent Identifiers Derived from Cryptographic Keys

Each BitChat installation generates a long-term device identifier that survives app restarts but remains cryptographically bound to the user's identity. This identifier is not a random UUID—it is derived directly from the Noise public key, which serves as the primary encryption and authentication key for all peer-to-peer communication.

According to SECURITY.md, this identifier is stored securely in the device keychain and is only erased when a panic wipe is triggered. This design guarantees that:

  • The same device is recognizable across sessions without requiring centralized account registration
  • No stale identity persists after a security reset
  • The identifier carries cryptographic meaning rather than being arbitrary metadata

The derivation happens once during initial setup and is reused for the lifetime of the installation:

import BitFoundation

let keychain = Keychain(service: "com.permissionless.bitchat")
let noiseKeyPair = try NoiseKeyPair.generate()          // Ed25519 + Noise keys
try keychain.set(data: noiseKeyPair.publicKey.rawData,
                forKey: "bitchat-noise-pubkey")      // persistent identifier

// Retrieve on app start
let persistedNoisePubKey = try keychain.data(forKey: "bitchat-noise-pubkey")
let deviceIdentifier = try PublicKey(data: persistedNoisePubKey)

This approach, implemented in the BitChat source code, ensures that identity continuity does not depend on any server infrastructure.


BLE Header Metadata: What Gets Broadcast

The BLE advertisement payload in BitChat intentionally leaks only three pieces of information. As documented in docs/privacy-assessment.md (lines 27–28), these are:

  • A human-readable nickname
  • The persistent Noise public key
  • The Ed25519 signing public key

This metadata is considered public by design. The BitChat security model assumes that any adversary within BLE range can observe presence, nicknames, and public keys—but cannot decrypt or forge messages without the corresponding private keys.

The construction of this advertisement follows strict size constraints to fit within standard BLE advertising frames:

import CoreBluetooth

func makeBLEAdvertisementPayload(nickname: String,
                                 noisePubKey: PublicKey,
                                 signingPubKey: PublicKey) -> Data {
    var payload = Data()
    // 1️⃣ Nickname (UTF‑8, length‑prefixed)
    let nameData = nickname.data(using: .utf8)!
    payload.append(UInt8(nameData.count))
    payload.append(nameData)

    // 2️⃣ Noise public key (32 bytes)
    payload.append(noisePubKey.rawData)

    // 3️⃣ Ed25519 signing public key (32 bytes)
    payload.append(signingPubKey.rawData)

    return payload          // ≤ 65 bytes, fits within BLE advertising frame
}

// Example usage inside BLEService:
let advData = makeBLEAdvertisementPayload(nickname: "Alice",
                                          noisePubKey: deviceIdentifier,
                                          signingPubKey: mySigningKey.publicKey)
peripheralManager.startAdvertising([CBAdvertisementDataServiceDataKey: advData])

The resulting payload is capped at 65 bytes, ensuring compatibility with BLE advertising data limits while leaving no room for accidental information leakage.


Transport Layer Boundary and Encrypted Payloads

The BLE transport layer, detailed in docs/BLE-ARCHITECTURE-V3.md, enforces a strict boundary: the header is metadata-only, and all higher-level protocols must rely on the encrypted channel for confidentiality and integrity.

This separation means:

Layer Responsibility Security Property
BLE Header Device discovery, identity binding Observable but non-sensitive
Noise Handshake Session key establishment Ephemeral, forward-secret
Encrypted Payload Message content, file transfers Confidential and authenticated

Any protocol built atop BitChat—whether courier messaging, private media sharing, or future extensions—must assume the BLE layer is untrusted and encrypt accordingly.


Panic Wipe and Identifier Lifecycle

The persistent identifier is not permanent in the sense of being unrecoverable. The SECURITY.md documentation specifies that a panic wipe operation erases:

  • The stored Noise public key from the keychain
  • All session state
  • Persistent message stores

This creates a clean break: after a panic wipe, the device presents as a completely new identity to the mesh. This property is essential for threat models involving device seizure or coercion.


Summary

  • Persistent identifiers in BitChat are derived from Noise public keys and stored in the device keychain, enabling recognition across sessions without centralized infrastructure
  • BLE header metadata is intentionally minimal—only nickname, Noise public key, and Ed25519 signing key are broadcast, as specified in docs/privacy-assessment.md
  • Transport layer isolation ensures that all sensitive data travels through encrypted channels established after the header exchange, per docs/BLE-ARCHITECTURE-V3.md
  • Panic wipe functionality guarantees complete identity reset when needed, leaving no cryptographic breadcrumbs

Frequently Asked Questions

What information does BitChat leak over BLE?

BitChat's BLE advertisements contain only a nickname, the Noise public key, and the Ed25519 signing public key—a total of approximately 65 bytes. No message content, contact lists, or location data is transmitted. This minimal metadata exposure is a deliberate design choice documented in the privacy assessment.

Can an attacker track my device using the persistent identifier?

The Noise-derived identifier is long-term but not permanent. An adversary within BLE range can correlate advertisements to track presence over time, but the SECURITY.md panic wipe mechanism allows complete identity rotation. Additionally, the identifier alone cannot decrypt any communication without the corresponding private key.

How does BitChat prevent metadata leakage in group chats?

All payloads beyond the BLE header—including group membership and message content—are encrypted using the Noise protocol. The transport layer boundary defined in BLE-ARCHITECTURE-V3.md ensures that group metadata never appears in unencrypted BLE frames, regardless of group size.

What happens to my identifier if I uninstall and reinstall BitChat?

Uninstalling the app typically clears the iOS keychain entry for that specific app, which erases the stored Noise public key. Upon reinstallation, NoiseKeyPair.generate() creates a fresh cryptographic identity with no linkage to the previous installation.

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 →