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
…
}
Legacy Fallback and Consent Flow
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 approvalcancelLegacyPrivateMediaConsent()— 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
MeshFileTransferringprotocol defines the API for private and broadcast file transfers over BLE mesh. ChatMediaTransferCoordinatormanages 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →