How BLEAnnounceHandlingPolicy Validates Peer Announcements in BitChat: A 3-Stage Security Pipeline

BitChat validates BLE peer announcements through a layered three-stage policy involving preflight identity checks, cryptographic trust verification, and response planning—all orchestrated in BLEAnnounceHandler.swift.

BitChat's Bluetooth Low Energy (BLE) peer discovery relies on cryptographically secured announcements broadcast between devices. The BLEAnnounceHandlingPolicy in the permissionlesstech/bitchat repository implements a defensive validation pipeline that prevents malformed packets, spoofing attempts, and replay attacks before any response is generated.

The Three-Stage Validation Architecture

The BLEAnnounceHandlingPolicy splits validation across three specialized policies, each implemented in bitchat/Services/BLE/BLEAnnounceHandlingPolicy.swift. This design enables early rejection of invalid announcements and minimizes expensive cryptographic operations.

Stage 1: Preflight Identity Verification

The preflight check ensures the announced peer identifier actually belongs to the public signing key that signed the packet. BLEAnnouncePreflightPolicy.evaluate(...) (lines 32–35) computes the hash of the signing key and compares it against the claimed PeerID.

// From BLEAnnounceHandler.swift - early rejection path
let preflight = BLEAnnouncePreflightPolicy.evaluate(packet, from: peerID)
guard case .accept(let preflightInfo) = preflight else { return nil }

This stage is computationally cheap—just a hash comparison—yet eliminates a significant class of spoofing attacks where an attacker claims another peer's identity with a different key.

Stage 2: Cryptographic Trust Verification

The trust check verifies the self-signature inside the announce. BLEAnnounceTrustPolicy.evaluate(...) (lines 94–103) extracts the Ed25519 public key from the announcement and validates that the peer signed their own payload (excluding TTL and RSR flag fields).

A valid signature cryptographically proves the announcement originated from the holder of the corresponding private key. This stage is intentionally sequenced after preflight verification to ensure the key material has already passed basic integrity checks.

Stage 3: Response Planning

After successful validation, BLEAnnounceResponsePolicy.plan(...) (lines 130–133) determines how to respond. The policy returns a BLEAnnounceResponsePlan specifying whether to send a direct reply, relayed reply, or no reply based on network conditions and routing requirements.

Full Evaluation Flow in BLEAnnounceHandler

The BLEAnnounceHandler.swift file orchestrates the complete validation pipeline. The handler's handle(_:from:) method implements strict short-circuit evaluation—any failure immediately terminates processing:

func handle(_ packet: BitchatPacket, from peerID: PeerID) -> BLEAnnounceHandlingResult? {
    // 1️⃣ Preflight – reject malformed IDs early
    let preflight = BLEAnnouncePreflightPolicy.evaluate(packet, from: peerID)
    guard case .accept(let preflightInfo) = preflight else { return nil }

    // 2️⃣ Trust – verify the self-signature
    let trustDecision = BLEAnnounceTrustPolicy.evaluate(packet, from: peerID, info: preflightInfo)
    guard case .accept = trustDecision else { return nil }

    // 3️⃣ Response – decide how to answer the announce
    let responsePlan = BLEAnnounceResponsePolicy.plan(packet, from: peerID, info: preflightInfo)
    return BLEAnnounceHandlingResult(preflight: preflightInfo,
                                     trust: trustDecision,
                                     responsePlan: responsePlan)
}

Trust-on-First-Use (TOFU) Key Pinning

Beyond the three-stage validation, BitChat implements TOFU security: the first time a peer's signing key is encountered, its public key hash is permanently stored. Subsequent announcements from the same PeerID must present identical key material—any key mismatch triggers immediate rejection.

This pinning mechanism prevents replay attacks where an adversary reuses a legitimate peer ID with a newly generated key pair.

Working with the Validation Policies

Running the Full Handling Pipeline

import Bitchat

// 1️⃣ Build an announce packet (typically received from BLE)
let packet = BitchatPacket.announce(...)

// 2️⃣ Derive sender identity from the signing key hash
let senderID = PeerID(publicKeyHash: packet.signingKeyHash)

// 3️⃣ Execute complete validation
if let result = BLEAnnounceHandler(environment: env).handle(packet, from: senderID) {
    print("✅ Announce accepted – plan: \(result.responsePlan)")
} else {
    print("❌ Announce rejected")
}

Direct Policy Usage for Testing

The individual policies can be invoked directly—useful for unit tests and debugging:

// Test preflight validation in isolation
let decision = BLEAnnouncePreflightPolicy.evaluate(packet, from: senderID)

switch decision {
case .accept(let info):
    print("Preflight passed – ID matches key hash: \(info)")
case .reject(let reason):
    print("Preflight failed: \(reason)")
}

Key Source Files

File Purpose
BLEAnnounceHandlingPolicy.swift Implements BLEAnnouncePreflightPolicy, BLEAnnounceTrustPolicy, and BLEAnnounceResponsePolicy
BLEAnnounceHandler.swift Orchestrates the three-stage pipeline and produces BLEAnnounceHandlingResult
BLEAnnouncePreflightPolicyTests.swift Unit tests for identity verification logic
BLEAnnounceTrustPolicyTests.swift Unit tests for Ed25519 signature validation
BLEAnnounceResponsePolicyTests.swift Unit tests for response plan generation

Summary

  • BLEAnnounceHandlingPolicy implements defense-in-depth through three sequential validation stages in bitchat/Services/BLE/BLEAnnounceHandlingPolicy.swift
  • Preflight verification provides cheap rejection of identity mismatches before expensive cryptography
  • Trust verification uses Ed25519 signature validation to prove private key possession
  • TOFU key pinning prevents replay attacks by permanently binding peer IDs to their first-seen public keys
  • The response planning stage determines optimal reply strategy only after full cryptographic validation succeeds

Frequently Asked Questions

What cryptographic algorithm does BitChat use for announcement signatures?

BitChat uses Ed25519 for signing peer announcements. The public key is extracted from the announcement packet and used to verify the self-signature covering the payload (excluding mutable fields like TTL and RSR flag). This design choice provides fast signature verification suitable for resource-constrained BLE environments.

How does the preflight policy prevent spoofing attacks?

BLEAnnouncePreflightPolicy prevents spoofing by enforcing cryptographic identity binding. It recomputes the hash of the signing key presented in the announcement and compares it against the claimed PeerID. Because PeerID is defined as a hash of the public key, this check mathematically guarantees that the claimed identity could only have been generated by the holder of the corresponding private key—or is a legitimate reuse of the same key.

What happens if a peer changes their signing key?

BitChat's TOFU pinning rejects key changes for existing peer IDs. When a previously seen PeerID presents a different public key hash, BLEAnnounceTrustPolicy classifies this as a trust violation and rejects the announcement. Peers wishing to rotate keys must generate a new PeerID entirely, ensuring no ambiguity about identity continuity.

Can I use the validation policies independently of the full handler?

Yes. Each policy class exposes static evaluate(...) or plan(...) methods that operate on BitchatPacket and PeerID instances. The test suite demonstrates this pattern extensively—BLEAnnouncePreflightPolicyTests.swift directly invokes preflight checks without instantiating BLEAnnounceHandler, enabling focused unit testing of individual validation stages.

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 →