How BitChat Synchronizes Public Messages Between Peers: Gossip Protocol Implementation

BitChat uses a gossip-style synchronization protocol where GossipSyncManager periodically sends REQUEST_SYNC packets to peers, RequestSyncManager validates responses using a 30-second attribution window, and BLEPublicMessageHandler delivers public messages after timestamp or RSR flag verification.

BitChat implements a decentralized mesh networking stack to keep public chat history consistent across all connected devices without centralized servers. Understanding how public messages are synchronized between peers requires examining the three-layer architecture that handles request orchestration, response validation, and packet delivery. According to the permissionlesstech/bitchat source code, this design prevents sync floods while allowing historic messages to propagate through the mesh.

The Three-Layer Synchronization Architecture

The synchronization flow splits into distinct responsibilities across three logical layers. Each layer operates independently to ensure efficient, secure message propagation.

Layer 1: Sync Orchestration with GossipSyncManager

The GossipSyncManager (located in bitchat/Sync/GossipSyncManager.swift) initiates all synchronization requests. It iterates over every connected peer and constructs a REQUEST_SYNC packet containing the SyncTypeFlags.publicMessages flag.

For normal synchronization, the packet includes IS_RSR = 0x10. For solicited responses, the packet sets ttl = 0 alongside the RSR flag. Before transmission, the manager registers the outbound request via RequestSyncManager.registerRequest(to: peerID) to establish attribution tracking.

Layer 2: Request Tracking via RequestSyncManager

The RequestSyncManager (in bitchat/Sync/RequestSyncManager.swift) maintains a short-lived map of peerID → requestTimestamp entries with a default expiration window of 30 seconds. This component prevents unsolicited sync floods by ensuring only valid responses are accepted.

When a response arrives, the handler calls isValidResponse(from:isRSR:), which returns true only if the packet is marked as a Request-Sync-Response (RSR) and a matching pending request exists within the time window. Expired entries are automatically pruned by the cleanup() method.

Layer 3: Packet Validation in BLEPublicMessageHandler

The BLEPublicMessageHandler (in bitchat/Services/BLE/BLEPublicMessageHandler.swift) performs low-level verification before delivering messages to the UI. When receiving a BitchatPacket, it checks the isRSR flag:

  • If isRSR is true, it validates the response against RequestSyncManager and skips the timestamp check to allow historic messages.
  • If isRSR is false, it enforces the 2-minute timestamp rule to reject stale or spoofed packets.

Valid packets are converted into .publicMessageReceived events and passed to the PublicMessagePipeline for local storage and UI notification.

Public Message Packet Structure

The binary protocol encodes public message synchronization using specific flags and payload types defined in bitchat/Sync/SyncTypeFlags.swift and bitchat/Noise/BinaryProtocol.swift.

  • Header: Contains SyncTypeFlags.publicMessages to identify the sync type.
  • Payload: Either an AnnounceV2Packet (for peer metadata) or a BitchatMessage with type = .message.
  • Flags: The IS_RSR flag (value 0x10) marks packets as responses to sync requests, exempting them from normal timestamp validation.

The BinaryProtocol.swift encoder handles the new IS_RSR bit, ensuring responses are properly distinguished from unsolicited broadcasts.

The Synchronization Flow Step-by-Step

The complete process of synchronizing public messages between peers follows this strict sequence:

  1. Periodic Sync Task: GossipSyncManager builds a REQUEST_SYNC packet targeting SyncTypeFlags.publicMessages.
  2. Request Registration: Before sending, it registers the request with RequestSyncManager to create the attribution window.
  3. Unicast Transmission: The packet is sent to each peer individually—no blind broadcast storms occur.
  4. Peer Reception: The recipient's BLEService decodes the packet via BinaryProtocol, sees isRSR = false, and applies the 2-minute timestamp rule.
  5. Response Generation: The peer constructs a response packet with isRSR = true and ttl = 0.
  6. Attribution Check: The original requester receives the packet; BLEPublicMessageHandler detects isRSR = true and queries RequestSyncManager.isValidResponse(...). If pending, the timestamp check is bypassed.
  7. Delivery: Valid payloads reach PublicMessagePipeline for archiving, emitting .publicMessageReceived events that ChatViewModel displays in the UI.

This flow is rate-limited to 8 responses per 30 seconds per peer to prevent network congestion.

Security and Efficiency Guarantees

The implementation provides three critical protections:

  • Attribution: Only responses matching a pending request in RequestSyncManager are accepted, preventing unsolicited sync floods from malicious or malfunctioning peers.
  • Security: Normal packets must fall within a 2-minute clock window; RSR packets bypass this only when cryptographically linked to a valid request.
  • Efficiency: Synchronization uses unicast messaging rather than broadcast storms, with built-in rate limiting to preserve battery and bandwidth on mobile devices.

Code Implementation Examples

The following Swift patterns demonstrate how the public message synchronization API is used throughout the codebase:

// Sending a public message from the UI layer
let viewModel = ChatViewModel(...)
viewModel.sendPublicMessage(
    content: "Hello, mesh!",
    to: .mesh          // public channel
)

// Internal pipeline queuing
publicMessagePipeline.enqueue(message, to: .mesh)

The BLE layer handles reception through the BLEPublicMessageHandler:

// Simplified validation logic in BLEPublicMessageHandler.swift
publicMessageHandler.handle(packet, from: peerID)

if packet.isRSR {
    // Verify the response belongs to a pending request
    guard requestSyncManager.isValidResponse(from: peerID, isRSR: true) else { return }
    // Accept historic messages beyond 2-minute window
} else {
    // Enforce timestamp freshness (2 minutes)
    guard abs(now - packet.timestamp) < 120 else { return }
}

// Deliver to UI
delegate.publicMessageReceived(
    peerID: peerID,
    nickname: packet.nickname,
    content: packet.content,
    timestamp: packet.timestamp,
    messageID: packet.messageID
)

Summary

  • GossipSyncManager initiates sync by sending REQUEST_SYNC packets with SyncTypeFlags.publicMessages and registering requests in RequestSyncManager.
  • RequestSyncManager maintains a 30-second attribution window to validate RSR-flagged responses and prevent unsolicited sync floods.
  • BLEPublicMessageHandler enforces a 2-minute timestamp rule for normal packets while bypassing checks for valid RSR responses.
  • The protocol uses IS_RSR = 0x10 flagging and ttl = 0 to distinguish solicited historic messages from live traffic.
  • Rate limiting (8 responses per 30 seconds per peer) and unicast transmission prevent mesh network congestion.

Frequently Asked Questions

What is the IS_RSR flag in BitChat's synchronization protocol?

The IS_RSR (Request-Sync-Response) flag is a binary bitmask value (0x10) set in the packet header to indicate that a message is a response to a specific synchronization request rather than a live broadcast. When isRSR is true, the receiving peer queries RequestSyncManager to verify the response matches a pending request, allowing the packet to bypass the standard 2-minute timestamp validation and accept older historic messages.

How does BitChat prevent stale or spoofed messages during synchronization?

BitChat enforces a 2-minute timestamp window on all incoming packets that are not marked as RSR responses, rejecting any packet where abs(now - packet.timestamp) >= 120. Additionally, the RequestSyncManager requires that RSR-flagged packets match a previously registered request within a 30-second window, ensuring only solicited historic data is accepted and preventing replay attacks or unsolicited sync floods.

What is the time window for tracking sync requests in RequestSyncManager?

The default tracking window is 30 seconds. When GossipSyncManager sends a REQUEST_SYNC packet, it registers the peer ID and timestamp in RequestSyncManager. The isValidResponse(from:isRSR:) method only returns true if the incoming RSR packet arrives within this window, after which cleanup() prunes expired entries to prevent memory growth.

How does BitChat secure public message synchronization against spoofing?

The protocol ensures attribution by requiring all historic messages (RSR packets) to correlate with a registered request in RequestSyncManager, preventing arbitrary injection of old messages. Combined with the 2-minute freshness check for non-RSR packets and unicast-only sync transmission, these mechanisms ensure that peers only accept messages from legitimate mesh participants that are currently connected and responsive.

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 →