# BitChat Version 2 Packet Structure: Binary Wire Format Explained

> Explore the BitChat Version 2 packet structure. Understand the binary wire format including fixed header sender ID recipient ID hop list and Ed25519 signature.

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

---

**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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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.

```swift
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.

```swift
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`](https://github.com/permissionlesstech/bitchat/blob/main/BinaryProtocol.swift) and [`BitchatPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/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.