# BitChat Offline Sealed Message Encryption: Noise X Mechanism and Limitations

> Explore BitChat's offline sealed message encryption with the Noise X mechanism. Learn about its limitations including payload caps and expiry windows. Discover a secure communication solution.

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

---

**BitChat encrypts offline sealed messages using a one-way Noise X construction where Version 1 uses static Noise public keys and Version 2 uses one-time pre-keys for forward secrecy, with strict constraints including 16KB payload limits, 24-hour expiry windows, and daily rotating recipient tags.**

BitChat implements a specialized store-and-forward protocol for delivering messages to offline peers through a "courier envelope" system. According to the permissionlesstech/bitchat source code, this offline messaging layer found in [`localPackages/BitFoundation/Sources/BitFoundation/CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/CourierEnvelope.swift) uses a one-way Noise X encryption scheme with specific versioned implementations and operational constraints designed for lightweight text transmission.

## How the Courier Envelope Encryption Works

The `CourierEnvelope` struct encapsulates the cryptographic protections that allow untrusted couriers to forward messages without accessing their contents.

### One-Way Noise X Construction

At the core of the implementation is an **opaque one-way Noise X ciphertext**. The Noise X handshake pattern provides sender authentication and encryption, but the construction is deliberately one-way—meaning the recipient cannot encrypt a reply using the same envelope. The envelope contains only a rotating "recipient tag" and the ciphertext, ensuring the forwarding courier remains unable to read the message payload.

### Version 1 vs Version 2

BitChat supports two cryptographic versions with different key exchange properties:

- **Version 1 (v1)** – The payload is encrypted with the recipient's **static Noise public key**. This provides encryption but lacks forward secrecy.
- **Version 2 (v2)** – The payload is encrypted with a **one-time pre-key** identified by `prekeyID`. This provides **forward secrecy** because the pre-key is consumed after a single use and cannot be used to decrypt past messages if compromised.

As implemented in [`CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/CourierEnvelope.swift) (lines 31-37 and 109-115), v2 envelopes contain a `prekeyID` TLV field that v1 decoders skip silently, creating version compatibility considerations.

### Recipient Tag Derivation

The envelope identifies its intended recipient through a rotating tag rather than a static address. The tag is derived via **HMAC-SHA-256** of the recipient's static key combined with the **UTC day**. This produces a `recipientTag` that rotates daily to prevent long-term correlation. The system generates `candidateTags` for the creation day plus adjacent days to tolerate clock skew, as detailed in lines 71-93 of [`CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/CourierEnvelope.swift).

## Limitations of BitChat's Offline Messaging

The courier envelope system imposes strict operational constraints that define its security and usability envelope.

### Payload Size and Media Restrictions

Offline messages are strictly limited to text-sized payloads. The implementation defines `maxCiphertextBytes` as **16 KB** (line 41), and the source code explicitly notes that "media transfers are out of scope." Large files must use a different transport mechanism, as the envelope cannot accommodate binary media.

### Expiry and Copy Budget Constraints

Each envelope carries hardcoded lifetime limits to prevent indefinite storage:

- **Expiry window**: Enforces a maximum lifetime of **24 hours** via `maxLifetimeSeconds` (line 43). After expiry, the envelope is discarded.
- **Copy budget**: The `copies` field limits redistribution to a maximum of **8 copies** (`maxCopies`, lines 46-47). A copy budget of **1** designates "carry-only" mode, where the holder may transport but not re-spray the envelope to other peers.

### Daily Rotating Recipient Tags

The recipient tag's daily rotation creates a delivery window limitation. Tags are valid only for the UTC day of creation plus adjacent days (`candidateTags`). If a recipient remains offline beyond this tolerance window, the envelope will not be recognized upon return, effectively limiting offline duration to approximately 48-72 hours depending on when the message was sent relative to day boundaries.

### Version Compatibility and One-Way Design

- **Version compatibility**: v1-only clients cannot decrypt v2 envelopes. When a v1 decoder encounters the `prekeyID` field, it skips the TLV and fails to process the envelope silently.
- **One-way limitation**: The Noise X construction provides no reply path. Recipients cannot encrypt responses using the same envelope; replies require a separate courier envelope created by the original recipient.

## Summary

- BitChat uses a **one-way Noise X construction** in [`CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/CourierEnvelope.swift) for offline message encryption.
- **Version 2** provides forward secrecy via one-time pre-keys, while **Version 1** relies on static keys.
- Hard limits restrict payloads to **16KB text-only** content with a **24-hour expiry** and maximum **8-copy** redistribution budget.
- **Daily rotating recipient tags** limit offline availability to approximately 48-72 hours.
- The scheme is **one-way only** and lacks backward compatibility for v2 envelopes on v1-only clients.

## Frequently Asked Questions

### What encryption protocol does BitChat use for offline sealed messages?

BitChat uses a **one-way Noise X construction** implemented in the `CourierEnvelope` struct. The protocol encrypts payloads using either static Noise public keys (v1) or one-time pre-keys (v2), wrapped in an envelope that includes a daily rotating recipient tag derived from HMAC-SHA-256.

### Why is there a 16KB limit on offline messages?

The `maxCiphertextBytes` constant in [`CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/CourierEnvelope.swift) (line 41) caps ciphertext at 16KB because the courier envelope system is designed exclusively for text-sized payloads. The source code explicitly excludes media transfers from this transport mechanism, requiring large files to use alternative protocols.

### What happens if a recipient is offline for more than 24 hours?

Envelopes expire after **24 hours** (`maxLifetimeSeconds`) and are automatically discarded by the network. Additionally, recipient tags rotate daily based on UTC time, so if a peer remains offline beyond the adjacent-day tolerance window, they will not recognize the envelope even if it hasn't expired, effectively limiting practical offline duration to 48-72 hours.

### Can offline messages be decrypted by the forwarding courier?

No. The courier receives only the encrypted ciphertext and a rotating recipient tag. Because the payload is encrypted with the recipient's public key (v1) or one-time pre-key (v2) using the Noise X protocol, the forwarding node cannot access the plaintext content. However, the courier does learn the approximate timing and size of the transmission.