BitChat Version 2 Packet Structure: Binary Wire Format Explained

A Version 2 BitChat packet consists of a 16-byte fixed header followed by variable-length sections including an 8-byte sender ID, optional recipient ID, optional routing hop list, and optional 64-byte Ed25519 signature.

The permissionlesstech/bitchat repository implements a compact binary protocol for decentralized mesh messaging. Version 2 expands the original V1 format to support 32-bit payload lengths and multi-hop routing information, as defined in BinaryProtocol.swift within the BitFoundation package.

Fixed Header Layout (16 Bytes)

Every V2 packet begins with a fixed 16-byte header structured in network byte order (big-endian). According to the headerSize function in BinaryProtocol.swift lines 11‑15, this header is mandatory and version-dependent.

Offset Size Field Description
0 1 byte Version Must be 2 for V2 packets.
1 1 byte Type Application-defined message type.
2 1 byte TTL Hop-limit counter for mesh routing.
3‑10 8 bytes Timestamp Unix epoch time as UInt64 (big-endian).
11 1 byte Flags Bitmask indicating optional fields.
12‑15 4 bytes PayloadLength 32-bit unsigned integer defining payload size.

Source: Header field definitions and size constants are documented in BinaryProtocol.swift lines 23‑30【/cache/repos/github.com/permissionlesstech/bitchat/main/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift#L23-L30】.

Flags Byte and Optional Fields

Byte 11 of the header acts as a bitmask controlling optional packet features. The BinaryProtocol encoder populates these flags during serialization (lines 89‑96)【/cache/repos/github.com/permissionlesstech/bitchat/main/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift#L89-L96】.

Bit Flag Description
0 hasRecipient An 8-byte recipient ID follows the sender ID.
1 hasSignature A 64-byte Ed25519 signature is appended after the payload.
2 isCompressed Payload is zlib compressed; includes 4-byte original-size preamble.
3 hasRoute Routing list is present (V2 only).
4 isRSR Re-send request flag.
5‑7 Reserved Future use.

Variable-Length Payload Sections

Following the fixed header, the packet contains ordered variable sections. The decoder logic in BinaryProtocol.swift parses these sequentially (lines 43‑55 for flags, lines 119‑150 for route handling)【/cache/repos/github.com/permissionlesstech/bitchat/main/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift#L43-L55】【/cache/repos/github.com/permissionlesstech/bitchat/main/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift#L119-L150】.

SenderID (8 bytes)
Always present immediately after the header.

RecipientID (8 bytes)
Present only if the hasRecipient flag is set.

Route (1 + N×8 bytes)
Present only if the hasRoute flag is set (V2 feature). Structure:

  • 1 byte: Hop count N
  • N × 8 bytes: Hop identifiers (sender-ID sized)

Compression Preamble (4 bytes)
If isCompressed is set, a 4-byte unsigned integer indicating the uncompressed payload size precedes the actual payload data (encoder logic lines 150‑173)【/cache/repos/github.com/permissionlesstech/bitchat/main/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift#L150-L173】.

Payload (variable)
Raw message data of length PayloadLength bytes. All multi-byte values use network byte order (big-endian)【/cache/repos/github.com/permissionlesstech/bitchat/main/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift#L98-L99】.

Signature (64 bytes)
Ed25519 signature appended only if hasSignature is set.

Encoding Version 2 Packets

The BitchatPacket struct (defined in BitchatPacket.swift lines 11‑38)【/cache/repos/github.com/permissionlesstech/bitchat/main/localPackages/BitFoundation/Sources/BitFoundation/BitchatPacket.swift#L11-L38】 provides the toBinaryData method for encoding. Setting version: 2 triggers the 4-byte payload length field and enables the hasRoute flag.

import Foundation
import BitFoundation

let sender = Data([0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08])
let recipient = Data([0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17])
let payload = "Hello, BitChat!".data(using: .utf8)!

let packet = BitchatPacket(
    type: 0x01,
    senderID: sender,
    recipientID: recipient,
    timestamp: UInt64(Date().timeIntervalSince1970 * 1_000_000),
    payload: payload,
    signature: nil,
    ttl: 5,
    version: 2,                              // V2 header format
    route: [Data([0xAA,0xBB,0xCC,0xDD,0xEE,0xFF,0x00,0x11])],
    isRSR: false
)

guard let binary = packet.toBinaryData(padding: false) else {
    fatalError("Encoding failed")
}
print("Encoded V2 packet size:", binary.count) // Header (16) + Sender (8) + Recipient (8) + Route (1+8) + Payload (15)

Decoding Received Packets

The static from(_:) method parses the binary data according to the version detected in the first byte.

if let decoded = BitchatPacket.from(binary) {
    print("Version:", decoded.version)               // → 2
    print("Has route?:", decoded.route != nil)       // → true
    print("Payload:", String(data: decoded.payload, encoding: .utf8)!)
}

The decoder reads the 4-byte PayloadLength field and interprets the hasRoute flag to extract the routing list accordingly.

Summary

  • Version 2 uses a 16-byte fixed header with a 4-byte PayloadLength field (UInt32), unlike V1's 2-byte length.
  • The Flags byte controls five optional features: recipient ID, signature, compression, routing list, and re-send request.
  • Routing support (bit 3) is exclusive to V2, storing hop count and hop IDs after the sender/recipient fields.
  • All multi-byte integers are big-endian (network byte order).
  • The core implementation resides in BinaryProtocol.swift and BitchatPacket.swift within the BitFoundation package.

Frequently Asked Questions

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

Version 1 uses a 14-byte header with a 2-byte payload length field, limiting payloads to 65 KB. Version 2 expands this to a 16-byte header with a 4-byte length field (lines 12‑15)【/cache/repos/github.com/permissionlesstech/bitchat/main/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift#L12-L15】, supporting larger payloads and adding the hasRoute flag for mesh routing.

How large can a Version 2 payload be?

The PayloadLength field is a 32-bit unsigned integer, allowing payloads up to 4 GB in theory. However, practical limits depend on underlying transport MTU and memory constraints in the BitChat implementation.

How does the routing list structure work?

When the hasRoute flag (bit 3) is set, the encoder first writes a single byte indicating the number of hops N, followed by N 8-byte sender IDs representing the path. This allows intermediate nodes to forward packets toward the destination without flooding the entire mesh (encoder logic lines 119‑150)【/cache/repos/github.com/permissionlesstech/bitchat/main/localPackages/BitFoundation/Sources/BitFoundation/BinaryProtocol.swift#L119-L150】.

Is the signature field mandatory in Version 2?

No. The 64-byte signature is optional and controlled by the hasSignature flag (bit 1). When present, it is appended after the payload bytes and covers the entire packet content for authentication and integrity verification.

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 →