# How Public History Synchronization Works in BitChat: The Gossip Sync Protocol

> Learn how BitChat synchronizes public history using the Gossip Sync Protocol. Discover how Golomb-coded set filters ensure efficient message exchange and data retention.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-08-22

---

**BitChat synchronizes public chat history across mesh peers using a lightweight gossip-sync protocol that exchanges compact Golomb-coded set (GCS) filters every 15 seconds to identify and request missing messages, maintaining a rolling 6-hour retention window for text and 15 minutes for media fragments.**

BitChat is a decentralized, offline-first messaging application designed for permissionless mesh networking. To ensure all participants maintain a consistent view of recent public conversation without relying on central servers, the app implements **Public History synchronization**—a gossip-based mechanism formalized in the project's whitepaper. This protocol leverages space-efficient probabilistic filters and selective message retrieval to minimize bandwidth usage across intermittent Bluetooth and peer-to-peer links.

## The Gossip Sync Architecture

### Persistent Local Cache

Every device maintains a local cache of approximately **1,000 recent public broadcast packets** stored in a persistent `ConversationStore`. According to [`docs/CONVERSATION-STORE-DESIGN.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/CONVERSATION-STORE-DESIGN.md), this cache survives app restarts by writing to disk, enabling devices that reconnect after network partitions to serve recent history to peers. The [`bitchat/App/PublicChatModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/App/PublicChatModel.swift) file houses the core view-model that owns this cache and orchestrates the synchronization lifecycle.

### Periodic Reconciliation with GCS Filters

At the heart of the protocol lies the **Golomb-coded set (GCS) filter**, a space-efficient probabilistic data structure implemented in [`bitchat/Utils/GcsFilter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Utils/GcsFilter.swift). Approximately every **15 seconds**, each peer advertises a compact GCS filter summarizing the hashes of messages currently held in its local cache. This interval balances synchronization freshness with power consumption constraints on mobile devices operating in Bluetooth mesh environments.

## The Synchronization Workflow

### Filter Exchange and Delta Detection

When `PublicChatModel` triggers `runGossipSync()`, the system generates a local GCS filter from `cachedPublicMessages` and transmits it via `peerConnector.send(filter:)`. Upon receiving a remote peer's filter, the local device executes `missingHashes(comparedTo:)` to compute the set difference—identifying exactly which message hashes exist on the remote side but are absent locally.

### Selective Message Retrieval

Rather than broadcasting full message contents, peers request only the specific packets they lack. The `requestMissingMessages(hashes:)` method iterates through missing hashes, calling `peerConnector.requestMessage(hash:)` for each. This targeted approach keeps bandwidth minimal in low-throughput mesh environments. Received packets are immediately added to the local cache via `cachePublicMessage()`, and the local GCS filter updates to reflect the new state.

## Retention Policies and Storage Constraints

### Text Message Retention Window

Cached public messages remain **synchronizable for 6 hours** before automatic eviction. As documented in [`docs/WHITEPAPER.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/WHITEPAPER.md) Section 6.3, this window provides sufficient overlap for transient network partitions while preventing unbounded storage growth. The persistent disk backing ensures that even devices offline for extended periods retain their 6-hour history buffer to share with reconnecting peers.

### Media Fragment Lifecycle

Media fragments and file-transfer packets operate under stricter constraints. These larger payloads utilize a **15-minute retention window**, after which they are discarded regardless of synchronization status. This differential treatment prioritizes text conversation consistency over resource-intensive media distribution in bandwidth-constrained mesh topologies.

## Implementation Details in Swift

The gossip-sync loop begins via `startGossipSync(every:)` in [`PublicChatModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PublicChatModel.swift), which establishes a timer-driven reconciliation process:

```swift
// Start the periodic gossip sync (called from AppRuntime)
publicChatModel.startGossipSync(every: 15.0)   // 15‑second interval

// Inside PublicChatModel.swift (simplified)
func startGossipSync(every interval: TimeInterval) {
    Timer.publish(every: interval, on: .main, in: .common)
        .autoconnect()
        .sink { [weak self] _ in self?.runGossipSync() }
        .store(in: &cancellables)
}

private func runGossipSync() {
    let localFilter = GcsFilter.create(from: cachedPublicMessages)
    peerConnector.send(filter: localFilter) { remoteFilter in
        let missingHashes = remoteFilter.missingHashes(comparedTo: localFilter)
        self.requestMissingMessages(hashes: missingHashes)
    }
}

```

Selective retrieval of missing packets occurs through the networking layer, typically implemented in [`bitchat/Networking/PeerConnector.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Networking/PeerConnector.swift):

```swift
// Request missing packets from a peer
func requestMissingMessages(hashes: [MessageHash]) {
    for hash in hashes {
        peerConnector.requestMessage(hash: hash) { message in
            self.cachePublicMessage(message)
        }
    }
}

```

The `GcsFilter.create(from:)` method in [`bitchat/Utils/GcsFilter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Utils/GcsFilter.swift) handles the Golomb-coded serialization of message hashes, producing filters sized at just a few dozen bytes—critical for efficient transmission over Bluetooth Low Energy.

## Summary

- **Public History synchronization** relies on a gossip-sync protocol with 15-second reconciliation intervals.
- **Golomb-coded set (GCS) filters** enable efficient set reconciliation using only tens of bytes per exchange.
- The system maintains approximately **1,000 public messages** in a persistent disk cache to survive app restarts and network partitions.
- **Text messages** remain synchronizable for **6 hours**, while **media fragments** expire after **15 minutes**.
- Core implementation resides in [`PublicChatModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PublicChatModel.swift), utilizing `PeerConnector` for transport and `GcsFilter` for hash summarization.

## Frequently Asked Questions

### How does BitChat handle devices that reconnect after long disconnections?

Devices persist their ~1,000 message cache to disk via `ConversationStore`, allowing them to retain the most recent 6 hours of public history even after app restarts or extended offline periods. When reconnecting to the mesh, these devices immediately advertise their cached history through GCS filters, allowing peers who missed messages during the partition to request the specific packets they lack.

### What is a GCS filter and why does BitChat use it?

A **Golomb-coded set (GCS) filter** is a space-efficient probabilistic data structure that compresses a set of message hashes into a compact binary format. BitChat uses GCS filters—implemented in [`bitchat/Utils/GcsFilter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Utils/GcsFilter.swift)—because they require only a few dozen bytes to represent hundreds of message hashes, making them ideal for bandwidth-constrained Bluetooth mesh networks where exchanging full message lists would be prohibitively expensive.

### How frequently do peers synchronize their public chat history?

Peers initiate synchronization approximately **every 15 seconds**, as configured by the `startGossipSync(every: 15.0)` timer in [`PublicChatModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PublicChatModel.swift). This interval provides near-real-time consistency while conserving battery life on mobile devices operating in decentralized mesh topologies.

### Why are media fragments retained for a shorter period than text messages?

BitChat applies a **15-minute retention window** to media fragments compared to the 6-hour window for text messages because media files consume significantly more storage and bandwidth. This distinction ensures that the limited resources of mesh nodes prioritize the consistency of text conversation history over the distribution of large file transfers.