# BitChat Binary Protocol: Complete Guide to Message Encoding and Fragmentation

> Master the BitChat binary protocol. Learn about message encoding, fragmentation into BLE chunks, and Version 2 requirements for efficient, scalable communication.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: api-reference
- Published: 2026-08-02

---

**The BitChat binary protocol uses a fixed 16-26 byte header with optional fields, supports packets up to 1MiB through fragmentation into BLE-sized chunks, and requires Version 2 for all fragments to preserve source routing.**

Every message in the BitChat network travels as a compact binary packet designed for low-bandwidth BLE mesh networks. The protocol balances header efficiency with extensibility, using explicit versioning and optional fields to keep small packets small while supporting复杂 routing and encryption features.

## Protocol Header Structure

The packet header occupies **16 bytes minimum** and **26 bytes maximum** for standard packets, with additional bytes for version-specific extensions.

| Field | Size | Description |
|-------|------|-------------|
| **Version** | 1 byte | `0x01` for v1, `0x02` for v2 |
| **Type** | 1 byte | Packet class: announce (`0x01`), fragmentStart (`0x2C`), etc. |
| **TTL** | 1 byte | Starts at 7, decremented by each relay |
| **Timestamp** | 4 bytes | Unix-epoch seconds (big-endian) |
| **Flags** | 1 byte | Bitfield: encryption, signature presence, etc. |
| **Sender ID** | 8 bytes | First 8 bytes of SHA-256(Noise static key) |
| **Recipient ID** | 8 bytes | Optional; omitted for broadcast packets |
| **Payload** | variable | Data, encrypted, or signed content |
| **Signature** | 64 bytes | Optional; Ed25519 covering all bytes **except TTL** |

The signature deliberately excludes the TTL byte so relays can decrement it without invalidating cryptographic verification. This design choice is documented in the white-paper's [*Packet Format* section](https://github.com/permissionlesstech/bitchat/blob/main/WHITEPAPER.md#L45-L48).

Version 2 packets may include an **explicit source route** immediately after the flags field—the only in-band routing mechanism in the protocol.

## Padding Rules

Padding follows PKCS#7 with strict limits:

- **Padded types**: `noiseEncrypted` and `noiseHandshake` only
- **Bucket sizes**: 256, 512, 1024, or 2048 bytes
- **Single-byte length field**: Maximum 255 padding bytes
- **Overflow behavior**: Packets requiring >255 bytes padding transmit **unpadded**

All other packet types—public messages, announcements, fragments, files, voice frames—emit at natural length. This preserves bandwidth for the most common traffic types.

## Packet Fragmentation for BLE MTU

When payloads exceed the **469-byte BLE link MTU**, the router fragments them into independently routable packets.

### Fragment Header Fields

| Field | Size | Purpose |
|-------|------|---------|
| **Fragment ID** | 8 bytes | Random group identifier |
| **Index / Total** | 2 bytes (1+1) | Zero-based position and count |
| **Inherited header** | variable | Full parent header (version, type, TTL, timestamp, flags, IDs, route) |
| **Payload portion** | variable | MTU-fitted chunk |

### Fragmentation Requirements

All fragments **must use Version 2**, regardless of the original packet's version. This requirement exists because:

- Source routes are **v2-only features**
- Fragment reassembly without routing information forces fallback to flooding
- Consistent routing preserves bandwidth across the mesh

As implemented 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#L109-L112), the **identical Route field** must appear on every fragment.

### Reassembly Limits

The BLE mesh layer enforces strict resource boundaries:

- **128 concurrent assemblies** per node
- **1 MiB total size cap** per reassembly
- **30-second timeout** for fragment arrival

These limits prevent memory exhaustion and denial-of-service through partial transmissions.

## Swift Implementation Examples

### Building a Version 2 Header

```swift
func makeHeader(
    type: UInt8,
    ttl: UInt8,
    timestamp: UInt32,
    flags: UInt8,
    senderID: Data,
    recipientID: Data? = nil,
    route: [UInt8]? = nil
) -> Data {
    var header = Data()
    header.append(0x02)                 // version 2
    header.append(type)                 // packet type
    header.append(ttl)                  // TTL (modified by relays)
    header.append(contentsOf: timestamp.bigEndianBytes) // 4 bytes
    header.append(flags)                // flags bitfield
    header.append(senderID)             // 8-byte sender ID
    if let rID = recipientID { header.append(rID) }
    if let r = route { header.append(contentsOf: r) }
    return header
}

```

### Fragmenting Oversized Payloads

```swift
func fragmentPayload(
    header: Data,
    payload: Data,
    mtu: Int = 469
) -> [Data] {
    let fragmentOverhead = 8 + 2        // ID + index/total
    let maxFragmentPayload = mtu - header.count - fragmentOverhead
    
    guard payload.count > maxFragmentPayload else {
        return [header + payload]       // fits without fragmentation
    }

    let fragmentID = Data(UUID().uuidString.prefix(8).utf8)
    var fragments: [Data] = []
    var offset = 0
    var index: UInt8 = 0
    let total = UInt8((payload.count + maxFragmentPayload - 1) / maxFragmentPayload)

    while offset < payload.count {
        let chunkSize = min(maxFragmentPayload, payload.count - offset)
        var fragment = Data()
        fragment.append(fragmentID)     // 8-byte group ID
        fragment.append(index)          // position
        fragment.append(total)          // total count
        fragment.append(header)         // full inherited header
        fragment.append(payload.subdata(in: offset..<offset+chunkSize))
        fragments.append(fragment)
        offset += chunkSize
        index += 1
    }
    return fragments
}

```

## Key Source Files

| File | Relevance |
|------|-----------|
| [`WHITEPAPER.md`](https://github.com/permissionlesstech/bitchat/blob/main/WHITEPAPER.md) | [Packet Format](https://github.com/permissionlesstech/bitchat/blob/main/WHITEPAPER.md) and [Fragmentation](https://github.com/permissionlesstech/bitchat/blob/main/WHITEPAPER.md#L66-L68) sections define the binary layout |
| [`docs/SOURCE_ROUTING.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/SOURCE_ROUTING.md) | Fragment inheritance of v2 and route fields |
| [`docs/ARCHITECTURE_V2.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/ARCHITECTURE_V2.md) | BLE mesh layer context and reassembly buffer |
| [`bitchatTests/ChatViewModelTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/ChatViewModelTests.swift) | Header formatting validation |
| [`bitchat/Views/Media/MediaMessageView.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Views/Media/MediaMessageView.swift) | UI consumption of encoded headers |

## Summary

- **BitChat binary protocol** uses a compact, extensible header with versioned fields and optional components
- **Signature excludes TTL** to allow relay modification without cryptographic re-signing
- **Padding applies selectively** to encrypted traffic only, with single-byte limits
- **Fragmentation requires Version 2** and identical source routes on all fragments
- **Reassembly limits** (128 concurrent, 1MiB max, 30s timeout) protect edge devices

## Frequently Asked Questions

### What is the minimum and maximum BitChat packet header size?

The minimum header is **16 bytes** (version, type, TTL, timestamp, flags, sender ID, no recipient). The maximum standard header reaches **26 bytes** with recipient ID and source route. Signatures add 64 bytes optionally.

### Why does the BitChat protocol signature exclude the TTL field?

Relays decrement TTL on every hop. Excluding this byte from the Ed25519 signature allows modification without invalidating the cryptographic proof. This trade-off accepts that TTL is unauthenticated but prevents per-hop re-signing overhead.

### How does BitChat handle packets larger than the BLE MTU?

Packets exceeding **469 bytes** are fragmented into independent sub-packets. Each fragment carries an 8-byte group ID, index/total counters, and a complete copy of the parent header. Receivers buffer fragments for up to 30 seconds with 1MiB total limits.

### What happens if fragment reassembly exceeds the 1MiB limit?

The assembly aborts immediately. This protects nodes from memory exhaustion attacks. Senders must keep payload sizes below this threshold or implement application-level segmentation outside the BitChat protocol layer.