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

> Discover how BitChat secures peer announcements using BLEAnnounceHandlingPolicy's 3-stage security pipeline: identity checks, trust verification, and response planning.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: internals
- Published: 2026-08-03

---

**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`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandler.swift).**

BitChat's Bluetooth Low Energy (BLE) peer discovery relies on cryptographically secured announcements broadcast between devices. The **`BLEAnnounceHandlingPolicy`** in the [permissionlesstech/bitchat](https://github.com/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`](https://github.com/permissionlesstech/bitchat/blob/main/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`.

```swift
// 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`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandler.swift) file orchestrates the complete validation pipeline. The handler's `handle(_:from:)` method implements strict short-circuit evaluation—any failure immediately terminates processing:

```swift
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

```swift
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:

```swift
// 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`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandlingPolicy.swift) | Implements `BLEAnnouncePreflightPolicy`, `BLEAnnounceTrustPolicy`, and `BLEAnnounceResponsePolicy` |
| [`BLEAnnounceHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandler.swift) | Orchestrates the three-stage pipeline and produces `BLEAnnounceHandlingResult` |
| [`BLEAnnouncePreflightPolicyTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnouncePreflightPolicyTests.swift) | Unit tests for identity verification logic |
| [`BLEAnnounceTrustPolicyTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceTrustPolicyTests.swift) | Unit tests for Ed25519 signature validation |
| [`BLEAnnounceResponsePolicyTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnouncePreflightPolicyTests.swift) directly invokes preflight checks without instantiating `BLEAnnounceHandler`, enabling focused unit testing of individual validation stages.