What Are the Core Components of the Bitchat Architecture? A Deep Dive into the Dual-Transport Messaging Stack
Bitchat's core architecture centers on a dual-transport messaging stack that combines a local Bluetooth-mesh layer with a global Nostr-relay layer, unified through a transport-agnostic protocol abstraction.
The bitchat project (permissionlesstech/bitchat) implements a privacy-first, peer-to-peer messaging system designed to function seamlessly across both offline mesh networks and internet-connected relays. Understanding the core components of the bitchat architecture reveals how the system achieves resilience, privacy, and seamless failover between local and global transports.
The Dual-Transport Foundation: BLEService and NostrRelayManager
At the heart of bitchat lie two complementary transport implementations that the application orchestrates automatically.
BLEService: The Bluetooth-Mesh Engine
BLEService implements the local Bluetooth-mesh transport in bitchat/Services/BLE/BLEService.swift (class definition at line 78). This component handles:
- Peer discovery and link-state management for nearby devices
- Noise-protocol handshakes for secure session establishment
- Fragment re-assembly for large message and file transfers
- Secure private-media transfers over the mesh
The BLEService contains the complete mesh engine and operates independently of internet connectivity, enabling messaging in offline scenarios such as festivals, protests, or disaster zones.
NostrRelayManager: The Global Relay Gateway
NostrRelayManager manages WebSocket connections to Nostr relays in bitchat/Nostr/NostrRelayManager.swift (defined at line 31). Its responsibilities include:
- Encoding and decoding Nostr events
- Event deduplication across multiple relays
- Exponential back-off for reconnections
- Outbound message queuing during network unavailability
This component ensures messages reach recipients even when they are outside Bluetooth range, using the decentralized Nostr relay network as a fallback.
Transport Protocol Abstraction: The Unified Interface
Both transport layers conform to a common Transport protocol defined in bitchat/Services/Transport.swift (line 7). This abstraction enables the rest of the application to send messages without knowledge of the underlying medium.
// Sending a message transparently chooses Bluetooth first, then Nostr if needed
chatViewModel.sendMessage("Hello, mesh!", mentions: [], to: nil)
// Private direct messages use the same interface with a recipient PeerID
let peer = PeerID(str: "ab12cd34ef56...")
chatViewModel.sendMessage("Secret note", mentions: [], to: peer)
The transport layer internally prioritizes BLEService when peers are nearby, falling back to NostrRelayManager only when necessary.
Synchronization and State Management
GossipSyncManager: Periodic Mesh Synchronization
GossipSyncManager (bitchat/GossipSyncManager.swift) performs periodic "gossip" sync across the Bluetooth mesh. Instantiated within BLEService.startServices(), it exchanges:
- Public message packets
- File-transfer fragments
- Pre-key bundles for encryption
This gossip protocol keeps local state consistent across nearby peers without requiring a central coordinator.
RequestSyncManager: On-Demand Synchronization
RequestSyncManager (bitchat/RequestSyncManager.swift) handles explicit sync requests, such as when a user joins a new location channel. It tracks pending EOSE (End-Of-Stored-Events) callbacks from relays, ensuring complete message history retrieval.
Message Integrity: Deduplication and Broadcast Tracking
Two specialized components prevent duplicate processing:
| Component | Location | Purpose |
|---|---|---|
| MessageDeduplicator | bitchat/MessageDeduplicator.swift |
De-duplicates inbound events across both BLE and Nostr transports |
| BLESelfBroadcastTracker | bitchat/BLESelfBroadcastTracker.swift |
Prevents locally-sent broadcasts from being re-processed when echoed by peers |
These mechanisms are essential for maintaining consistency in a distributed system where messages may arrive through multiple paths.
Identity and Encryption Layer
Bitchat employs dual encryption strategies:
- Noise Protocol (
bitchat/Services/NoiseEncryptionService.swift): End-to-end encryption for Bluetooth-mesh traffic - Proprietary XChaCha20-Poly1305 envelope (
bitchat/Nostr/NostrIdentityBridge.swift): Encryption wrapper for Nostr private messages
The NostrIdentityBridge bridges the application-specific private-envelope format to standard Nostr events, preserving compatibility with the broader Nostr ecosystem while adding custom security layers.
Channel and Location Routing
Bitchat maps geographic geohash channels (block, neighborhood, city tiers) to Nostr relay subscriptions. This location-based routing system, documented in docs/GeohashPresenceSpec.md, enables geographically-scoped group chats.
// Subscribe to a location channel using geohash "dr5rsj"
let filter = NostrFilter(kinds: [1], tags: ["#geohash": ["dr5rsj"]])
NostrRelayManager.shared.subscribe(filter: filter, handler: { event in
// Process incoming event…
})
NostrRelayManager constructs these filters automatically when users join location-based channels.
Persistence and Store-And-Forward
Offline resilience depends on several store implementations:
CourierStore(bitchat/Stores/CourierStore.swift): Queues outbound courier envelopes for delayed deliveryBoardStore(bitchat/Stores/BoardStore.swift): Persists board postsBLEIncomingFileStore(bitchat/Stores/BLEIncomingFileStore.swift): Manages private-media receipts
These stores ensure no messages are lost when recipients are temporarily unreachable.
UI Integration: Delegate Pattern
The architecture exposes state changes to the view layer through BitchatDelegate and TransportEventDelegate protocols. Declared in bitchat/bitchat.entitlements and consumed throughout ChatViewModel.swift, these delegates deliver:
- Decrypted public messages
- Private-message envelopes
- Connection-state changes
This separation maintains clean boundaries between transport logic and presentation logic.
Summary
- BLEService provides local Bluetooth-mesh messaging with Noise encryption and fragment handling
- NostrRelayManager enables global reach through WebSocket connections to decentralized relays
- Transport protocol abstraction unifies both layers behind a common interface
- GossipSyncManager and RequestSyncManager handle periodic and on-demand state synchronization
- MessageDeduplicator and BLESelfBroadcastTracker ensure exactly-once message semantics
- Dual encryption layers protect traffic appropriate to each transport's threat model
- Geohash-based channel routing enables location-scoped conversations
- Store-and-forward persistence guarantees message delivery despite intermittent connectivity
Frequently Asked Questions
How does bitchat decide between Bluetooth and Nostr for message delivery?
The transport layer automatically prioritizes BLEService when target peers are within Bluetooth range. If no mesh path exists, the message routes through NostrRelayManager. This decision is transparent to calling code due to the Transport protocol abstraction in bitchat/Services/Transport.swift.
What encryption does bitchat use for private messages?
For Bluetooth-mesh traffic, bitchat uses the Noise Protocol implemented in bitchat/Services/NoiseEncryptionService.swift. For Nostr relay traffic, it uses a proprietary XChaCha20-Poly1305 envelope defined in bitchat/Nostr/NostrIdentityBridge.swift, providing authenticated encryption compatible with the Nostr ecosystem.
Can bitchat function completely offline?
Yes. When no internet connectivity exists, BLEService maintains full messaging capability within Bluetooth range (typically 10-100 meters depending on device and environment). Messages queue in CourierStore for Nostr delivery when connectivity returns, and file transfers complete directly over the mesh via fragment re-assembly.
How does bitchat prevent the same message from appearing multiple times?
MessageDeduplicator (bitchat/MessageDeduplicator.swift) maintains a cache of seen message identifiers across both transports. BLESelfBroadcastTracker (bitchat/BLESelfBroadcastTracker.swift) specifically prevents echo effects where a device receives its own broadcast back through mesh hops. Together they ensure users see each message exactly once.
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 →