# How Private Media Is Stored and Encrypted in BitChat: A Noise Protocol Deep Dive

> Discover how BitChat encrypts private media using Noise XX handshakes and ChaCha20-Poly1305. Learn about secure storage, quotas, and expiration policies.

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

---

**BitChat encrypts private media using Noise XX handshakes with X25519 key agreement and ChaCha20-Poly1305 authenticated encryption, storing only ciphertext in the Application Support directory with automatic 100 MB quotas and seven-day expiration policies.**

BitChat, the open-source mesh messaging application from the permissionlesstech/bitchat repository, treats images, voice notes, and files as end-to-end encrypted payloads that remain opaque to intermediate relays. Understanding how private media is stored and encrypted in BitChat requires examining the interplay between the Noise cryptographic protocol, BLE capability negotiation, and iOS sandboxed storage policies.

## End-to-End Encryption Architecture

BitChat wraps all private-media packets in a **Noise XX** handshake envelope. The implementation utilizes **X25519** for elliptic-curve key agreement, **ChaCha20-Poly1305** for authenticated encryption, and **SHA-256** for hashing, as documented in the project's privacy assessment.

When a peer advertises the *private-media* capability (bit 8), the application transmits media as an XChaCha20-Poly1305-encrypted `noiseEncrypted` payload. This ciphertext is completely opaque to mesh nodes and BLE relays; only the intended recipient possessing the correct session keys can decrypt the content after completing the Noise handshake.

## Capability Verification and Session Management

Before transmitting encrypted content, BitChat verifies that the remote peer actually supports private media reception. The **`BLEPrivateMediaSessionStore`** class, located in [`bitchat/Services/BLE/BLEPrivateMediaSessionStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEPrivateMediaSessionStore.swift), manages per-peer state including current Noise generations, advertised capabilities, and pending policy-resolution waiters.

The store enforces strict timeout watchdogs for capability-proof exchanges, ensuring that stale or forged proofs cannot authorize media transmission. According to lines 15–20 of [`BLEPrivateMediaSessionStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEPrivateMediaSessionStore.swift), these timeouts prevent ambiguous session states from lingering beyond policy limits.

### Receipt-Aware Capability Checking

The store distinguishes between basic private media (bit 8) and durable receipts (bit 9). When an application requests receipt generation, the method `provenGeneration(for:requireReceipts:)` validates that the peer proved both capabilities before authorizing transmission. As implemented in lines 68–77 of [`BLEPrivateMediaSessionStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEPrivateMediaSessionStore.swift), this guarantees that receipt-guaranteed media only flows to nodes capable of storing and retrying failed deliveries.

## On-Device Storage Format and Policies

Private media files never exist in cleartext on disk. BitChat writes incoming files to the **Application Support** directory as raw Noise-encrypted ciphertext. The privacy assessment specifies a **100 MB oldest-first quota** for incoming media and automatic clearance of all managed media after **seven days**.

A panic-wipe operation exposed through `BLEPrivateMediaSessionStore.panicReset()` immediately destroys session state and removes outstanding media files. Lines 33–36 of the session store implementation handle this atomic cleanup.

### Storage Quotas and Automatic Expiration

The storage subsystem enforces size-based eviction when the 100 MB threshold is exceeded, prioritizing the oldest files for deletion. Combined with the mandatory seven-day retention limit, these policies ensure that encrypted media does not accumulate indefinitely on user devices.

## The Private Media Delivery Workflow

The transmission process follows a strict four-phase pipeline that ensures encryption occurs before any network exposure.

### 1. Capability Validation

The sender queries `BLEPrivateMediaSessionStore.provenGeneration(for:requireReceipts:)` to determine if the peer advertises the private-media capability bit. If the check fails, BitChat falls back to sending the file in a signed cleartext envelope that mesh relays can inspect, as shown in lines 68–78 of the session store.

### 2. Noise Encryption

Valid sessions trigger encryption using the established Noise session keys, producing an opaque `noiseEncrypted` blob that encapsulates the media content.

### 3. BLE Transmission

The encrypted blob passes to **`BLEFileTransferHandler`**, which queues the data for delivery over the mesh network while respecting the private-media capability flag. Lines 50–58 of [`BLEFileTransferHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEFileTransferHandler.swift) manage this queuing logic and watchdog logging.

### 4. Persistent Ciphertext Storage

Upon receipt, the ciphertext writes directly to the media folder under Application Support without intermediate decryption. The file remains encrypted until the receiving peer completes the Noise handshake and explicitly decrypts the content for display.

## Code Examples

```swift
// Check if a peer can receive encrypted private media with receipts
if let generation = BLEPrivateMediaSessionStore.shared.provenGeneration(
        for: peerID,
        requireReceipts: true) {
    // Peer supports encrypted private media + receipts → send encrypted blob
    sendEncryptedMedia(to: peerID, using: generation)
} else {
    // Fallback: send a signed, clear-text envelope (mesh relays will see content)
    sendSignedEnvelope(to: peerID, mediaData)
}

```

```swift
// Store an inbound private-media file (ciphertext only)
func storeIncomingMedia(_ ciphertext: Data, for messageID: String) {
    let mediaURL = MediaDirectory.privateMedia.appendingPathComponent(messageID)
    // Ciphertext stays encrypted on disk
    try? ciphertext.write(to: mediaURL, options: .atomic)
}

```

```swift
// Panic-wipe removes all private-media state and files
func wipeAllPrivateMediaOnPanic() {
    BLEPrivateMediaSessionStore.shared.panicReset()   // Clears session & receipt data
    try? FileManager.default.removeItem(at: MediaDirectory.privateMedia) // Delete files
}

```

## Summary

- **Noise XX Protocol**: All private media uses X25519 key agreement with ChaCha20-Poly1305 authenticated encryption and SHA-256 hashing.
- **Capability Verification**: `BLEPrivateMediaSessionStore` validates peer capabilities (bit 8) and optional receipt support (bit 9) before transmission.
- **Ciphertext-Only Storage**: Files persist only in Application Support as encrypted blobs, subject to 100 MB quotas and seven-day automatic expiration.
- **Four-Phase Workflow**: Capability check → Noise encryption → BLE transmission → persistent ciphertext storage.
- **Panic Wipe**: `panicReset()` provides immediate destruction of session keys and media files.

## Frequently Asked Questions

### What encryption algorithm does BitChat use for private media?

BitChat implements the Noise XX handshake pattern using **X25519** for elliptic-curve Diffie-Hellman key exchange, **ChaCha20-Poly1305** for authenticated encryption, and **SHA-256** for integrity hashing. This combination provides forward secrecy and ensures that only the intended recipient can decrypt media files.

### Where does BitChat store private media files on iOS?

BitChat stores private media exclusively in the **Application Support** directory as Noise-encrypted ciphertext. The application never writes cleartext media to disk, and enforces a 100 MB oldest-first quota with automatic deletion of files older than seven days according to the privacy assessment documentation.

### How does BitChat prevent unauthorized peers from receiving encrypted media?

The **`BLEPrivateMediaSessionStore`** validates capability proofs through the `provenGeneration` method before transmission. If a peer does not advertise the private-media capability bit, BitChat sends the file in a signed cleartext envelope instead, preventing wasteful encryption to incompatible nodes and ensuring transparency when end-to-end encryption is unavailable.

### What happens to private media during a panic wipe?

Invoking **`BLEPrivateMediaSessionStore.panicReset()`** immediately clears all session generations, capability proofs, and receipt data from memory and persistent stores. Simultaneously, the application deletes the private media directory from Application Support, ensuring no ciphertext or session metadata remains on the device.