How the Bitchat Client-Side Architecture Is Structured: A Layer-by-Layer Guide

Bitchat's client is a Swift iOS/macOS application organized into six distinct layers—UI, ViewModels, Core Services, Cryptography, Sync, and Data Models—enabling secure, decentralized messaging over multiple transport protocols.

This article examines the client-side architecture of bitchat, an open-source peer-to-peer chat client. Understanding how the bitchat client-side is structured reveals how the app achieves clean separation between presentation logic, business operations, and cryptographic operations while supporting simultaneous BLE mesh, Nostr, and TCP/UDP transports.

Architectural Overview

The bitchat client-side architecture follows MVVM (Model-View-ViewModel) with heavy reliance on dependency injection and protocol-oriented design. Every major component is replaceable, making the codebase testable and adaptable to new transport mechanisms.

The six primary layers are:

  • Views — SwiftUI components that render conversation lists, message threads, and composer interfaces
  • ViewModels — ObservableObject classes that bridge user actions to service layer operations
  • Core Services — High-level business logic for networking, encryption, routing, and presence
  • Cryptography — Noise protocol implementation for end-to-end encryption
  • Sync & Gossip — Peer-to-peer state replication and message deduplication
  • Data Models & Identity — Plain Swift structs defining domain entities and cryptographic identity management

App Launch and Dependency Injection

The entry point BitchatApp.swift establishes the entire dependency graph. Unlike many iOS apps that rely on singletons, bitchat constructs a fresh AppRuntime on launch that instantiates all services and injects them into view-models.

According to the source in BitchatApp.swift, the bootstrap sequence:

  1. Creates Transport, NoiseEncryptionService, GossipSyncManager, and GeohashPresenceService
  2. Passes these to ChatViewModelBootstrapper.build()
  3. Assigns the resulting ChatViewModel to the root view's environment
// Simplified representation of the bootstrap flow
let runtime = AppRuntime()
let services = runtime.instantiateServices()
let viewModel = ChatViewModelBootstrapper.build(with: services)
rootView.environmentObject(viewModel)

This injection pattern keeps view-models free of hardcoded dependencies, allowing identical view code to run against mocks in previews and tests.

ViewModels: State Management and Coordination

The bitchat client-side structure places significant logic in view-models. ChatViewModel.swift serves as the central observable state holder, but the architecture delegates specialized concerns to coordinator objects.

ChatViewModelBootstrapper.swift wires approximately seven coordinators:

Coordinator Responsibility
ChatPublicConversationCoordinator Public channel message flows
ChatPrivateConversationCoordinator Direct message handling
ChatMediaTransferCoordinator Image/audio upload and download
ChatComposerCoordinator Input validation and draft management
GeoPresenceTracker Location-based peer discovery
BluetoothAlertCoordinator BLE permission and state handling

Each coordinator conforms to a protocol, enabling test doubles and alternative implementations.

Example: Sending a Message Through the ViewModel

struct MessageComposer: View {
    @ObservedObject var chatVM: ChatViewModel
    @State private var text = ""

    var body: some View {
        HStack {
            TextField("Type a message…", text: $text)
            Button("Send") {
                // Triggers: validation → MessageRouter → encryption → transport
                chatVM.sendMessage(text: text, media: nil)
                text = ""
            }
        }
    }
}

The sendMessage method in ChatViewModel constructs a BitchatMessage struct, then delegates routing to MessageRouter.swift.

Core Services: Routing and Transport Abstraction

The service layer in bitchat's client-side architecture abstracts all networking complexity. MessageRouter.swift implements the central routing decision engine.

When sendMessage reaches the router, it evaluates:

  1. Destination type — public group, private conversation, or broadcast
  2. Available transports — BLE mesh proximity, Nostr relay connectivity, or direct TCP/UDP
  3. Encryption requirements — Noise session existence with recipient
// Conceptual router logic based on MessageRouter.swift implementation
func route(message: BitchatMessage, to destination: Destination) {
    if canUseBLEMesh(destination) {
        sendViaBLE(message)
    } else if nostrRelayAvailable {
        sendViaNostr(message)
    } else {
        sendViaTransport(message) // TCP/UDP fallback
    }
}

Transport.swift provides the underlying abstraction. Concrete implementations handle platform-specific networking while presenting a uniform send(data:to:) interface.

Cryptography Layer: Noise Protocol Implementation

Bitchat's client-side security depends on Noise/SecureNoiseSession.swift, implementing the Noise Protocol Framework for zero-round-trip encryption.

NoiseEncryptionService.swift manages the session lifecycle:

Each session maintains cryptographic state independent of the transport carrying its packets. This separation means encrypted messages survive transport switching—a message may encrypt over Noise, fragment for BLE, fail, retry over Nostr, and decrypt identically at the recipient.

Sync and Gossip: Decentralized State Replication

The bitchat client-side structure includes sophisticated peer-to-peer synchronization via Sync/GossipSyncManager.swift and Sync/RequestSyncManager.swift.

These components implement epidemic broadcast for consistency without central servers:

  • GossipSyncManager periodically exchanges RequestSyncPacket structures with discovered peers
  • RequestSyncManager handles incoming sync requests and constructs delta responses
  • Sync/SyncResponseRateLimiter.swift prevents sync flooding

The sync layer operates below message routing but above raw transport, ensuring conversation history converges even when participants intermittently connect through different network paths.

Identity and Persistence

Cryptographic identity lives in Identity/SecureIdentityStateManager.swift, which:

  • Generates X25519 key pairs on first launch
  • Batches pre-keys for offline peer initialization
  • Persists secrets via KeychainManager.swift

The keychain integration uses iOS SecItem APIs with kSecAttrAccessibleAfterFirstUnlock for background message decryption. Group membership and message content use plain Swift structs (Models/BitchatPeer.swift, Models/BitchatMessage+Media.swift) serialized to the app's private storage.

Key Source Files

File Path Architectural Role
BitchatApp.swift Application entry and dependency graph construction
ViewModels/ChatViewModel.swift Central observable state holder
ViewModels/ChatViewModelBootstrapper.swift Factory for view-models and coordinators
ViewModels/GeohashSubscriptionManager.swift Location-based presence observation
Services/Transport.swift Multi-protocol networking abstraction
Services/MessageRouter.swift Message routing logic across transports
Services/NoiseEncryptionService.swift Noise session management
Services/GeohashPresenceService.swift Geographic peer discovery
Noise/SecureNoiseSession.swift Concrete Noise protocol implementation
Sync/GossipSyncManager.swift Peer-to-peer gossip synchronization
Identity/SecureIdentityStateManager.swift Cryptographic identity lifecycle
Models/BitchatMessage+Media.swift Message data model with media support
Utils/MessageDeduplicator.swift Duplicate message suppression

Summary

  • Bitchat's client-side architecture separates UI, state, services, cryptography, sync, and persistence into distinct layers
  • MVVM with coordinators keeps view code simple while isolating complex business logic
  • Dependency injection throughout enables testing and transport flexibility
  • Noise protocol implementation in SecureNoiseSession.swift provides modern encryption
  • Gossip-based sync allows decentralized operation without central servers
  • Multi-transport support (BLE, Nostr, TCP/UDP) is transparent to UI code through Transport.swift abstraction

Frequently Asked Questions

What architecture pattern does bitchat use?

Bitchat uses MVVM with coordinator objects. SwiftUI views observe ObservableObject view-models, which delegate to specialized coordinators for concerns like public conversations, private messages, media transfer, and Bluetooth state. This pattern keeps views thin and testable.

How does bitchat handle multiple transport protocols?

The MessageRouter.swift service evaluates destination type, network availability, and encryption state to select among BLE mesh, Nostr relays, or direct TCP/UDP. The Transport.swift abstraction hides protocol specifics from higher layers.

Where is the encryption implemented in bitchat?

End-to-end encryption lives in Noise/SecureNoiseSession.swift, implementing the Noise Protocol Framework. NoiseEncryptionService.swift manages session creation and key rotation, while Identity/SecureIdentityStateManager.swift handles long-term key storage in the iOS keychain.

How does bitchat synchronize messages without a server?

Sync/GossipSyncManager.swift implements epidemic broadcast: peers exchange RequestSyncPacket structures to discover missing messages. The protocol converges on consistent history through pairwise synchronization, with rate limiting in Sync/SyncResponseRateLimiter.swift to control bandwidth.

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 →