What Media Transfer Protocol Does BitChat Use for Private File Sharing?

BitChat uses a custom "Mesh File Transferring" protocol backed by Noise encryption for private file sharing, implemented through the MeshFileTransferring protocol and coordinated by ChatMediaTransferCoordinator.

This article examines the BitChat iOS application's approach to secure media transfers over its decentralized mesh network. The protocol combines Bluetooth Low Energy (BLE) mesh transport with the Noise cryptographic framework to enable private image, voice, and file exchanges without centralized servers.


Mesh File Transferring Protocol Overview

BitChat's private file sharing operates through the MeshFileTransferring protocol defined in bitchat/Services/MeshTransportCapabilities.swift. This protocol abstracts the underlying mesh transport layer, providing a clean interface for the chat UI to initiate and manage file transfers.

Core Protocol Methods

The MeshFileTransferring protocol declares three essential methods (lines 32-41):

func privateMediaSendPolicy(to peerID: PeerID) -> PrivateMediaSendPolicy
func sendFilePrivate(_ packet: BitchatFilePacket,
                     to peerID: PeerID,
                     transferId: String,
                     allowLegacyFallback: Bool)
func sendFileBroadcast(_ packet: BitchatFilePacket, transferId: String)

The privateMediaSendPolicy method drives the transfer flow. It returns one of four policies—.encrypted, .legacyRequiresConsent, .awaitingCapabilityProof, or .blockedDowngrade—determining whether the coordinator can proceed with an encrypted transfer or must use fallback mechanisms.


ChatMediaTransferCoordinator: The Orchestration Layer

Private media transfers are coordinated by ChatMediaTransferCoordinator, located in bitchat/ViewModels/ChatMediaTransferCoordinator.swift. This class bridges the UI layer (ChatViewModel) with the mesh transport.

Policy Lookup and Transfer Initiation

Before sending, the coordinator queries the mesh transport for the target peer's capabilities (lines 59-61):

context.privateMediaSendPolicy(to: peerID)   // → PrivateMediaSendPolicy

Based on this policy, the coordinator chooses between:

  • Encrypted path: Direct Noise-encrypted transfer via sendFilePrivate
  • Legacy consent path: User-prompted approval for unencrypted fallback
  • Deferred path: Wait for capability proof from an unknown peer

Sending a File

Here's the complete flow when a user shares an image:

// 1️⃣ Initiate a private-media send (image, voice note, or generic file)
chatViewModel.mediaTransferCoordinator.sendImage(from: imageURL)

// Inside ChatMediaTransferCoordinator.swift
func sendImage(from sourceURL: URL) {
    guard context.canSendMediaInCurrentContext else { … }

    let targetPeer = context.selectedPrivateChatPeer
    let packet = try prepareImagePacket(sourceURL: URL)          // encodes the file
    let transferId = makeTransferID(messageID: message.id)

    // Register the transfer for cancellation / retry handling
    registerTransfer(transferId: transferId, messageID: message.id)

    // Choose private or broadcast path based on the current peer
    if let peerID = targetPeer {
        beginPrivateMediaSend(packet, to: peerID,
                              transferId: transferId,
                              messageID: message.id)
    } else {
        context.sendFileBroadcast(packet, transferId: transferId)
    }
}

Noise Protocol Encryption

The Noise protocol (bitchat/Noise/NoiseProtocol.swift) provides the cryptographic layer for private media transfers. When privateMediaSendPolicy returns .encrypted, the coordinator wraps the BitchatFilePacket in a Noise-encrypted envelope before transmission.

Transport Implementation

The mesh transport implements MeshFileTransferring with Noise integration:

// 2️⃣ Private-media policy lookup (inside the Mesh transport)
extension SomeMeshService: MeshFileTransferring {
    func privateMediaSendPolicy(to peerID: PeerID) -> PrivateMediaSendPolicy {
        // Examine peer capabilities; return .encrypted, .legacyRequiresConsent,
        // .awaitingCapabilityProof, or .blockedDowngrade
        …
    }

    func sendFilePrivate(_ packet: BitchatFilePacket,
                         to peerID: PeerID,
                         transferId: String,
                         allowLegacyFallback: Bool) {
        // Wrap the packet in a Noise-encrypted envelope and forward
        // it over the BLE mesh.
        …
    }
}

Reception and Decryption

Incoming packets pass through the Noise layer for validation and decryption:

// 3️⃣ Handling a received private-media packet (Noise decryption)
func handleIncomingPacket(_ packet: BitchatFilePacket) {
    // The Noise layer validates the envelope and extracts the payload
    let decrypted = try noiseSession.decrypt(packet)
    // Store the file and create the UI bubble
    …
}

Not all peers support encrypted media. The coordinator handles this through the ChatMediaTransferContext protocol, which ChatViewModel implements. When privateMediaSendPolicy indicates .legacyRequiresConsent, the coordinator invokes:

  • requestLegacyPrivateMediaConsent() — prompts the user for approval
  • cancelLegacyPrivateMediaConsent() — aborts the fallback transfer

This design maintains security by default while preserving interoperability with older or constrained peers.


Key Implementation Files

Component File Path Purpose
Coordinator bitchat/ViewModels/ChatMediaTransferCoordinator.swift Orchestrates sends, retries, and UI updates for private media
Context Protocol bitchat/ViewModels/ChatMediaTransferCoordinator.swift (protocol ChatMediaTransferContext) Minimal interface required from ChatViewModel
Mesh Transfer Protocol bitchat/Services/MeshTransportCapabilities.swift (protocol MeshFileTransferring) Declares the private-media API used by the coordinator
Encryption Layer bitchat/Noise/NoiseProtocol.swift Implements the Noise protocol that secures private-media packets
Message Packet bitchat/Protocols/BitchatFilePacket.swift Binary representation of a file packet sent over the mesh

Summary

  • BitChat's media transfer protocol is custom-built, not a standard protocol like HTTP, FTP, or BitTorrent.
  • The MeshFileTransferring protocol defines the API for private and broadcast file transfers over BLE mesh.
  • ChatMediaTransferCoordinator manages transfer lifecycle, policy resolution, and UI coordination.
  • Noise protocol encryption secures payloads when both peers support encrypted media.
  • Legacy consent flow provides backward compatibility with non-encrypted peers, requiring explicit user approval.

Frequently Asked Questions

Does BitChat use standard protocols like WebRTC or MQTT for file sharing?

No. BitChat implements a proprietary mesh-based protocol specific to its decentralized architecture. The MeshFileTransferring protocol operates over Bluetooth Low Energy mesh rather than IP-based protocols like WebRTC or MQTT.

What happens if the recipient doesn't support encrypted media transfers?

The coordinator enters a legacy consent flow. It calls requestLegacyPrivateMediaConsent() to prompt the user for explicit approval before falling back to unencrypted transfer. The policy .blockedDowngrade prevents any transfer if the administrator forbids unencrypted media.

How does the coordinator track ongoing transfers?

Each transfer receives a unique transferId generated via makeTransferID(messageID:). The coordinator registers this ID using registerTransfer(transferId:messageID:), enabling cancellation, retry logic, and progress updates bound to specific chat messages.

Is the Noise protocol implementation custom or from a library?

The analysis references bitchat/Noise/NoiseProtocol.swift as the encryption layer. While the file structure suggests BitChat maintains its own Noise implementation, the protocol itself follows the Noise Protocol Framework specification—a well-established cryptographic standard used by WireGuard and other modern secure protocols.

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 →