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:

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:

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:

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 →