Differences Between BitChat BLE Packet Formats V1 and V2

BitChat V2 introduces an explicit version byte, TLV-encoded source routing, and a 64-bit capability field, while V1 uses implicit typing with flood-based delivery and lacks extensible metadata headers.

BitChat's BLE mesh transport layer evolved significantly between Version 1 (the original flood-based format) and Version 2 (which added deterministic routing and extensible metadata). Understanding these wire-format distinctions is essential for developers building compatible mesh nodes or debugging packet flows in the permissionless network.

Header Structure and Version Discrimination

The most immediate difference between V1 and V2 appears in the packet header itself, fundamentally changing how nodes parse incoming BLE frames.

Implicit vs Explicit Versioning

In V1, the first byte of every packet represented the message type directly, with no explicit version identifier. According to the codebase, legacy parsing logic in bitchat/Protocols/Packets.swift treats the initial octet as a type discriminator (currentVersion = 1 context), forcing backward compatibility to be inferred from payload structure alone.

V2 inserts a dedicated version byte at the head of every packet. As documented in docs/SOURCE_ROUTING.md, this upgrade allows receivers to inspect data[0] and branch to appropriate parsing logic before interpreting subsequent fields.

Header Size and Layout

The header expansion reflects the new architecture:

  • V1: 5-byte header (type + TTL + flags)
  • V2: 6-byte header (version + type + TTL + flags)

This additional byte enables the version-gated handling seen in bitchat/Services/BLE/BLEService.swift around line 3707, where the service checks requiringVersion: 2 before processing advanced features.

Source Routing and Topology Changes

V1 relied on network flooding or simple unicast, while V2 implements explicit pathing through the mesh.

Flood-Based Delivery in V1

V1 packets contain no routing information beyond destination and TTL. Every intermediate node floods the packet to all neighbors until the hop limit expires, creating redundant traffic and battery drain on dense networks.

TLV-Encoded Source Routes in V2

V2 introduces a Type-Length-Value (TLV) structure for source-route lists. When the R-S-R (Request Source Route) flag is set—bit mask 0b0000_0100 as defined in bitchat/Services/BLE/BLEService.swift at line 3701—the packet includes a TLV-encoded array of hop IDs that instruct each intermediate node exactly where to forward the frame next.

This eliminates flooding for routed messages and enables power-efficient unicast chains across the mesh.

Capability Advertisement and Feature Extensibility

V2 adds machine-readable feature negotiation through a capability bitfield, absent in V1's fixed metadata.

The 64-Bit Capability Bitset

Where V1 announce packets carried only sender ID and basic metadata, V2 announce packets include a peer-capability bitmask (uint64) and optional peer-ID rotation fields. As noted in docs/PEER-ID-ROTATION.md, this bitset reserves specific bits for features—bit 14 handles peer-ID rotation—allowing nodes to advertise support for stickers, Nostr double-ratchet, and other extensions without breaking older clients.

The bitchat/Services/BLE/BLEAnnounceHandlingPolicy.swift file (lines 32-35) demonstrates how nodes pre-flight check these capability bits before engaging in protocol upgrades.

Payload Handling and Fragmentation

The wire format changes directly impact maximum transmission unit (MTU) handling and message fragmentation strategies.

MTU Constraints in V1

V1 packets were constrained by standard BLE MTU limits (~20 bytes), forcing large messages to split into numerous tiny fragments. The BLEOutboundFragmentPlanner treated every fragment as a discrete packet, incurring overhead proportionate to the 5-byte header on each chunk.

Extended Fragment Sizes in V2

V2 supports up to 512-byte fragments, selectable via the packet version field. The fragment planner in bitchat/Services/BLE/BLEOutboundFragmentPlanner.swift (lines 6-91) uses fragmentVersion = 2 to determine chunk sizing, significantly reducing header overhead for bulk data like voice frames or encrypted attachments.

Security and Privacy Enhancements

V2 hardens the protocol against size-based fingerprinting and improves forward privacy.

Constant-Width Signing and Padding

V1 packets contained no padding, meaning every byte contributed to the signed hash and packet lengths varied with payload size. This created a side-channel for traffic analysis.

V2 mandates signed, constant-width padding. As stated in docs/BLE-ARCHITECTURE-V3.md, "padding is signed" and fixed-width, simplifying verification and preventing adversaries from inferring message types from packet dimensions.

Peer-ID Rotation Support

V2's capability bits enable dynamic peer-ID rotation, whereas V1 used static identifiers for the duration of the session. This privacy feature requires the new announce format introduced in V2 to distribute rotated keys without disrupting the mesh graph.

Code Examples

Constructing a V2 Packet in Swift

The following pattern from BLEOutboundFragmentPlanner.swift demonstrates assembling a V2 packet with source routing:

import Bitchat

let payload = Data("Hello, mesh!".utf8)
let version: UInt8 = 2                     // V2 format identifier
let type = MessageType.chatMessage.rawValue // 0x01
let ttl: UInt8 = 5
let rsrFlag: UInt8 = 0b0000_0100           // Request Source Route flag

var packet = Data()
packet.append(version)                     // Explicit version byte
packet.append(type)
packet.append(ttl)
packet.append(rsrFlag)

// Optional source-route TLV (hop list)
let sourceRoute: [Data] = [peerA, peerB]
packet.append(contentsOf: TLV.encode(id: .sourceRoute, value: sourceRoute))

packet.append(payload)
BLEService.shared.send(packet)

Parsing Version-Tagged Packets

Receivers use the version byte to branch between legacy and modern parsers:

func parseBLEPacket(_ data: Data) throws -> BLEPacket {
    guard let version = data.first, version == 2 else {
        // Fallback to V1 parser: first byte is message type
        return try BLEPacketV1(data)
    }
    
    let type   = data[1]
    let ttl    = data[2]
    let flags  = data[3]
    
    var cursor = 4
    var sourceRoute: [Data] = []
    
    if flags & 0b0000_0100 != 0 {               // R-S-R flag check
        let tlv = try TLV.decode(from: data, at: &cursor)
        if tlv.id == .sourceRoute { 
            sourceRoute = tlv.value 
        }
    }
    
    let payload = data.suffix(from: cursor)
    return BLEPacket(
        version: version,
        type: type,
        ttl: ttl,
        flags: flags,
        sourceRoute: sourceRoute,
        payload: payload
    )
}

Summary

  • V1 uses a 5-byte header with no version byte, supports only flooding/unicast, lacks capability advertisement, and is limited to ~20-byte BLE MTU payloads.
  • V2 adds a 6-byte header with explicit versioning, TLV-encoded source routing, a 64-bit capability bitfield (enabling peer-ID rotation), R-S-R flags, and 512-byte fragment support.
  • Interoperability: V2 nodes can parse V1 packets by detecting the absence of the version byte (value ≤ 1), but V1 nodes cannot interpret V2's extended headers or source-route TLVs.
  • Implementation: Key logic resides in BLEService.swift (version gating), BLEOutboundFragmentPlanner.swift (fragment sizing), and BLEAnnounceHandlingPolicy.swift (capability checks).

Frequently Asked Questions

Can Version 1 nodes communicate with Version 2 nodes in BitChat?

Yes, but unidirectionally. V2 implementations inspect the first byte of every received packet; if the value is 1 or less, they fall back to legacy V1 parsing logic. However, V1 nodes receiving V2 packets will misinterpret the version byte as a message type, leading to parsing failures unless they ignore unknown types.

Why did BitChat move from flooding to source routing in V2?

Flooding creates excessive battery drain and airtime contention in dense meshes. The V2 TLV-encoded source-route list (triggered by the R-S-R flag) allows senders to specify exact forwarding paths, eliminating redundant transmissions and enabling efficient multi-hop unicast for large payloads like voice frames.

How does the capability bitfield affect packet compatibility?

The 64-bit capability field in V2 announce packets (located in BLEAnnounceHandlingPolicy.swift) uses reserved bits to advertise features like peer-ID rotation and Nostr support. Older V1 nodes ignore these bytes as trailing padding, preserving backward compatibility while allowing V2 nodes to negotiate advanced features before exchanging sensitive data.

What is the maximum practical payload size difference between V1 and V2?

V1 fragments are constrained by the standard BLE MTU of approximately 20 bytes per packet. V2 supports fragments up to 512 bytes when the fragment planner detects version = 2, reducing the number of BLE transactions required for large messages by roughly 96% compared to V1 fragmentation.

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 →