# What Data Does BitChat Store and Where? A Privacy‑First Storage Architecture

> Discover what data BitChat stores and where. Learn about its privacy-first architecture, storing minimal data on device and keeping conversation history in memory.

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

---

**BitChat stores only minimal, bounded data on device: cryptographic secrets in the system keychain, preferences in UserDefaults, and encrypted/transient payloads in Application Support, while full conversation history remains purely in memory.**

The open-source BitChat messaging app built by permissionlesstech adopts a "privacy-first" ephemerality model. According to the source code analyzed in [`docs/privacy-assessment.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/privacy-assessment.md), most user conversation data never touches persistent storage. Only specific categories—encrypted outbox messages, temporary public archives, and media files with strict quotas—are written to disk, all protected by platform security mechanisms and automatic expiration.

## Key Data Types and Their Storage Locations

BitChat organizes persistent data into three tiers: **keychain-backed secrets**, **UserDefaults preferences**, and **file-based encrypted/transient stores** in Application Support.

### Cryptographic Secrets: System Keychain

All cryptographic material uses device-only keychain accessibility. This includes:

- **Noise static keys** for encrypted sessions
- **Group encryption keys** for private group messaging
- **Outbox encryption keys** for pending private messages
- **Per-geohash Nostr seeds** for location-based identities

These items are configured with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`, meaning they cannot be transferred to another device or accessed when locked. On a **panic wipe**—triggered by a triple-tap emergency gesture—the app deletes all keychain entries alongside file-based stores.

### User Preferences and Metadata: UserDefaults

Lightweight settings persist in `UserDefaults` within the app sandbox:

- Nickname and display preferences
- Favorites, petnames, and bookmarked locations
- Read-receipt identifiers
- Selected geohash channels and teleport flags

As declared in `bitchat/PrivacyInfo.xcprivacy`, this data usage is explicitly surfaced to App Store review processes.

### Encrypted and Transient File Stores: Application Support

| Store | Contents | Lifetime | Protection |
|-------|----------|----------|------------|
| **Outbound Private Outbox** | ChaChaPoly-sealed messages awaiting delivery | 24 hour TTL | Encryption key in keychain; cleared on panic wipe |
| **Courier Mail** | Noise-sealed envelopes held by relay devices | 24 hour TTL | Same as outbox; courier cannot decrypt contents |
| **Group State** | Group names, roster, creator identity, epoch | Persistent | OS file protection + keychain-backed keys |
| **Public Gossip Archive** | Signed mesh messages | ≤6 hours | Removed on app restart or panic wipe |
| **Public Board Posts** | Signed board events with tombstones | Max 7 days | Bounded by quota; cleared on panic wipe |
| **Media Files** | Voice notes and images | Max 7 days | 100 MB quota with oldest-first eviction |

All file-based stores reside in `~/Library/Application Support/` subdirectories. The **media tree** at `…/Application Support/Media/` enforces automatic cleanup: files exceed 7 days or the quota triggers deletion.

## Ephemeral by Design: In-Memory Conversation Store

The defining architectural decision appears in [`bitchat/App/ConversationStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/App/ConversationStore.swift): **full chat histories live only in RAM**. When the app terminates or the device reboots, this data disappears entirely. This eliminates forensic recovery risks for conversation content, as no plaintext messages are ever written to persistent storage.

The [`NoiseSessionManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NoiseSessionManager.swift) module reinforces this—Noise session keys and session state remain in-memory only, never serialized to disk.

## Practical Code Examples

These Swift snippets from the source demonstrate store access patterns:

### Enqueue an Encrypted Outbox Message (24h TTL)

```swift
import Foundation

func enqueuePrivateMessage(_ data: Data) throws {
    let key = try KeychainHelper.retrieveKey(tag: "outboxEncryptionKey")
    let sealed = try ChaChaPoly.seal(data, using: key)
    let outboxURL = FileManager.default.urls(
        for: .applicationSupportDirectory, in: .userDomainMask
    )[0].appendingPathComponent("Outbox")
    try FileHelper.append(sealed.ciphertext, to: outboxURL)
}

```

The `KeychainHelper` type resides in `bitchat/Identity/`, while `FileHelper` implements bounded-age appending logic.

### Load Media with 7-Day Age Validation

```swift
func loadMediaIfValid(at url: URL) -> Data? {
    guard let attrs = try? FileManager.default.attributesOfItem(atPath: url.path),
          let modDate = attrs[.modificationDate] as? Date else { return nil }
    guard Date().timeIntervalSince(modDate) < 7 * 24 * 60 * 60 else { return nil }
    return try? Data(contentsOf: url)
}

```

### Derive Location-Based Nostr Identity

```swift
func nostrIdentity(for geohash: String) throws -> SecKey {
    let seed = try KeychainHelper.retrieveData(tag: "geohashSeed")
    return try CryptoHelper.deriveNostrKey(seed: seed, salt: geohash)
}

```

The seed persists in keychain; derived identities regenerate on demand and do not require separate storage.

## Core Architectural Guarantees

Four principles govern what data BitChat stores and where:

- **Ephemerality First**: Conversation content stays in memory; persistent stores are strictly bounded exceptions
- **Time-Boxed Persistence**: Every file-based store has hard limits (24 hours, 7 days, or quota-based eviction)
- **Panic Wipe Capability**: Triple-tap emergency wipe clears all persistent stores plus keychain secrets
- **Minimal Attack Surface**: Device compromise reveals only quota-limited, encrypted, or transient data—not full message history

## Key Source Files

| File | Purpose |
|------|---------|
| [`docs/privacy-assessment.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/privacy-assessment.md) | Complete enumeration of persistent stores and security analysis |
| [`bitchat/App/ConversationStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/App/ConversationStore.swift) | In-memory conversation timeline implementation |
| [`bitchat/App/LocationPresenceStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/App/LocationPresenceStore.swift) | Geohash and presence flag persistence |
| [`bitchat/Identity/SecureIdentityStateManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Identity/SecureIdentityStateManager.swift) | Keychain-backed identity lifecycle |
| [`bitchat/Noise/NoiseSessionManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Noise/NoiseSessionManager.swift) | In-memory Noise session state |
| [`PRIVACY_POLICY.md`](https://github.com/permissionlesstech/bitchat/blob/main/PRIVACY_POLICY.md) | User-facing data handling summary |

## Summary

- BitChat stores **cryptographic secrets** exclusively in the system keychain with device-only accessibility
- **UserDefaults** holds lightweight preferences and location state, declared via `PrivacyInfo.xcprivacy`
- **Application Support** files contain encrypted outbox messages (24h), transient public archives (≤6h), board posts (7d max), and media (100MB quota, 7d max)
- **Full conversation histories** remain purely in-memory and vanish on app termination
- **Panic wipe** provides instant, complete data destruction across all stores

## Frequently Asked Questions

### Does BitChat store my chat messages on disk?

No. Full conversation timelines remain purely in-memory via [`ConversationStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ConversationStore.swift) and are lost when the app terminates. Only encrypted outbox messages awaiting delivery are temporarily stored, sealed with ChaChaPoly and purged after 24 hours or upon panic wipe.

### What happens to my data during a panic wipe?

A triple-tap emergency gesture triggers deletion of all keychain secrets, removes the entire media tree, clears all Application Support files, and erases UserDefaults. This leaves no recoverable cryptographic material or message content on the device.

### How are my encryption keys protected?

All private keys—including Noise static keys, group keys, outbox encryption keys, and per-geohash Nostr seeds—reside in the iOS system keychain with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`. They cannot be backed up or transferred to another device and are inaccessible when the device is locked.

### Will media I receive remain on my device indefinitely?

No. Incoming voice notes and images are subject to a 100 MB quota with oldest-first eviction, plus a hard 7-day age limit. `FileHelper` automatically validates modification timestamps and rejects expired files during access attempts.