Security Implications of Broadcasting Public Keys and Nicknames in Cleartext on the BitChat Mesh

Broadcasting public keys and nicknames in cleartext on the BitChat mesh creates permanent device fingerprints, exposes social graphs through neighbour lists, and enables passive tracking by anyone within BLE radio range.

BitChat is an open-source, offline-first mesh messaging protocol that relies on Bluetooth Low Energy (BLE) broadcasts for peer discovery. In the current v1 implementation, devices transmit their Noise static public keys, Ed25519 signing keys, and user-selected nicknames in unencrypted announce packets every 4–30 seconds. This design fundamentally undermines user privacy by allowing passive adversaries to harvest persistent identifiers and reconstruct social relationships without ever joining the mesh.

How BitChat V1 Broadcasts Data in Cleartext

BitChat’s legacy announce mechanism transmits discovery data using Type-Length-Value (TLV) encoding defined in localPackages/BitFoundation/Sources/BitFoundation/MessageType.swift. The v1 packet structure exposes the following fields to any device within radio range:

TLV Field Size Risk
0x01 Nickname Variable Direct identity disclosure
0x02 Noise static public key 32 bytes Permanent device fingerprint
0x03 Ed25519 signing public key 32 bytes Authentication material leakage
0x04 Direct neighbour list Up to 80 bytes Social graph exposure
0x05 Capabilities 1–8 bytes Device capability profiling

Because these packets are broadcast in cleartext, no authentication or encryption is required to capture them. As noted in docs/privacy-assessment.md, anyone with a standard BLE dongle can harvest this data silently, making large-scale surveillance trivially inexpensive.

Critical Security Risks of Cleartext Broadcasting

Permanent Device Fingerprinting via Static Noise Keys

The 32-byte Noise static public key (0x02) serves as a long-term cryptographic identity. BitChat derives the peer ID from this key using SHA-256(noiseStaticPublicKey)[0..8], creating an immutable 64-bit identifier. Because the static key never rotates in v1, an adversary can track the device across all future encounters by simply hashing the observed public key. This defeats the purpose of peer ID rotation, which is designed to break linkability between sessions.

Nickname Exposure and Identity Correlation

The 0x01 TLV transmits the user’s chosen nickname (e.g., "Alice") in plain UTF-8. Since nicknames are often reused across sessions, an observer can correlate the static public key with human-readable identifiers, building a persistent profile of the individual. In activist or protest settings where anonymity is critical, this correlation removes the deniability that rotating keys would otherwise provide.

Social Graph Leakage Through Neighbour Lists

The neighbour list field (0x04) contains up to ten peer IDs representing the device's immediate mesh neighbours. By collecting multiple announces over time, an adversary can reconstruct physical adjacency relationships and map local social graphs without any active participation in the protocol. This metadata reveals who was physically close to whom and when, creating a movement and association fingerprint.

Unauthenticated Key Binding and Replay Attacks

According to the source in localPackages/BitFoundation/Sources/BitFoundation/AnnounceV2Packet.swift, legacy v1 announce packets are unsigned ("Unsigned, on purpose and not without cost"). Because the public keys are transmitted in cleartext without authentication, an attacker can replay a victim’s static Noise and Ed25519 keys while substituting their own signing material. While BitChat uses TOFU (trust-on-first-use) pinning to mitigate this, a sophisticated adversary who intercepts the initial handshake can impersonate victims by replaying their static identity data.

The Path Forward: Announce V2 Privacy Architecture

To eliminate these vulnerabilities, the BitChat team developed AnnounceV2Packet, defined in localPackages/BitFoundation/Sources/BitFoundation/AnnounceV2Packet.swift. This format deliberately omits the cleartext fields that cause linkability:

  • No static public keys: The Noise and Ed25519 keys are removed from the broadcast entirely.
  • No nickname: Human-readable identifiers are transmitted only inside the encrypted Noise XX handshake.
  • Epoch-based rotation: V2 transmits only an epoch number, a 16-byte tag block, optional capabilities, and an optional bridge geohash.

Section 4.5 of docs/PEER-ID-ROTATION.md proposes binding the rotating peer ID to the static keys using a signed proof inside the encrypted handshake, preventing unauthenticated replay attacks. Additionally, the protocol will use pairwise recognition tags—cryptographically derived values that only mutual favorites can compute—eliminating social-graph leakage while maintaining fast reconnection capabilities.

Practical Code Examples

The following Swift snippets demonstrate the difference between the legacy v1 implementation (which exposes cleartext identity material) and the privacy-preserving v2 format.

Decoding a Legacy V1 Announce (Cleartext Exposure)

import BitFoundation

// Simulate raw BLE packet captured from air
let rawData: Data = /* BLE advertisement payload */

if let announce = AnnouncementPacket.decode(from: rawData) {
    // All fields are extracted in cleartext
    print("Nickname:        \(announce.nicknameString)")
    print("Noise Public Key: \(announce.noisePublicKey.hex)")
    print("Signing Key:     \(announce.signingPublicKey.hex)")
    
    // Calculate permanent peer ID
    let peerId = SHA256.hash(data: announce.noisePublicKey).prefix(8)
    print("Derived Peer ID: \(peerId.hex)")
}

Encoding a Privacy-Preserving V2 Announce

import BitFoundation

// V2 contains no public keys or nicknames
let currentEpoch: UInt32 = 42
let tagBlock = Data(repeating: 0, count: AnnounceV2Packet.tagBlockLength)

let v2Packet = AnnounceV2Packet(
    epoch: currentEpoch,
    tagBlock: tagBlock,
    capabilities: nil,
    bridgeGeohash: nil
)

if let encoded = v2Packet.encode() {
    // Broadcast over BLE - contains only epoch and opaque tags
    blePeripheralManager.updateValue(encoded, ...)
}

Summary

  • Legacy v1 announces transmit Noise static public keys, Ed25519 signing keys, nicknames, and neighbour lists in cleartext every 4–30 seconds, as defined in MessageType.swift.
  • Permanent tracking is possible because the peer ID is derived directly from the unchanging static public key (SHA-256(noiseStaticPublicKey)[0..8]).
  • Social graph analysis is trivial due to the neighbour list TLV (0x04) which reveals immediate adjacency relationships.
  • Replay attacks are possible because v1 packets are unsigned, allowing adversaries to rebroadcast captured static keys.
  • Announce V2 mitigates these issues by removing all cleartext identity material, using only epoch numbers and pairwise recognition tags, as implemented in AnnounceV2Packet.swift.

Frequently Asked Questions

What specific data is exposed when BitChat broadcasts public keys and nicknames in cleartext?

BitChat’s v1 announce packets expose the user’s nickname (TLV 0x01), a 32-byte Noise static public key (TLV 0x02), a 32-byte Ed25519 signing key (TLV 0x03), and a list of up to ten neighbour peer IDs (TLV 0x04). All of this data is transmitted in cleartext over BLE, allowing any nearby device to harvest identity material without authentication.

How does broadcasting static public keys defeat BitChat’s peer ID rotation mechanism?

BitChat derives the peer ID from the static Noise public key using SHA-256(noiseStaticPublicKey)[0..8]. Since the static key is broadcast in cleartext and never changes, an adversary can calculate the peer ID at any time, linking the device across all rotation epochs. According to docs/PEER-ID-ROTATION.md section 4.2, rotation only provides privacy if the rotating ID is derived from private key material; broadcasting the public key allows observers to compute all past and future IDs.

Can attackers replay captured BitChat announce packets to impersonate users?

Yes. The legacy v1 announce packets are unsigned (as noted in AnnounceV2Packet.swift), meaning there is no cryptographic proof of origin. An attacker can capture a victim’s static Noise and Ed25519 keys and rebroadcast them with their own data. While BitChat uses TOFU (trust-on-first-use) pinning to detect changes, a sophisticated adversary who intercepts the initial handshake can exploit this to impersonate the victim before the legitimate device connects.

What mitigations does BitChat implement to stop cleartext broadcasting of public keys?

BitChat is implementing Announce V2, which removes all cleartext identity fields from the broadcast. Instead of public keys and nicknames, V2 transmits only an epoch counter, a 16-byte opaque tag block, and optional capabilities. The static keys and nicknames are moved inside the encrypted Noise XX handshake, and docs/PEER-ID-ROTATION.md proposes signed bindings to prevent replay attacks while maintaining the ability for mutual favorites to recognize each other using pairwise-derived tags.

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 →