How Bitchat Peer-to-Peer Communication Works: Architecture and Implementation
Bitchat uses a gossip-style synchronization layer with encrypted packets, relay routing, and deduplication to enable resilient peer-to-peer messaging without central servers.
The open-source Bitchat protocol implements peer-to-peer (P2P) communication through a lightweight gossip architecture that routes encrypted messages through configurable relays. This article examines the Swift implementation in permissionlesstech/bitchat to explain how peer-to-peer communication works at the code level.
Core Architecture Components
Bitchat's P2P stack consists of three interconnected layers, each handling specific responsibilities in the communication pipeline.
| Component | Role | Key Source Files |
|---|---|---|
| BitchatPeer | Immutable peer identifier with public keys and routing metadata | bitchat/Models/BitchatPeer.swift |
| Sync Layer | Packet encoding/decoding, rate-limiting, TTL handling | bitchat/Sync/RequestSyncPacket.swift, SyncResponseRateLimiter.swift, SyncTypeFlags.swift |
| GossipSyncManager | Orchestrates when, what, and through which relay to synchronize | bitchat/Sync/GossipSyncManager.swift, RequestSyncManager.swift |
BitchatPeer: Identity Foundation
The BitchatPeer value type uniquely identifies each participant in the network. When a user logs in, this instance is created from the device's persistent private key in bitchat/Identity/SecureIdentityStateManager.swift.
- peerID: A hex string that may carry prefixes (
mesh,noise,geoDM) indicating the transport origin - Public keys: Used for Noise protocol handshake and session establishment
- Serialization helpers: For network transmission and storage
The Peer-to-Peer Communication Flow
Step 1: Message Creation and Encryption
Outbound messages follow a strict encapsulation pipeline:
- Plain text is wrapped in
BitchatMessage - Payload is encrypted using Noise (XChaCha20-Poly1305)
- Encrypted data is placed into a
RequestSyncPacket
The RequestSyncPacket structure in bitchat/Models/RequestSyncPacket.swift contains:
| Field | Purpose |
|---|---|
p |
Packet sequence number |
m |
Maximum fragment size |
data |
Encrypted payload |
types |
Bit-mask from SyncTypeFlags categorizing content |
| TTL | Time-to-live for relay forwarding |
// Create a sync packet for a new public message
let message = BitchatMessage(text: "Hello world!", from: myPeerID)
let encrypted = try message.encrypt(using: mySessionKey) // Noise encryption
let packet = RequestSyncPacket(
p: nextPacketSeq(),
m: 1024,
data: encrypted,
types: .publicMessages
)
Step 2: Gossip-Based Dispatch
GossipSyncManager in bitchat/Sync/GossipSyncManager.swift controls packet distribution:
- Obtains reachable relays from
RelayController - Applies relay jitter to prevent traffic stampedes
- Invokes
RelayController.sendEvent(...)for transmission - Supports multi-hop forwarding through relay networks
let gossip = GossipSyncManager(
myPeerID: myPeerID,
config: .default,
requestSyncManager: RequestSyncManager()
)
await gossip.send(packet: packet)
Step 3: Relay Handling and Store-and-Forward
Relays treat packets as opaque binary blobs, enabling store-and-forward routing:
- Relay receives packet and decrements TTL
- If TTL > 0, forwards to connected peers
- Original sender identity remains hidden from recipients
This design ensures message delivery even with intermittent connectivity and NAT-restricted networks.
Step 4: Reception and Deduplication
Incoming packets traverse the BLEReceivePipeline or WebSocket-based Nostr transport:
func handleIncoming(_ raw: Data) async throws {
let sync = try RequestSyncPacket.decode(from: raw)
// Deduplication check using PacketIdUtil
guard !gossip.isDuplicate(sync) else { return }
let payload = try sync.decrypt(using: mySessionKey)
let message = try BitchatMessage(payload)
await chatService.deliver(message)
}
Deduplication occurs via PacketIdUtil in bitchat/Sync/PacketIdUtil.swift, ensuring each logical message processes once even when arriving through multiple relays.
Step 5: Rate Limiting and Abuse Protection
SyncResponseRateLimiter in bitchat/Sync/SyncResponseRateLimiter.swift enforces:
- Default limit: 2 sync responses per 30-second window
- Per-peer throttling to constrain bandwidth
- Protection against spam and denial-of-service attacks
Security Mechanisms in Peer-to-Peer Communication
Bitchat implements multiple security layers to protect message integrity and confidentiality:
- End-to-end encryption: All payloads use per-peer Noise session keys, ensuring confidentiality even across untrusted public relays
- TTL enforcement: Prevents infinite forwarding loops in the gossip network
- Fragment filtering:
RequestSyncPacket.encodeFragmentIdFiltercaps fragments per sync, blocking large-payload DoS attempts - Relay jitter: Distributes traffic temporally to prevent congestion spikes
Key Files for Understanding P2P Communication
| File | Purpose |
|---|---|
bitchat/Models/BitchatPeer.swift |
Peer identity and cryptographic helpers |
bitchat/Models/RequestSyncPacket.swift |
Binary packet format with encryption, TTL, type flags |
bitchat/Sync/SyncTypeFlags.swift |
Bit-mask enumeration for payload categories |
bitchat/Sync/SyncResponseRateLimiter.swift |
Per-window rate limiting implementation |
bitchat/Sync/GossipSyncManager.swift |
High-level sync orchestration and relay coordination |
bitchat/Sync/RequestSyncManager.swift |
Low-level packet construction and validation |
bitchat/Sync/PacketIdUtil.swift |
Packet ID generation and deduplication logic |
bitchat/Identity/SecureIdentityStateManager.swift |
Persistent cryptographic identity storage |
Summary
- Bitchat's peer-to-peer communication relies on gossip synchronization rather than direct peer connections, enabling operation through intermittent connectivity
- Three-layer architecture:
BitchatPeerfor identity, sync layer for packet handling, andGossipSyncManagerfor orchestration - Noise encryption provides end-to-end security without trusting relays
- Store-and-forward relays with TTL and deduplication ensure message delivery while preserving sender anonymity
- Rate limiting and fragment filtering protect against abuse and bandwidth exhaustion
Frequently Asked Questions
How does Bitchat find peers without a central server?
Bitchat uses configurable relays discovered through RelayController. Peers do not need direct IP visibility—messages propagate through gossip-style forwarding where each relay forwards to its connected peers. The GossipSyncManager manages this process with jitter and TTL controls.
What encryption protects messages in transit?
All payloads use Noise protocol with XChaCha20-Poly1305 authenticated encryption. Session keys are established per peer pair, ensuring that even relays handling the encrypted RequestSyncPacket cannot read message contents.
How does Bitchat prevent the same message from processing multiple times?
The PacketIdUtil in bitchat/Sync/PacketIdUtil.swift generates unique packet identifiers. GossipSyncManager.isDuplicate() checks against a deduplication cache before processing. This handles common scenarios where packets arrive via multiple relay paths.
What limits message forwarding to prevent network floods?
Three mechanisms work together: TTL decrements at each hop prevent infinite forwarding, relay jitter spreads traffic temporally, and SyncResponseRateLimiter enforces a default maximum of 2 responses per 30 seconds per peer. Additionally, encodeFragmentIdFilter caps fragment counts per packet.
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 →