How BitChat Implements Source Routing for Packet Forwarding
BitChat uses a source-routing algorithm where the sender embeds an explicit list of relay identifiers in the packet header, allowing intermediate nodes to forward traffic without performing network discovery or flooding operations.
BitChat is an open-source mesh networking protocol designed for permissionless communication. The source routing algorithm implemented in the BitChat client enables efficient packet forwarding by having the originator pre-calculate the entire path through the network, eliminating the need for intermediate hops to maintain routing tables or perform broadcast searches.
Packet Version 2 and the Source-Route Header
All packets utilizing the source routing mechanism use the Packet Version 2 format defined in bitchat/Network/Packet.swift. The first byte encodes the version identifier, followed by a compact header containing a route list—an ordered array of relay identifiers represented as variable-length integers (var-ints).
The source-route header structure stores this list of relay IDs that the packet must traverse. Each intermediate node reads the first identifier in the list, determines the next network hop, and trims the consumed ID before forwarding. This design ensures that large payloads, including voice notes and media blobs, follow predetermined paths without triggering mesh-wide broadcast storms.
How the Source Routing Algorithm Works
The forwarding logic follows a strict forward-only pattern implemented in bitchat/Network/RelayForwarder.swift. When a relay receives a packet, it executes the following deterministic steps:
- Decode the header to extract the version number and route list.
- Validate the version—if the packet uses Version 1 (legacy format without source routes), the node immediately triggers the fallback flooding mechanism.
- Inspect the route list—if the list is empty, the packet has reached its final destination and is handed to the local protocol stack via
deliverLocally(). - Consume the next hop—the relay extracts the first relay ID from the list, performs a lookup to resolve the network address, and forwards the packet with the updated (trimmed) route header.
This approach guarantees that every fragment of a fragmented message follows the identical path, preventing the reassembly issues that would occur if fragments arrived via different routes.
Route Processing Logic
The core implementation of the source routing algorithm handles packet deserialization, version checking, and hop consumption. The following Swift code illustrates the critical path executed by the RelayForwarder class:
// Decode a Version-2 packet and extract the route list
let packet = try Packet.decode(rawData)
guard packet.version == .v2 else {
// Fallback to legacy flooding logic
return forwardViaFlooding(packet)
}
var route = packet.sourceRoute // e.g. [relayID1, relayID2, …]
// If the route is empty, the packet is for the local node
guard let nextRelayID = route.first else {
return deliverLocally(packet)
}
// Forward to the next hop
route.removeFirst() // Consume this hop
let nextRelay = try Relay.lookup(id: nextRelayID)
let forwarded = packet.withUpdatedRoute(route)
try nextRelay.send(data: forwarded.serialized)
// Fragment handling
if packet.isFragmented {
// Each fragment carries the same sourceRoute
// Forwarding logic is identical to the non-fragment case
forwardFragment(packet)
}
Key implementation details from this logic include the sourceRoute field being mutated on each hop and the fallback-to-flooding path executed when version mismatches occur.
Fragmentation and Large Payload Support
Large messages are segmented into multiple fragments using the logic in bitchat/Network/Fragmenter.swift. Unlike traditional mesh protocols that might route fragments independently, BitChat ensures that every fragment carries the identical source route in its header.
This design choice prevents the "flood-and-reassemble" fallback that would otherwise saturate the network. Because all fragments traverse the same predetermined path, the receiving node can reassemble the payload efficiently without waiting for missing pieces from alternative routes.
Security and Integrity
The source routing mechanism includes cryptographic protections implemented in bitchat/Crypto/CryptoSeal.swift. Each packet is authenticated using XChaCha20-Poly1305, with the authentication tag covering the entire packet payload including the route list.
This ensures that intermediate relays cannot modify the source path without detection. Any tampering with the relay identifiers invalidates the cryptographic seal, causing the receiving node to discard the packet as corrupted.
Fallback Mechanisms
The algorithm includes a robust fallback to flooding for edge cases. If a packet arrives as Version 1 (indicating an older client or missing route information), or if the route list is exhausted before the payload reaches its destination, the system automatically switches to classic flooding mode.
This safeguard guarantees message delivery at the cost of increased bandwidth usage, ensuring backward compatibility and resilience in dynamic network topologies where pre-calculated routes may become stale.
Key Implementation Files
The source routing algorithm is distributed across the following source files in the permissionlesstech/bitchat repository:
docs/SOURCE_ROUTING.md— The authoritative design document describing packet formats, motivations, and protocol specifications.bitchat/Network/Packet.swift— Defines thePacketstruct, version enumeration, and serialization logic for the source-route header.bitchat/Network/RelayForwarder.swift— Implements the core forwarding routine that consumes the source-route list and manages hop-by-hop transmission.bitchat/Network/Fragmenter.swift— Handles message segmentation and ensures each fragment retains the complete source route.bitchat/Crypto/CryptoSeal.swift— Provides cryptographic sealing and verification using XChaCha20-Poly1305, protecting route integrity.
Summary
- Source routing in BitChat embeds the complete forwarding path in the packet header, eliminating per-hop routing decisions.
- Packet Version 2 uses a var-int-encoded route list that relays trim progressively as they forward traffic.
- Fragmented messages carry identical source routes to ensure ordered reassembly without network flooding.
- XChaCha20-Poly1305 authentication covers the route list, preventing path manipulation by untrusted relays.
- A fallback to flooding ensures compatibility and delivery when source routes are unavailable or exhausted.
Frequently Asked Questions
What happens if a relay in the source route is offline?
If an intermediate relay specified in the source route becomes unreachable, the packet cannot proceed along the predetermined path. According to the design in docs/SOURCE_ROUTING.md, the sending node must detect the failure through timeout or lack of acknowledgement and calculate a new route for subsequent packets. Individual packets already in transit that encounter a dead relay are dropped, as the algorithm does not support dynamic route recalculation mid-transmission.
How does BitChat handle packets larger than a single MTU?
Large payloads are fragmented using the Fragmenter.swift utility, which splits the data into multiple Packet Version 2 frames. Each fragment header contains the same source route as the original packet, ensuring all pieces traverse the identical relay sequence. The receiving node buffers fragments until the complete payload is reassembled, preventing the broadcast storms that would occur if fragments were routed independently.
Is the source routing algorithm vulnerable to route tampering?
No. The implementation in bitchat/Crypto/CryptoSeal.swift applies an XChaCha20-Poly1305 authentication tag over the entire packet, including the route list. Intermediate relays can read the next-hop information but cannot modify the remaining path without invalidating the cryptographic signature. The destination node verifies this tag before processing, rejecting any packets with altered routes.
When does BitChat fallback to flooding instead of using source routes?
The system triggers fallback flooding in two specific cases: when a packet uses Version 1 format (which predates source routing) or when the source route list is exhausted before reaching the destination. The RelayForwarder.swift implementation checks for these conditions and invokes forwardViaFlooding() to broadcast the packet mesh-wide, ensuring delivery compatibility at the cost of reduced efficiency.
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 →