# BitChat Source Routing Mechanism: How Explicit Hop Paths Replace Flooding in Protocol v2

> Discover BitChat's source routing mechanism in protocol v2. Learn how explicit hop paths replace flooding for efficient unicast forwarding, improving performance and reducing network overhead.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: internals
- Published: 2026-08-07

---

**BitChat implements source-based routing through protocol version 2, which embeds an explicit ordered list of intermediate relay peers directly into packets, enabling efficient unicast forwarding instead of default flood-and-gossip behavior.**

The BitChat peer-to-peer messaging protocol supports two forwarding modes: legacy flooding and directed unicast via source routing. This article examines the source routing mechanism as implemented in the `permissionlesstech/bitchat` repository, covering packet structure, topology discovery, route computation, and relay forwarding logic.

## Protocol Versioning and Routing Flags

BitChat distinguishes packet capabilities through version numbering. **Version 1** uses a 2-byte payload length field with no routing capabilities. **Version 2** expands this to 4 bytes and introduces the `HAS_ROUTE (0x08)` flag, which is only valid when `version ≥ 2` according to [[`docs/SOURCE_ROUTING.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md)](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md#L11-L17).

The version field determines how peers parse subsequent bytes. A v2 parser checks the flag bitmap before attempting to read the variable-length source route field.

## Packet Layout and Source Route Field

A v2 packet with source routing activated follows this byte layout:

```

Fixed Header (16 B) | SenderID (8 B) | RecipientID (8 B) |
Source Route (variable) | Payload | Signature

```

The **Source Route** field consists of two components:

- **1-byte hop count** specifying how many intermediate relays follow
- **N × 8-byte peer IDs** for each intermediate hop

As documented in [[`docs/SOURCE_ROUTING.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md)](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md#L68-L71), the route lists **only intermediate hops**—the sender and final recipient are intentionally omitted. This reduces packet size and prevents route manipulation attacks that might target endpoint impersonation.

## Topology Discovery via Neighbor Gossip

Before computing routes, nodes must build a mesh graph. BitChat accomplishes this through a **Neighbor List TLV (`0x04`)** embedded in `IdentityAnnouncement` packets. Each node advertises its directly-connected peers using this TLV structure defined in [[`docs/SOURCE_ROUTING.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md)](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md#L82-L94).

The mesh-graph service constructs bidirectional edges with strict confirmation requirements: **an edge is only valid when both peers list each other**. Unconfirmed edges are ignored for route calculation to prevent routing through stale or one-sided connections. This symmetric validation appears in [[`docs/SOURCE_ROUTING.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md)](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md#L98-L104).

## Route Computation and Origination Policy

When the local device initiates a directed packet, **`BLESourceRouteOriginationPolicy`** in [[`bitchat/Services/BLE/BLESourceRouteOriginationPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLESourceRouteOriginationPolicy.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLESourceRouteOriginationPolicy.swift#L4-L39) applies six sequential gate checks:

1. **Local authorship** – the packet must originate from this peer
2. **Single-peer destination** – `RecipientID` targets exactly one peer
3. **TTL > 1** – link-local packets like `REQUEST_SYNC` never carry routes
4. **Indirect recipient** – the destination must not be directly connected
5. **Policy permission** – `shouldAttemptRoute` returns true
6. **Route availability** – `computeRoute` closure returns a non-empty path

If any gate fails, the packet falls back to the legacy flood/direct-write path. This conservative approach preserves reliability while opportunistically optimizing when conditions permit.

### Swift Implementation Example

```swift
// Route origination with policy gates
let packet = BitchatPacket(
    version: 2,
    senderID: localPeerIDData,
    recipientID: recipientIDData,
    payload: messageData,
    flags: [.hasRoute]
)

// Policy evaluation through BLESourceRouteOriginationPolicy
let routedPacket = BLESourceRouteOriginationPolicy.route(
    for: packet,
    to: recipientID,
    localPeerIDData: localPeerIDData,
    isRecipientConnected: { BLEService.shared.isConnected($0) },
    shouldAttemptRoute: BLEService.shared.shouldAttemptRoute,
    computeRoute: { target in
        MeshTopologyTracker.shared.computeRoute(to: target)?
            .map { $0.rawData }
    }
)

// Transmission via BLEService
BLEService.shared.send(routedPacket)

```

## Forwarding Logic for Relay Nodes

When a relay receives a packet not addressed to itself, **`BLERouteForwardingPolicy`** in [[`bitchat/Services/BLE/BLERouteForwardingPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLERouteForwardingPolicy.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLERouteForwardingPolicy.swift) determines handling through priority-ordered conditions:

| Condition | Action | Source Lines |
|-----------|--------|--------------|
| `REQUEST_SYNC` packet type | Suppress flood relay | [L38-L44](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLERouteForwardingPolicy.swift#L38-L44) |
| Packet addressed to local peer | Suppress flood relay | [L46-L48](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLERouteForwardingPolicy.swift#L46-L48) |
| No route present in packet | Allow normal flood relay | [L50-L52](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLERouteForwardingPolicy.swift#L50-L52) |
| TTL ≤ 1 | Suppress flood relay (loop prevention) | [L54-L56](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLERouteForwardingPolicy.swift#L54-L56) |
| Valid route present | Forward to next hop (or destination if final hop) | [L62-L73](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLERouteForwardingPolicy.swift#L62-L73) |

Critical reliability feature: **if the calculated next hop is unreachable**, the policy falls back to flood-relay to guarantee delivery rather than silently dropping the packet.

### Relay Forwarding Implementation

```swift
func handleRelay(_ packet: BitchatPacket, from incomingPeer: PeerID) {
    let forwardPlan = BLERouteForwardingPolicy.plan(
        for: packet,
        localPeerID: localPeerID,
        localRoutingData: localPeerID.rawData,
        routingPeer: { data in MeshTopologyTracker.shared.peer(for: data) },
        isPeerConnected: { BLEService.shared.isConnected($0) }
    )
    
    switch forwardPlan {
    case .allowFloodRelay:
        flood(packet, excludedPeer: incomingPeer)
        
    case .suppressFloodRelay:
        // Process locally or drop per packet type rules
        processLocally(packet)
        
    case .forward(let forwardPacket, let nextHopPeer):
        guard let nextHop = nextHopPeer else {
            // Unreachable next hop: fallback to flood
            flood(packet, excludedPeer: incomingPeer)
            return
        }
        BLEService.shared.send(to: nextHop, packet: forwardPacket)
    }
}

```

## Security and Integrity

The existing **Ed25519 signature** covers the entire serialized packet including the source route field. Any modification to hop count, peer IDs, or order invalidates the signature, causing receiving peers to drop the packet before processing. This design prevents:

- Route manipulation attacks that might redirect traffic
- Hop count inflation for denial-of-service amplification
- Path truncation attacks

As noted in [[`docs/SOURCE_ROUTING.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md)](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md#L18-L25), the signature boundary encompasses all routing metadata, not just the payload.

## Fragmentation Handling

Large packets exceeding MTU are fragmented, but **all fragments inherit the v2 version and identical source route**. This ensures fragmented streams continue unicast forwarding rather than reverting to flooding mid-transmission. Per [[`docs/SOURCE_ROUTING.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md)](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md#L107-L113), fragment reassembly occurs at the destination using the same route path as the original packet.

## Key Implementation Files

| Component | File Path |
|-----------|-----------|
| Protocol specification and packet format | [[`docs/SOURCE_ROUTING.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md)](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md) |
| Origination policy with six gate checks | [[`bitchat/Services/BLE/BLESourceRouteOriginationPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLESourceRouteOriginationPolicy.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLESourceRouteOriginationPolicy.swift) |
| Relay forwarding decision logic | [[`bitchat/Services/BLE/BLERouteForwardingPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLERouteForwardingPolicy.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLERouteForwardingPolicy.swift) |
| Unit tests for origination gates | [[`bitchatTests/Services/BLESourceRouteOriginationPolicyTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/BLESourceRouteOriginationPolicyTests.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/BLESourceRouteOriginationPolicyTests.swift) |
| Unit tests for forwarding behavior | [[`bitchatTests/Services/BLERouteForwardingPolicyTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/BLERouteForwardingPolicyTests.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/BLERouteForwardingPolicyTests.swift) |

## Summary

- **Source routing in BitChat** operates exclusively through protocol v2 packets with the `HAS_ROUTE` flag set
- **Topology discovery** uses symmetric neighbor gossip via TLV `0x04` in identity announcements
- **Route origination** requires six policy gates to pass, falling back to flooding on any failure
- **Relay forwarding** follows explicit hop lists with TTL-based loop prevention and unreachable-hop fallback
- **Security** is maintained through end-to-end Ed25519 signatures covering all routing metadata
- **Fragmentation** preserves source routes across all packet segments

## Frequently Asked Questions

### What triggers source routing instead of flooding in BitChat?

Source routing activates when `BLESourceRouteOriginationPolicy` confirms six conditions: local packet authorship, single-peer destination, TTL greater than 1, indirect recipient connection, policy permission via `shouldAttemptRoute`, and successful route computation. If any condition fails, the packet uses legacy flooding. This conservative design prioritizes delivery reliability over optimization.

### How does BitChat prevent routing loops with source routes?

The `BLERouteForwardingPolicy` suppresses flood relay when TTL ≤ 1, preventing packets from circulating indefinitely. Additionally, each relay decrements TTL and forwards only to the explicit next hop. The source route itself is fixed at origination—relays cannot modify the path, eliminating dynamic loop formation through protocol design.

### Can an attacker modify the source route to redirect traffic?

No. The Ed25519 signature covers the entire packet including the 1-byte hop count and all 8-byte peer IDs in the route. Any bit-flipping in the route invalidates the signature, causing immediate packet rejection by the next receiver. The sender and recipient are omitted from the route field specifically to reduce attack surface against endpoint impersonation.

### What happens if an intermediate relay in the source route goes offline?

The `BLERouteForwardingPolicy` detects unreachable next hops through the `isPeerConnected` closure evaluation. When the designated next hop is unavailable, the policy automatically falls back to flood-relay behavior, broadcasting the packet to all connected peers except the incoming relay. This guarantees eventual delivery at the cost of temporarily increased bandwidth usage.