How Peer Discovery Works in the BitChat Bluetooth Mesh Network
BitChat uses a three-stage pipeline—continuous BLE scanning, cryptographic pre-flight validation, and trust-based reciprocal announcements—to discover, verify, and bind peers into a self-healing mesh topology.
BitChat is an open-source, permissionless messaging application developed by permissionlesstech that constructs a Bluetooth-LE mesh network among nearby devices. The peer discovery process within the BitChat Bluetooth mesh network coordinates scanning, security validation, and topology tracking to enable decentralized, multihop communication without internet connectivity.
The Three-Stage Discovery Pipeline
The discovery architecture separates concerns into distinct policy-driven stages managed by BLEService and BLEAnnounceHandler:
Continuous BLE Scanning
When the central manager state becomes powered-on, BLEService.startScanning() initiates a Bluetooth-LE scan with allowDuplicates = true. This ensures every advertisement is delivered immediately, which is essential for rapid mesh convergence. The scan duty cycle adapts via BLEScanDutyPolicy.plan, switching between continuous and duty-cycled modes based on app activity and recent traffic.
Pre-Flight Validation
Raw advertisements are decoded into AnnouncementPacket structures and filtered by BLEAnnouncePreflightPolicy.evaluate(...). This stage performs lightweight, signature-less sanity checks: it derives the sender's PeerID from the noisePublicKey, rejects malformed payloads, drops self-announces, and discards stale packets outside the freshness window.
Trust Verification and Response
BLEAnnounceTrustPolicy.evaluate(...) enforces cryptographic security: it verifies key matches, enforces TOFU (Trust On First Use) signing-key pinning, and validates packet signatures. Once trusted, BLEAnnounceResponsePolicy.plan(...) orchestrates the response—logging UI events, scheduling gossip syncs, sending reciprocal announces, and triggering after-glow re-announcements for robustness.
Step-by-Step Discovery Flow
The complete flow from advertisement to mesh entry follows nine coordinated steps implemented across BLEService.swift and BLEAnnounceHandler.swift:
-
Scan Activation
centralManagerDidUpdateState(_:)triggersstartScanning()when Bluetooth is ready. The service usesallowDuplicates = trueto capture every broadcast instantly. -
Advertisement Receipt
The system delegate callscentral(_:didDiscover:advertisementData:rssi:). The raw payload converts to aBitchatPacketand passes toBLEAnnounceHandler.handle(_:,from:). -
Pre-Flight Checks
BLEAnnouncePreflightPolicydecodes theAnnouncementPacket, derives the PeerID from thenoisePublicKey, and rejects packets for.malformed,.senderMismatch,.selfAnnounce, or.staleconditions. -
Cryptographic Trust
BLEAnnounceTrustPolicyretrieves stored Noise and signing keys from the registry. It rejects on key mismatch, protects authenticated Noise sessions from key rotation attacks, verifies TOFU pinning, and validates signatures against announced signing keys. -
Registry Update
Inside awithRegistryBarrierblock, the handler callsupsertVerifiedAnnounceto persist the peer, records a "peer-connected" UI event for direct announces, and schedules the initial gossip sync. -
Bidirectional Discovery
For direct announces (packet.ttl == messageTTL) whereshouldSendAnnounceBackis true, the handler invokessendAnnounceBack(). This reciprocal announcement ensures both peers confirm the link, creating a two-way edge in the mesh. -
After-Glow Scheduling
For new peers,scheduleAfterglowqueues a delayed re-announcement to mitigate packet loss during initial contact. -
Topology Tracking
Verified announces carry adirectNeighborslist.MeshTopologyTracker.updateNeighbors(for:neighbors:at:)stores these claims with timestamps, pruning stale entries after approximately 60 seconds. -
Route Computation
When routing messages,MeshTopologyTracker.computeRoute(...)executes a BFS over confirmed, fresh edges, optionally filtering by protocol version to find multihop paths.
Mesh Topology and Multihop Routing
The MeshTopologyTracker maintains the distributed routing table. It aggregates neighbor claims from announcements, timestamps each edge, and removes expired entries to prevent routing through departed nodes.
When the app needs to forward a message, the tracker performs a breadth-first search:
let tracker = MeshTopologyTracker()
let hops = tracker.computeRoute(
from: myPeerIDData,
to: destinationPeerIDData,
maxHops: 8,
requiringVersion: 2
)
if let hops = hops {
sendMessageViaMesh(hops: hops, payload: data)
}
Relevant code: MeshTopologyTracker.computeRoute in [MeshTopologyTracker.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/MeshTopologyTracker.swift).
Implementation Examples
Initializing the BLE Service
Bootstrap the service to begin peer discovery:
let bleService = BLEService(
transportConfig: TransportConfig(),
env: BLEServiceEnvironment(...)
)
bleService.start() // Powers up CBCentralManager and begins scanning
Relevant code: BLEService.start() → centralManagerDidUpdateState → startScanning() in [BLEService.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift).
Handling Incoming Advertisements
Process discovered peripherals through the validation pipeline:
func central(
_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String: Any],
rssi RSSI: NSNumber
) {
guard let packet = BitchatPacket.from(advertisementData) else { return }
let peerID = PeerID(publicKey: packet.senderID)
_ = bleAnnounceHandler.handle(packet, from: peerID)
}
The bleAnnounceHandler coordinates BLEAnnouncePreflightPolicy, BLEAnnounceTrustPolicy, and BLEAnnounceResponsePolicy defined in [BLEAnnounceHandlingPolicy.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEAnnounceHandlingPolicy.swift).
Configuring Duty-Cycled Scanning
Optimize battery usage in background modes:
let plan = BLEScanDutyPolicy.plan(
dutyEnabled: true,
appIsActive: UIApplication.shared.applicationState == .active,
connectedCount: bleService.connectedPeerCount,
hasRecentTraffic: trafficMonitor.recentTraffic
)
switch plan {
case .continuous:
bleService.startScanning()
case .dutyCycle(let on, let off):
bleService.startDutyCycleScanning(onDuration: on, offDuration: off)
}
Relevant code: BLEScanDutyPolicy.plan in [BLEScanDutyPolicy.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEScanDutyPolicy.swift).
Summary
- Continuous scanning with
allowDuplicates = trueensures immediate detection of nearby peers, whileBLEScanDutyPolicybalances discovery speed against battery life. - Defense-in-depth validation through
BLEAnnouncePreflightPolicyandBLEAnnounceTrustPolicyprevents spoofing, replay attacks, and key-mismatch exploits before peers enter the mesh. - Bidirectional bonding via reciprocal announces guarantees symmetric links, ensuring both parties recognize the connection before message routing begins.
- Self-healing topology via
MeshTopologyTrackermaintains fresh neighbor tables with 60-second pruning and computes BFS-based multihop routes on demand.
Frequently Asked Questions
How does BitChat prevent spoofed peer announcements?
BitChat cryptographically binds each AnnouncementPacket to a specific identity. BLEAnnounceTrustPolicy enforces TOFU signing-key pinning and verifies Ed25519 signatures against the announced key. If the derived PeerID from the noisePublicKey does not match the packet's declared sender, or if the signing key conflicts with previously pinned keys, the packet is rejected before the peer enters the registry.
What triggers the after-glow mechanism in peer discovery?
The after-glow mechanism triggers for brand-new peers immediately after the first successful validation. BLEAnnounceResponsePolicy.plan(...) sets shouldSendAnnounceBack = true for direct announces, and BLEAnnounceHandler calls scheduleAfterglow to queue a delayed re-announcement. This redundancy improves reliability against packet loss during the critical initial contact window.
How does the mesh support multihop messaging without internet connectivity?
The MeshTopologyTracker aggregates neighbor lists from all verified announces to build a distributed graph of the local mesh. When sending to a non-adjacent peer, computeRoute(...) performs a BFS over confirmed edges to discover intermediate hops. Each node stores only its direct neighbors' claims, enabling decentralized routing decisions without a central directory or internet access.
Can BitChat discover peers while running in the background?
Yes, via adaptive duty-cycling implemented in BLEScanDutyPolicy. When dutyEnabled is true and the app enters background state, the policy calculates an on/off duty cycle based on connected peer count and recent traffic. This allows continuous (albeit intermittent) scanning to maintain mesh connectivity while respecting iOS background execution limits and preserving battery life.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →