# How Bitchat's File Transfer Protocol Manages Private Media Transfers Over BLE

> Discover how the bitchat file transfer protocol secures private media transfers over BLE using a three-layer architecture for authenticated storage and reliable delivery.

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

---

**The bitchat protocol uses a three-layer architecture—comprising BLEFileTransferPolicy for routing, BLEFileTransferHandler for authenticated storage, and BLEPrivateMediaReceiptStore for durable exactly-once delivery—to securely manage private media transfers over Bluetooth Low Energy.**

Bitchat is an open-source, offline-first messaging stack that implements a specialized file transfer protocol for private media transfers over BLE. The system guarantees cryptographic authenticity, storage quota enforcement, and persistent receipt tracking to ensure that sensitive media arrives exactly once even across app restarts or intermittent connectivity.

## Core Architecture of the BLE File Transfer Stack

The protocol delegates responsibility across three tightly-coupled components:

- **BLEFileTransferPolicy** – Determines routing logic, filters self-echoes, and distinguishes private messages from broadcasts. Implemented in [[`bitchat/Services/BLE/BLEFileTransferPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFileTransferPolicy.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFileTransferPolicy.swift#L9-L28).

- **BLEFileTransferHandler** – The central coordinator that authenticates senders, enforces quotas, writes files, and manages durable receipt commits. Located at [[`bitchat/Services/BLE/BLEFileTransferHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFileTransferHandler.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFileTransferHandler.swift#L19-L88).

- **BLEPrivateMediaReceiptStore** – Maintains a persistent ledger mapping stable message IDs to file URLs, enabling exactly-once delivery guarantees. Found in [[`bitchat/Services/BLE/BLEPrivateMediaReceiptStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEPrivateMediaReceiptStore.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEPrivateMediaReceiptStore.swift#L127-L215) alongside the `PrivateMediaMessageIdentity` generator in [[`BitchatFilePacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatFilePacket.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/BitchatFilePacket.swift#L168).

## Routing and Self-Echo Prevention

When a BLE packet arrives, `BLEFileTransferHandler.handle(_:from:)` immediately consults the policy layer to prevent processing self-generated echoes. If the packet originated from the local peer and carries a non-zero TTL, the handler drops it:

```swift
if BLEFileTransferPolicy.isSelfEcho(packet: packet,
                                    from: peerID,
                                    localPeerID: localPeerID) {
    return false                // ← drop self-echo
}

```

This guard runs at lines 47‑49 of [[`BLEFileTransferHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEFileTransferHandler.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFileTransferHandler.swift#L47-L49). Only packets where `deliveryPlan.isPrivateMessage == true` proceed to authentication; broadcasts or irrelevant traffic is ignored.

## Authentication and Sender Verification

Before writing any payload, the handler validates the sender’s cryptographic identity. The `authenticatedRawSenderNickname` function verifies packet signatures (or falls back to signed display names) to ensure only authentic peers can trigger file writes:

```swift
guard let senderNickname = authenticatedRawSenderNickname(
          packet: packet,
          from: peerID,
          peers: peersSnapshot,
          env: env) else {
    // Drop unauthenticated packets
    return false
}

```

See implementation at lines 37‑45. This authentication step is mandatory for both public and private media paths.

## The Private Media Transfer Workflow

Private media handling diverges from public file broadcasts through four specialized stages:

### 1. Stable ID Generation

For private transfers, the protocol generates a deterministic `messageID` using `PrivateMediaMessageIdentity.stableID`, which hashes the sender, recipient, and file packet metadata. This ID serves as the canonical reference for deduplication and receipt tracking:

```swift
let messageID = usesDurableReceipts
    ? PrivateMediaMessageIdentity.stableID(
          for: filePacket,
          senderPeerID: peerID,
          recipientPeerID: localPeerID)
    : nil

```

Reference implementation at lines 44‑50 in [[`BitchatFilePacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatFilePacket.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/BitchatFilePacket.swift#L168).

### 2. Deduplication and Receipt Reservation

The `PrivateMediaArrivalDeduplicator` coalesces concurrent arrivals of the same ID and queries the receipt store for current state (`accepted`, `tombstoned`, or `unavailable`):

```swift
if let messageID {
    switch privateMediaArrivals.reserve(messageID,
          receiptState: { env.privateMediaReceiptState(messageID) }) {
        case .reserved:   break                 // first arrival
        case .pending:    return true           // coalesce retries
        case .accepted(let existingFile): …      // duplicate handling
        case .tombstoned: …                      // deleted media
        case .unavailable: …                     // receipt store offline
    }
}

```

This reservation logic appears at lines 44‑55 and 71‑85.

### 3. Atomic Commit and Storage

After persisting the file bytes, the handler attempts a durable commit. If `commitPrivateMediaFile` fails, the protocol rolls back the write to prevent orphaned duplicates:

```swift
if let messageID,
   !env.commitPrivateMediaFile(messageID, destination) {
    // Commit failed → roll back the file
    env.removeIncomingFile(destination)
    return false
}

```

See the commit and cleanup logic at lines 34‑40.

### 4. Delivery Verification

Final UI delivery occurs only after verifying the receipt store still acknowledges the file. The `deliverStableMessage` closure compares the expected URL against the resolved URL in the persistent ledger:

```swift
env.deliverMessage(message,
    {
        guard case .accepted(let resolvedURL) =
                env.privateMediaReceiptState(messageID) else { return false }
        return resolvedURL.standardizedFileURL == expectedURL.standardizedFileURL
    },
    { env.acknowledgePrivateMedia(messageID, peerID) },
    { _ in env.finishIncomingFileDelivery(expectedURL) })

```

This guard executes at lines 90‑106, ensuring the UI never displays files that failed durable persistence.

## Storage Quota Enforcement

To prevent malicious peers from exhausting disk space, the handler enforces per-device quotas (specification **BCH‑01‑002**) before writing payload bytes:

```swift
env.enforceStorageQuota(filePacket.content.count)

```

See line 21 of [[`BLEFileTransferHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEFileTransferHandler.swift)](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFileTransferHandler.swift#L21). Exceeded quotas result in immediate rejection without affecting downstream gossip.

## Persistence and Recovery Across Restarts

The **BLEPrivateMediaReceiptStore** maintains a persistent mapping of `messageID → file URL` (lines 127‑215). On app relaunch, any in-flight private media already committed are re-assembled via this store, allowing senders to safely retry until the durable receipt is recorded. The handler finalizes each reservation with `privateMediaArrivals.finish(messageID)` (lines 16‑19) to release memory while preserving the persistent state.

## Integration with the BLE Service

`BLEFileTransferHandler` is instantiated lazily within `BLEService` at line 457:

```swift
private lazy var fileTransferHandler =
    BLEFileTransferHandler(environment: makeFileTransferHandlerEnvironment())

```

All heavyweight operations—signature verification, quota checks, and file I/O—execute off the CoreBluetooth queue, making the handler **queue-agnostic** and fully testable. The surrounding BLE service merely forwards packets and provides environment closures.

## Summary

- **Three-layer separation**: Routing policy, transfer handler, and receipt store collaborate to isolate concerns.
- **Exactly-once delivery**: Deterministic `stableID` generation and durable receipt commits prevent duplicate private media.
- **Cryptographic authentication**: `authenticatedRawSenderNickname` validates every packet before storage.
- **Storage protection**: Quota enforcement (BCH‑01‑002) and atomic commit/rollback prevent resource exhaustion and orphaned files.
- **Crash resilience**: Persistent receipt state allows recovery and idempotent retries across app restarts.

## Frequently Asked Questions

### How does bitchat prevent duplicate private media transfers?

The protocol generates a deterministic `messageID` using `PrivateMediaMessageIdentity.stableID` and routes it through `PrivateMediaArrivalDeduplicator.reserve`. This ensures concurrent arrivals of identical content coalesce into a single write, while the `BLEPrivateMediaReceiptStore` persists the `accepted` state to block replays after delivery.

### What happens if the app restarts during a private media transfer?

Because `BLEPrivateMediaReceiptStore` maintains a persistent ledger mapping message IDs to file URLs, committed files survive restarts. On relaunch, the store re-hydrates the receipt state, allowing the protocol to recognize already-received media and reject retries without re-writing files.

### How does the protocol protect against storage exhaustion attacks?

Before allocating disk space, `BLEFileTransferHandler` invokes `enforceStorageQuota` (BCH‑01‑002) to verify the incoming payload size against per-device limits. If the quota is exceeded, the handler drops the packet immediately without triggering downstream storage operations.

### What distinguishes private media handling from public file broadcasts?

Private media activates `usesDurableReceipts`, triggering stable ID generation, deduplication via `PrivateMediaArrivalDeduplicator`, and atomic commit semantics with `commitPrivateMediaFile`. Public files bypass these steps, proceeding directly to storage without the persistent receipt guarantees or sender-recipient binding enforced for private transfers.