How BitChat Implements Offline-First Communication: Store-and-Forward Architecture Explained
BitChat implements offline-first communication through a store-and-forward architecture where encrypted courier envelopes are persisted locally and forwarded opportunistically via BLE mesh or Nostr relays until delivery succeeds.
BitChat is an open-source messaging application built by permissionlesstech that guarantees message delivery even when recipients are temporarily unreachable or completely offline. The Swift-based implementation centers on a sophisticated store-and-forward system that combines local mesh networking with global relay infrastructure. This article examines the source code to explain exactly how BitChat achieves resilient offline-first communication without centralized servers.
Core Store-and-Forward Components
CourierStore (Offline Mailbag)
The CourierStore class in bitchat/Services/Courier/CourierStore.swift serves as the central mailbox for encrypted envelopes that devices carry on behalf of other peers. It stores opaque ciphertext envelopes that cryptographically hide the sender, receiver, and content from the storing device itself. The implementation enforces strict quotas—including total count limits, per-depositor tiers, copy budgets, and a 24-hour maximum lifetime—to prevent any single device from becoming a public mailbag.
CourierDepositTier and Trust-Based Quotas
BitChat uses CourierDepositTier to differentiate between trusted and untrusted depositors. Favorites receive larger storage quotas and immunity from eviction, while verified deposits (signature-verified but not favorited) receive smaller allocations. This ensures strangers cannot crowd out important messages from trusted contacts.
MessageRouter and Static Addressing
The MessageRouter in bitchat/Services/MessageRouter.swift provides static noise keys for each peer, enabling addressing of offline recipients through persistent cryptographic identifiers. When a message cannot be delivered immediately, the router hands the envelope to CourierStore.deposit(envelope, from:depositorNoiseKey:, tier:) with the depositor's trust tier for quota management.
UnifiedPeerService State Management
UnifiedPeerService in bitchat/Services/UnifiedPeerService.swift merges mesh connectivity, Nostr availability, and favorite status into unified BitchatPeer objects. Each peer carries three possible states: connected, reachable (seen recently on the BLE mesh), or offline. The service specifically retains offline mutual favorites so the router can continue addressing them despite temporary unavailability.
Dual-Transport Offline Resilience
BLE Mesh Transport for Local-First Delivery
The BLE mesh implementation in bitchat/Services/BLE/BLEService.swift creates a peer-to-peer network that functions without internet connectivity. When the recipient is mesh-reachable, envelopes spray directly to the peer with duplicate prevention via the sprayedTo set. The mesh reports peer snapshots that UnifiedPeerService interprets as reachable within a configurable retention window.
Nostr Transport as Global Fallback
NostrTransport in bitchat/Services/NostrTransport.swift provides global delivery when local mesh contacts fail. It maintains a reachablePeers cache derived from favorite relationships containing Nostr public keys. When internet connectivity returns, the transport pushes queued envelopes to Nostr relays for delivery to offline recipients who may reconnect through different network paths.
GossipSyncManager for Eventual Consistency
The GossipSyncManager in bitchat/Sync/GossipSyncManager.swift handles post-reconnection backfill. When devices reconnect after offline periods, they pull missed envelopes from the mesh or Nostr relays, ensuring eventual consistency across the decentralized network.
The Offline-First Message Flow
-
Composition and Encryption: The sender creates a
CourierEnvelopecontaining the recipient's tag (derived from the static noise key) and encrypts the payload. -
Deposit:
MessageRoutercallsCourierStore.deposit(envelope, from:myNoiseKey, tier:.favorite)with validation for size, expiry, and copy budget constraints. -
Immediate Forward: If the recipient is reachable on the BLE mesh, the envelope sprays immediately with budget tracking via the
sprayedToset. -
Offline Persistence: If the peer is offline, the envelope remains in
CourierStorewithmaxExpirySlackguaranteeing 24-hour retention and automatic eviction. -
Opportunistic Delivery: Courier devices forward envelopes as they move within mesh range or when Nostr connectivity resumes.
-
Acknowledgement:
AckPacerthrottles delivery receipts to avoid Nostr relay rate limits.
Implementation Examples
Deposit an envelope for offline delivery:
let envelope = CourierEnvelope(
recipientTag: recipientTag,
expiry: UInt64(Date().addingTimeInterval(48*3600).timeIntervalSince1970 * 1000),
ciphertext: encryptedPayload,
copies: 3,
prekeyID: nil
)
let success = CourierStore.shared.deposit(
envelope,
from: myNoiseKey,
tier: .favorite // or .verified for non-favorites
)
Creating offline peer objects in UnifiedPeerService:
let peer = BitchatPeer(
peerID: peerID,
noisePublicKey: favoriteKey,
nickname: favorite.nickname,
lastSeen: Date(),
isConnected: false,
isReachable: false,
localPetname: localPetname(forFingerprint: nil)
)
Sending via Nostr when mesh is unavailable:
let transport = NostrTransport(
keychain: keychain,
idBridge: idBridge
)
transport.send(message: envelope, to: recipientPeerID)
UI offline state detection:
if peer.isReachable {
label = Strings.mesh_peers.state.reachable
} else if peer.isConnected {
label = Strings.mesh_peers.state.connected
} else {
label = Strings.mesh_peers.state.offline // offline icon shown
}
Summary
- CourierStore provides encrypted, quota-managed storage for undeliverable messages in
bitchat/Services/Courier/CourierStore.swift - MessageRouter enables static addressing of offline peers through persistent noise keys in
bitchat/Services/MessageRouter.swift - UnifiedPeerService maintains comprehensive peer state across mesh, Nostr, and favorite relationships in
bitchat/Services/UnifiedPeerService.swift - Dual-transport architecture combines BLE mesh for local delivery and Nostr for global fallback without central coordination
- GossipSyncManager ensures eventual consistency when peers reconnect after offline periods
- All envelopes carry cryptographically enforced expiration timestamps and copy budgets to prevent indefinite storage growth
Frequently Asked Questions
How does BitChat prevent storage exhaustion when storing messages for offline peers?
BitChat implements tiered quotas through CourierDepositTier in CourierStore.swift. Favorite contacts receive larger allocations and eviction protection, while verified strangers face strict limits on envelope count, size, and 24-hour maximum lifetimes. The maxExpirySlack parameter guarantees automatic cleanup of undelivered messages after this window.
Can BitChat deliver messages without any internet connectivity?
Yes. The BLE mesh transport in bitchat/Services/BLE/BLEService.swift creates a local peer-to-peer network capable of forwarding encrypted envelopes between devices in physical proximity. Messages hop across the mesh until reaching the recipient or a device with Nostr connectivity that can bridge to the global network.
How does BitChat handle addressing when recipients are offline?
The MessageRouter utilizes static noise keys that persist in UnifiedPeerService as "offline favorite rows." These keys allow the router to encrypt and address envelopes specifically for recipients regardless of their current connectivity state. The envelopes remain in CourierStore until the recipient appears on either the mesh or Nostr network.
What prevents duplicate message delivery across multiple transports?
The CourierEnvelope includes a sprayedTo set that tracks which peers have already received the envelope. When forwarding via BLE mesh, the system checks this set against the copy budget to prevent double-spending. Additionally, AckPacer manages delivery acknowledgements to avoid redundant transmissions through Nostr relays.
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 →