What Is the Source-Based Routing Protocol v2 in BitChat? Implementation Guide

BitChat's Source-Based Routing Protocol v2 enables efficient unicast delivery across mesh networks by embedding an explicit list of intermediate relays (the source route) directly into packet headers, falling back to flood broadcast when routes fail.

The Source-Based Routing Protocol v2 (SBR v2) in the permissionlesstech/bitchat repository represents a significant evolution from the legacy flooding mechanism, allowing senders to specify exact forwarding paths through the mesh. This protocol extension maintains backward compatibility with Version 1 clients while introducing structured routing data that reduces unnecessary network traffic. All routing logic is cryptographically secured through Ed25519 signatures that cover the entire packet payload, including the embedded route list.

Overview of Source-Based Routing v2

Packet Structure and Versioning

SBR v2 introduces a fixed 16-byte header (expanded from 14 bytes in Version 1) to accommodate the routing metadata. The additional 2 bytes support a 4-byte payload length field required for larger unicast transmissions.

Key structural changes include:

  • Protocol Version: Packets explicitly set to Version 2; Version 1 packets maintain the legacy 2-byte payload length limit
  • HAS_ROUTE Flag: The header flag HAS_ROUTE (0x08) is only valid when version ≥ 2—legacy clients must ignore this bit
  • Source Route Field: A variable-length list positioned after the RecipientID containing:
    • Count (1 byte): Number of intermediate hops N
    • Hops (N × 8 bytes): Peer IDs of the relay nodes

Routing Identifiers

Each peer in the BitChat mesh is identified by an 8-byte routing ID (PeerID.routingData), derived from the short 16-character hexadecimal peer ID. As implemented in localPackages/BitFoundation/Sources/BitFoundation/PeerID.swift, this compact identifier minimizes header overhead while maintaining unique node addressing across the network.

Topology Discovery and Route Calculation

BitChat builds its mesh topology through confirmed edges—bidirectional peer relationships where both nodes acknowledge each other as neighbors.

The discovery process relies on the Neighbor List TLV (Type 0x04) embedded within IdentityAnnouncement packets. When a node receives neighbor information from a peer, it validates the edge only when reciprocity is confirmed (both peers list each other). The MeshTopologyTracker.swift file maintains this graph structure, providing the pathfinding substrate for source route calculation.

Routes are calculated to find paths of ≤ 4 intermediate hops through nodes that have been observed speaking Protocol v2, ensuring compatibility and minimizing latency across the mesh.

Security and Relay Logic

Cryptographic Protection

The protocol maintains end-to-end integrity through Ed25519 signatures that cover the entire packet, including the source route list. Any modification to the route by intermediate nodes invalidates the signature, causing receiving nodes to drop the packet immediately. This prevents route hijacking and ensures that only the original sender authorizes the forwarding path.

Relay Forwarding Mechanism

When a node receives a packet via BLERouteForwardingPolicy.swift, it executes the following validation sequence:

  1. Verify version ≥ 2 and HAS_ROUTE flag presence
  2. Locate its own routingData in the hop list
  3. Forward the packet to the next peer in the sequence (or the final recipient if last hop)
  4. Fall back to flood/broadcast if the next hop is unreachable

All fragmented payloads maintain Version 2 headers and carry identical route lists to preserve the unicast path across multiple physical frames.

iOS Implementation Specifics

The iOS client implements strict origination policies to prevent routing loops and unnecessary overhead. Defined in BLESourceRouteOriginationPolicy.swift, a source route is attached only when all six conditions are met:

  • The sender is the local peer (no re-signing of forwarded packets)
  • The recipient is not a broadcast address
  • TTL > 1
  • The recipient is not directly connected
  • A complete v2 path exists with ≤ 4 intermediary hops, all speaking Protocol v2
  • No recent failure entry exists in BLESourceRouteFailureCache

Failure Detection and Fallback

If a routed transmission does not receive an inbound acknowledgment from the recipient within 10 seconds, the route is marked as failed in BLESourceRouteFailureCache.swift for 60 seconds. During this window, the system falls back to flooding; after expiration, routing attempts resume automatically.

Code Examples

Creating a Source-Routed Packet

The following Swift implementation from BitchatPacket.swift demonstrates constructing a Version 2 packet with an explicit route:

import BitFoundation

// PeerID objects for sender, recipient, and intermediate hops
let sender = myPeerID
let recipient = remotePeerID
let hop1 = intermediatePeerA
let hop2 = intermediatePeerB

// Build route using 8-byte routingData
let route = [hop1.routingData!, hop2.routingData!]

// Construct Version 2 packet with embedded route
var packet = BitchatPacket(
    version: .v2,
    senderID: sender.routingData!,
    recipientID: recipient.routingData!,
    payload: myPayloadData,
    route: route
)

// Sign entire packet including route
packet.sign(with: mySigningKey)

Implementing Relay Forwarding Logic

The forwarding logic in BLERouteForwardingPolicy.swift processes incoming routed packets:

func forward(_ packet: BitchatPacket) {
    guard packet.version >= .v2,
          packet.flags.contains(.hasRoute),
          let route = packet.route,
          !route.isEmpty else {
        // No valid source route: fall back to flood
        flood(packet)
        return
    }

    // Locate self in the route sequence
    guard let myIndex = route.firstIndex(of: myPeerID.routingData!) else {
        // Not in route path: ignore or flood
        flood(packet)
        return
    }

    // Determine next hop (intermediate or final recipient)
    let nextHopData: Data
    if myIndex + 1 < route.count {
        nextHopData = route[myIndex + 1]
    } else {
        nextHopData = packet.recipientID
    }

    // Unicast to next hop
    sendToPeer(
        data: packet.encode(),
        peerID: PeerID(routingData: nextHopData)!
    )
}

Validating Confirmed Edges

Before forwarding, nodes verify topology through MeshTopologyTracker.swift:

let mesh = MeshTopologyTracker.shared
guard mesh.isConfirmedEdge(between: myPeerID, and: nextHopPeerID) else {
    // Unconfirmed edge: abort routing, use broadcast
    flood(packet)
    return
}

Key Source Files

The Source-Based Routing Protocol v2 is implemented across the following core files:

Summary

  • Source-Based Routing Protocol v2 upgrades BitChat packets to 16-byte headers with embedded explicit relay lists
  • The HAS_ROUTE (0x08) flag activates only in Version 2+, with strict backward compatibility for legacy clients
  • Routes consist of 1-byte hop count followed by 8-byte PeerID segments placed after the RecipientID field
  • Confirmed edges from bidirectional Neighbor List TLVs (Type 0x04) provide the topology graph for pathfinding
  • Ed25519 signatures cover the entire packet including routes, preventing tampering by intermediate nodes
  • iOS clients apply strict origination policies (6 conditions) and 60-second failure caching to optimize mesh efficiency
  • Automatic fallback to flood broadcast occurs when routes fail, next hops are unreachable, or edges are unconfirmed

Frequently Asked Questions

What is the difference between Version 1 and Version 2 packets in BitChat?

Version 1 packets use a 14-byte fixed header with a 2-byte payload length limit and do not support routing flags. Version 2 expands the header to 16 bytes (adding a 4-byte payload length field) and introduces the HAS_ROUTE (0x08) flag to indicate the presence of a source route list. Legacy Version 1 clients safely ignore the routing flag while continuing to process standard broadcasts.

How does BitChat ensure source routes have not been tampered with?

The protocol uses Ed25519 digital signatures that cryptographically cover the entire packet, including the source route list and payload. Because the signature is computed over the complete serialized bytes, any modification to the route by an intermediate relay invalidates the signature. Receiving nodes verify signatures before processing, dropping packets that fail authentication.

What happens when a source route fails in BitChat v2?

When a routed transmission receives no acknowledgment within 10 seconds, the route is marked as failed in BLESourceRouteFailureCache.swift and excluded from routing decisions for 60 seconds. During this period, the sender falls back to flood broadcast to ensure message delivery. After the cache entry expires, the system attempts source routing again to detect topology healing.

How does a BitChat node discover available routes?

Nodes build a mesh topology through Neighbor List TLVs (Type 0x04) exchanged in IdentityAnnouncement packets. The MeshTopologyTracker.swift module constructs a graph of confirmed edges, where an edge exists only if both peers acknowledge each other as neighbors. Source routes are calculated as paths of ≤ 4 hops through this confirmed-edge graph, ensuring all intermediate nodes are known and reachable.

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 →