# BitChat Courier Store-and-Forward Service for Offline Messaging: Technical Deep Dive

> Learn how the BitChat courier store-and-forward service ensures offline message delivery via encrypted envelopes and spray-and-wait diffusion. Discover its innovative anti-metadata features.

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

---

**The BitChat courier store-and-forward service guarantees message delivery to offline peers by having mutual favorites physically transport end-to-end encrypted envelopes through a spray-and-wait diffusion protocol, using rotating recipient tags and bounded copy-budgets to prevent metadata leakage and amplification attacks.**

BitChat, developed by the open-source team at permissionlesstech, solves the challenge of messaging when recipients are unreachable via live Bluetooth Low Energy (BLE) mesh or Nostr relays. The BitChat courier store-and-forward service for offline messaging enables mobile devices to carry opaque encrypted payloads for one another, ensuring eventual delivery without exposing message contents to intermediate devices or enabling long-term traffic analysis.

## The Courier Envelope Architecture

At the core of the offline messaging layer lies the **courier envelope**, a Type-Length-Value (TLV) structure defined in [`localPackages/BitFoundation/Sources/BitFoundation/CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/CourierEnvelope.swift). This opaque blob wraps private messages in a format that couriers can transport without gaining access to the underlying plaintext.

The envelope contains:
- A rotating recipient tag derived from the recipient’s Noise static public key
- An expiry timestamp enforcing bounded lifetime
- End-to-end encrypted ciphertext
- A copy-budget field limiting diffusion hops
- An optional pre-key ID for forward secrecy

Because the envelope appears as random bytes to anyone except the intended recipient, couriers act as oblivious data mules, unable to read, modify, or meaningfully analyze the traffic they carry.

## Rotating Recipient Tags for Privacy

To prevent long-term correlation attacks while still enabling routing, BitChat implements **rotating recipient tags** that change daily and cannot be linked across time periods. The tag is derived from the recipient’s Noise static public key and the current UTC day, calculated via `CourierEnvelope.recipientTag(noiseStaticKey:epochDay:)` in [`CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/CourierEnvelope.swift).

When a device receives an envelope, it checks candidate tags using `CourierEnvelope.candidateTags(noiseStaticKey:around:)`, generating possible tags for the current day and surrounding days to account for clock drift. This design ensures that couriers see only a temporary routing hint that expires with the day, eliminating persistent metadata trails that could identify communication patterns.

## Spray-and-Wait Diffusion with Copy-Budgets

The service employs a **spray-and-wait** diffusion strategy to propagate envelopes through moving crowds while preventing amplification attacks. Each envelope carries a `copies` field with a maximum budget of 8. When a courier accepts an envelope for forwarding, the copy budget is halved at each "spray" event, limiting how many times the message can split and preventing an attacker from flooding the network by cloning envelopes.

This bounded epidemic routing ensures that messages spread through a mobile device population efficiently without consuming unbounded bandwidth or storage. The outbox retention logic—described in the [`WHITEPAPER.md`](https://github.com/permissionlesstech/bitchat/blob/main/WHITEPAPER.md) (section 5.2)—retains envelopes until at least three distinct couriers have accepted them, or until the envelope expires, whichever comes first.

## Deposit, Retry, and Expiration Logic

Reliability is enforced through automatic deposit and retry mechanisms. The sender’s outbox, managed by the **MessageRouter**, retains envelopes and automatically re-deposits them when new eligible couriers appear within radio range. This ensures that transient connectivity gaps do not result in message loss.

Expiration is enforced by the `maxLifetimeSeconds` constant (defaulting to 24 hours) and the `isExpired(at:)` method in [`CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/CourierEnvelope.swift). Stale envelopes are discarded by the `CourierStore`, preventing storage exhaustion and replay attacks. The combination of expiry and copy-budget limits creates a finite window during which the message must reach its destination, after which it is safely purged from all participating devices.

## Bridge Couriers and Nostr Fallback

For scenarios where no direct mesh path exists between sender and recipient, BitChat supports **bridge courier drops** that allow devices acting as bridges to forward opaque envelopes over Nostr relays. The `BridgeCourierService` (tested in [`bitchatTests/Services/BridgeCourierServiceTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/BridgeCourierServiceTests.swift)) handles this flow, ensuring the bridge device cannot read the payload while extending the store-and-forward capability beyond local BLE range to the global Nostr network.

This hybrid approach combines the low-latency efficiency of local mesh diffusion with the reach of internet relay networks, all while maintaining strict end-to-end encryption boundaries.

## Implementing Store-and-Forward in Code

The following Swift examples demonstrate how to create, encode, and decode courier envelopes using the BitFoundation package.

### Creating and Sealing an Envelope

```swift
import BitFoundation

// Prepare the recipient's Noise static public key
let recipientStaticKey: Data = // recipient's Noise static public key
let message: Data = "Hello, offline!".utf8

// Derive the daily rotating tag for today
let today = Date()
let tag = CourierEnvelope.recipientTag(
    noiseStaticKey: recipientStaticKey,
    epochDay: CourierEnvelope.epochDay(for: today)
)

// Seal the message using Noise X encryption
let sealedCiphertext = try NoiseX.seal(message, to: recipientStaticKey)

// Construct the envelope with a copy-budget of 4
let envelope = CourierEnvelope(
    recipientTag: tag,
    expiry: UInt64(Date().addingTimeInterval(CourierEnvelope.maxLifetimeSeconds).timeIntervalSince1970 * 1000),
    ciphertext: sealedCiphertext,
    copies: 4
)

// Encode for BLE transmission
guard let encoded = envelope.encode() else {
    fatalError("Failed to encode envelope")
}
// `encoded` can now be handed to a courier via BLE advertisement

```

### Receiving and Verifying Envelopes

```swift
// Check if this envelope is addressed to you
let possibleTags = CourierEnvelope.candidateTags(
    noiseStaticKey: myStaticKey,
    around: Date()
)

if possibleTags.contains(envelope.recipientTag) {
    // Verify expiration before attempting decryption
    if !received.isExpired {
        let plaintext = try NoiseX.open(
            received.ciphertext, 
            withStaticKey: myStaticKey
        )
        print(String(data: plaintext, encoding: .utf8)!)
    }
}

```

## Security Guarantees

According to the [`WHITEPAPER.md`](https://github.com/permissionlesstech/bitchat/blob/main/WHITEPAPER.md) and source implementation, the courier service provides four critical security properties:

- **Confidentiality:** Couriers possess only the encrypted ciphertext and rotating tags; message content remains inaccessible without the recipient's private keys.
- **Forward Secrecy:** Envelopes can optionally be sealed to one-time pre-keys identified by `prekeyID`, limiting exposure if static keys are compromised later.
- **Replay Resistance:** The combination of `maxLifetimeSeconds` and halving copy-budgets prevents infinite message replication or delayed replay attacks.
- **Metadata Reduction:** Daily rotating recipient tags prevent long-term correlation of communication patterns, while the spray-and-wait protocol avoids centralized traffic analysis.

## Summary

- The **BitChat courier store-and-forward service** enables offline messaging by having devices physically carry encrypted envelopes for mutual favorites.
- **Envelope structure** is defined in [`CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/CourierEnvelope.swift) as a TLV containing rotating tags, expiry timestamps, ciphertext, and copy-budgets.
- **Privacy is protected** through `recipientTag(noiseStaticKey:epochDay:)` and `candidateTags(noiseStaticKey:around:)` which generate daily rotating tags that cannot be linked across days.
- **Anti-amplification** is enforced by halving the `copies` field (max 8) at each hop, while **expiration** is handled by `isExpired(at:)` using the 24-hour `maxLifetimeSeconds` constant.
- **Bridge couriers** extend reach to Nostr relays without compromising confidentiality, as implemented in `BridgeCourierService` and tested in [`BridgeCourierServiceTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BridgeCourierServiceTests.swift).

## Frequently Asked Questions

### How does BitChat prevent couriers from reading stored messages?

Couriers receive only the opaque courier envelope, which contains end-to-end encrypted ciphertext sealed with the recipient's Noise static key. The envelope format in [`CourierEnvelope.swift`](https://github.com/permissionlesstech/bitchat/blob/main/CourierEnvelope.swift) ensures that without the recipient's private key, the payload remains indistinguishable from random data, providing confidentiality even if the courier device is compromised.

### What prevents a malicious courier from copying and flooding the network with my envelope?

Each envelope contains a `copies` field initialized to a maximum of 8. According to the spray-and-wait protocol implementation, the copy-budget is halved each time the envelope is forwarded to a new courier. Once the budget reaches zero, the envelope stops spreading, preventing amplification attacks while still allowing sufficient diffusion through a crowd to reach the recipient.

### How long does BitChat retain undelivered courier envelopes?

By default, envelopes expire after 24 hours as defined by the `maxLifetimeSeconds` constant. The `isExpired(at:)` method checks the timestamp embedded during envelope creation. Additionally, the MessageRouter's outbox logic discards envelopes once three distinct couriers have accepted them, balancing reliability with storage constraints.

### Can offline messages be delivered over the internet instead of just BLE mesh?

Yes, through the bridge courier mechanism implemented in `BridgeCourierService`. Devices acting as bridges can deposit opaque courier envelopes onto Nostr relays when no local BLE path exists. The bridge cannot decrypt the payload, maintaining end-to-end security while extending delivery beyond direct radio range, as verified in [`bitchatTests/Services/BridgeCourierServiceTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/BridgeCourierServiceTests.swift).