How BLEAnnounceHandlingPolicy Prevents Identity Spoofing in BitChat

BitChat's BLEAnnounceHandlingPolicy prevents identity spoofing through a three-stage validation pipeline that cryptographically binds a peer's Noise identity to their Ed25519 signing key, rejecting any packet with mismatched, missing, or invalid signatures.

BitChat is an open-source, peer-to-peer messenger that uses Bluetooth Low Energy (BLE) for device discovery. Because BLE operates in a trustless environment where any nearby device can broadcast announces, the protocol must cryptographically verify identity claims before trusting them. The BLEAnnounceHandlingPolicy in the permissionlesstech/bitchat repository implements this defense through layered checks in BLEAnnouncePreflightPolicy, BLEAnnounceTrustPolicy, and BLEAnnounceResponsePolicy.

The Three-Stage Defense Against Identity Spoofing

The announce handling pipeline processes every incoming BLE packet through sequential validation stages. Each stage eliminates specific attack vectors, ensuring only cryptographically verified identities reach the application layer.

Stage 1: Pre-Flight Validation (BLEAnnouncePreflightPolicy)

Before any cryptographic verification, BLEAnnouncePreflightPolicy.evaluate filters malformed, replayed, or self-targeted packets. This function, implemented in bitchat/Services/BLE/BLEAnnounceHandlingPolicy.swift, performs four critical checks:

  • Decoding validation – The packet must deserialize as a valid AnnouncementPacket (lines 28–30)
  • PeerID binding – The derived PeerID from the announced Noise public key must match the BLE link-level peerID (lines 32–35)
  • Self-announce rejection – The sender cannot be the local device (lines 37–39)
  • Freshness verification – BLEPacketFreshnessPolicy rejects stale or replayed packets (lines 41–46)

Any mismatch aborts processing immediately, preventing unnecessary cryptographic operations on garbage data.

let preflight = BLEAnnouncePreflightPolicy.evaluate(
    packet: incomingPacket,
    from: remotePeerID,
    localPeerID: localPeerID,
    now: Date()
)

guard case .accept(let acceptance) = preflight else {
    // Rejected: malformed, stale, self-announce, or sender mismatch
    return
}

Stage 2: Trust Verification (BLEAnnounceTrustPolicy)

The trust stage enforces the cryptographic binding between a peer's Noise identity and their Ed25519 signing key. BLEAnnounceTrustPolicy.evaluate implements four layered checks that directly prevent identity spoofing:

Key mismatch rejection – If the local database already knows a Noise public key for this peer, a different announced key triggers rejection (lines 82–84).

Authenticated signing-key pinning – When an authenticated Noise session exists, the associated Ed25519 signing key is pinned; any different announced signing key is rejected (lines 89–92).

TOFU signing-key enforcement – For previously observed peers, new signing keys are rejected, preventing silent key rotation attacks (lines 100–103).

Signature validation – Every packet must contain a valid Ed25519 signature; missing or invalid signatures cause immediate rejection (lines 105–111).

let trust = BLEAnnounceTrustPolicy.evaluate(
    hasSignature: incomingPacket.hasSignature,
    signatureValid: incomingPacket.signatureIsValid,
    existingNoisePublicKey: knownNoiseKey,
    announcedNoisePublicKey: acceptance.announcement.noisePublicKey,
    existingSigningPublicKey: knownSigningKey,
    authenticatedSigningPublicKey: session?.authenticatedSigningKey,
    announcedSigningPublicKey: acceptance.announcement.signingPublicKey
)

guard trust.isVerified else {
    // Rejected: missing/invalid signature, key mismatch, or signing-key mismatch
    return
}

This layered design prevents the core spoofing attack: an attacker cannot replay a victim's Noise public key while substituting their own signing key. Any deviation from previously bound keys is explicitly rejected.

Stage 3: Response Planning (BLEAnnounceResponsePolicy)

Only packets passing both previous stages reach BLEAnnounceResponsePolicy.plan. This stage determines application-level responses—UI notifications, sync scheduling, or reciprocal announces—operating on a now-trusted identity (lines 31–38).

let response = BLEAnnounceResponsePolicy.plan(
    isDirectAnnounce: incomingPacket.isDirect,
    isNewPeer: !knownPeers.contains(acceptance.derivedPeerID),
    isReconnectedPeer: knownPeers.contains(acceptance.derivedPeerID),
    shouldSendAnnounceBack: true
)

if response.shouldNotifyPeerConnected { /* Update UI */ }
if response.shouldScheduleInitialSync { /* Start sync */ }
if response.shouldSendAnnounceBack   { /* Send reciprocal announce */ }

How the Architecture Thwarts Specific Attacks

Attack Vector Defense Mechanism Implementation Location
Replay of old announces BLEPacketFreshnessPolicy timestamp validation BLEAnnouncePreflightPolicy lines 41–46
Noise public key spoofing PeerID derived from Noise key must match link-level ID BLEAnnouncePreflightPolicy lines 32–35
Signing key substitution TOFU enforcement and authenticated session pinning BLEAnnounceTrustPolicy lines 89–103
Unsigned announces Mandatory signature presence and validation BLEAnnounceTrustPolicy lines 105–111
Announce flooding BLEAnnounceThrottle rate limiting BLEAnnounceThrottle.swift

Integration with the BLE Stack

The policy pipeline is orchestrated by BLEAnnounceHandler.swift, which instantiates the three policies and executes them for each received packet. BLEService.swift sets up this handler and integrates throttling via BLEAnnounceThrottle.swift, adding rate-limiting to prevent flood-based attacks that might be used to probe for key acceptance windows.

According to the bitchat source code, this design follows defense-in-depth principles: cryptographic verification occurs only after structural validation, and all state mutations require explicit trust verification.

Summary

  • BLEAnnounceHandlingPolicy implements a three-stage pipeline: pre-flight validation, trust verification, and response planning.
  • Pre-flight checks eliminate malformed, stale, and misrouted packets before cryptographic operations.
  • Trust verification cryptographically binds Noise identities to Ed25519 signing keys, rejecting any substitution attempt.
  • TOFU and authenticated pinning prevent silent key rotation and man-in-the-middle attacks during session establishment.
  • Signature enforcement ensures every identity claim is backed by valid Ed25519 cryptography.

Frequently Asked Questions

What happens if an attacker replays a valid announce packet from a legitimate peer?

The BLEPacketFreshnessPolicy validates timestamps within BLEAnnouncePreflightPolicy.evaluate (lines 41–46). Stale packets are rejected, preventing replay attacks even if the signature would otherwise validate.

Can a peer rotate their signing key if their device is compromised?

No automatic rotation is permitted. The TOFU (trust-on-first-use) model in BLEAnnounceTrustPolicy rejects new signing keys for previously known peers (lines 100–103). Key changes require out-of-band re-verification.

How does BitChat handle the first contact with a new peer?

On first contact, the announced Noise public key and Ed25519 signing key are recorded as the trusted binding. Subsequent contact must present identical keys, enforced by the key mismatch checks in BLEAnnounceTrustPolicy.evaluate (lines 82–84).

Where does the actual signature verification occur?

BLEAnnounceTrustPolicy.evaluate accepts pre-computed signatureValid and hasSignature parameters (lines 105–111). The cryptographic verification itself is performed by the caller before policy evaluation, typically using the Ed25519 implementation in the Noise protocol layer.

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 →