BitChat Private Envelope Format vs NIP-44: Architecture, Usage, and Comparison
BitChat uses a proprietary "private envelope" format built on secp256k1 ECDH, HKDF-SHA256, and XChaCha20-Poly1305 for offline courier delivery, sacrificing NIP-44's forward secrecy and interoperability for store-and-forward resilience.
The BitChat private envelope format is the messaging protocol's fallback mechanism for encrypted communications that cannot traverse standard Nostr relays. While NIP-44 represents the standardized approach to encrypted direct messages in the Nostr ecosystem, BitChat's envelope serves a specialized purpose: enabling offline, tag-routed message delivery through a decentralized courier system. This article examines how the format works, where it differs from NIP-44, and when each approach makes sense.
How the BitChat Private Envelope Works
The private envelope is defined in localPackages/BitFoundation/Sources/BitFoundation/CourierEnvelope.swift and implements a Noise-style static-key sealed payload with additional metadata for courier coordination.
Core Cryptographic Stack
| Layer | Implementation | Purpose |
|---|---|---|
| Key agreement | secp256k1 ECDH | Derives shared secret between sender and recipient static keys |
| Key derivation | HKDF-SHA256 | Expands shared secret to 256-bit symmetric key |
| Symmetric encryption | XChaCha20-Poly1305 | AEAD encryption with 24-byte nonce |
| Routing | 4-byte recipient tag | Enables courier routing without exposing full public key |
The secp256k1 curve choice is deliberate: it maintains compatibility with Bitcoin and Nostr public keys, allowing BitChat to reuse existing key material rather than requiring separate X25519 keys.
Unique Metadata Fields
BitChat's envelope includes three metadata components absent from NIP-44:
- Expiry timestamp — Milliseconds since epoch; couriers discard expired envelopes
- Copy budget — Redundancy limit for spray-and-wait storage strategies
- Version flag — Distinguishes v1 (static-sealed) from v2 (adds copy-budget TLV)
The recipient tag deserves special attention. Calculated from the recipient's Noise static public key and the current UTC day, this rotating tag allows couriers to route envelopes without learning the recipient's identity. According to docs/privacy-assessment.md#L60, this design "enables offline delivery while minimizing metadata exposure."
TLV Serialization
The envelope serializes as a TLV (type-length-value) payload, making it suitable for two transport modes:
- Embedded verbatim into Nostr
gift_wrapevents (kind 1059) - Direct placement into bridge-courier drops
NIP-44 Standard: The Established Approach
NIP-44 is Nostr's official encrypted direct message specification, implemented across most clients. Its design priorities differ fundamentally from BitChat's envelope.
NIP-44 Cryptographic Profile
- Curve: X25519 (Curve25519) for ECDH
- Key derivation: HKDF-SHA256 (identical to BitChat)
- Cipher: ChaCha20-Poly1305 (or AES-GCM in some implementations)
- Critical distinction: Per-message ephemeral DH keys provide forward secrecy
Forward Secrecy: The Decisive Difference
NIP-44 generates a new ephemeral key pair for every message. Even if a recipient's long-term private key is later compromised, past messages remain secure. BitChat's envelope, sealed to static keys, offers no forward secrecy—a compromise of the recipient's key decrypts all historical envelopes.
Direct Comparison: BitChat Private Envelope vs NIP-44
| Aspect | BitChat Private Envelope | NIP-44 |
|---|---|---|
| Standardization | Proprietary, BitChat-only | Official Nostr improvement proposal |
| Key agreement curve | secp256k1 | X25519 |
| Key derivation | HKDF-SHA256 | HKDF-SHA256 |
| AEAD cipher | XChaCha20-Poly1305 (24-byte nonce) | ChaCha20-Poly1305 or AES-GCM |
| Forward secrecy | None — static-key sealed | Yes — ephemeral DH per message |
| Metadata | Expiry, copy-budget, version flag | Minimal — encrypted blob with optional hints |
| Transport mechanism | gift_wrap events or physical courier drops |
Direct relay transmission only |
| Routing | Tag-based, offline-capable | Online relay-dependent |
| Interoperability | BitChat exclusive | Cross-client compatible |
| Size characteristics | ~2 KB limit + TLV overhead | Similar limit, no TLV overhead |
Practical Usage Examples
Creating a BitChat Private Envelope (Swift)
This example from CourierEnvelope.swift demonstrates envelope construction with copy-budget and expiry settings:
import BitFoundation
// Recipient's static Noise key (secp256k1)
let recipientKey = try NoiseKeyPair.fromHex("02a3…")
// Build plaintext payload
let plaintext = Data("Hello, secret world!".utf8)
// Create envelope: 1 copy, 24-hour expiry
var envelope = try CourierEnvelope(
recipientKey: recipientKey,
plaintext: plaintext,
expiry: UInt64(Date().addingTimeInterval(86_400).timeIntervalSince1970 * 1_000),
copies: 1)
// Encode to TLV bytes for transmission
let encoded = try envelope.encode()
// Wrap into Nostr gift_wrap event (kind 1059)
// (Use your Nostr client library to complete event creation)
The CourierEnvelope.encode() method handles TLV serialization, while CourierEnvelope.decode() reverses the process. Both are implemented in localPackages/BitFoundation/Sources/BitFoundation/CourierEnvelope.swift.
Decoding a Received Envelope (Swift)
import BitFoundation
// Extract TLV payload from gift_wrap event
guard let envelopeData = event.giftWrap?.envelope else {
fatalError("No envelope present")
}
let envelope = try CourierEnvelope.decode(envelopeData)
// Verify addressing
guard envelope.isAddressed(to: myNoiseStaticKey) else {
fatalError("Envelope not for this peer")
}
// Decrypt and obtain original message
let plaintext = try envelope.open(using: myNoiseStaticKey)
NIP-44 Encryption (JavaScript)
Using nostr-tools for comparison:
import { encrypt, decrypt } from "nostr-tools/nip44";
const alice = generateSecretKey(); // sender (X25519)
const bob = generateSecretKey(); // recipient (X25519)
// Encrypt with ephemeral key (forward secrecy guaranteed)
const ciphertext = await encrypt(alice, bob, "Hello, NIP-44!");
// Decrypt on receipt
const plaintext = await decrypt(bob, alice, ciphertext);
Architectural Trade-offs Explained
When BitChat's Envelope Excels
Offline resilience is the envelope's primary advantage. When Nostr relays are unreachable—whether due to censorship, network partitions, or deliberate air-gapping—the courier system provides an alternative path. The copy-budget mechanism enables redundant, time-bounded storage across untrusted nodes without requiring recipient online presence.
The tag-based routing in docs/BRING_THE_NOISE.md demonstrates how couriers can batch and forward envelopes without cryptographic capability to read contents or identify recipients beyond day-rotated tags.
When NIP-44 Is Preferable
For standard relay-based messaging, NIP-44 offers:
- Immediate interoperability across Nostr clients
- Strong forward secrecy guarantees
- Simpler implementation with audited libraries
- No additional metadata management overhead
Summary
- BitChat's private envelope uses secp256k1 ECDH, HKDF-SHA256, and XChaCha20-Poly1305 in a TLV structure supporting expiry timestamps and copy-budget metadata
- The format enables offline courier delivery through recipient tags computed from static keys and UTC day rotation
- No forward secrecy: compromise of a static key exposes all past envelopes
- NIP-44 provides standardized, forward-secure encryption via X25519 and ephemeral keys, but requires online relay access
- Choose BitChat's envelope for resilient, store-and-forward scenarios; use NIP-44 for standard interoperable messaging
Frequently Asked Questions
What cryptographic primitives does the BitChat private envelope use?
The envelope combines secp256k1 ECDH for key agreement, HKDF-SHA256 for key derivation, and XChaCha20-Poly1305 for authenticated encryption. The XChaCha20 variant uses a 24-byte nonce, providing safer nonce handling than standard ChaCha20's 12-byte nonce. These choices are documented in docs/privacy-assessment.md and implemented in CourierEnvelope.swift.
Why doesn't BitChat use NIP-44 for all messages?
NIP-44 requires online relay connectivity and provides no mechanism for store-and-forward delivery. BitChat's courier architecture needs envelopes that can persist on untrusted nodes with configurable redundancy (copy-budget) and automatic expiration. The proprietary envelope format encodes these requirements directly in the TLV structure.
How does the recipient tag protect privacy?
The 4-byte tag is computed from the recipient's Noise static public key and the current UTC day. Couriers use this tag for routing decisions without possessing the full public key. Tags rotate daily, limiting correlation windows. However, as noted in docs/privacy-assessment.md, relays and couriers still observe timestamps, approximate sizes, and the tags themselves—metadata absent from the encrypted payload but present in the transport layer.
Can other Nostr clients read BitChat private envelopes?
No. The format is intentionally incompatible with NIP-44, NIP-17, and NIP-59. Only BitChat implements the secp256k1-based key agreement and TLV parsing required for decryption. This exclusivity is the cost of the specialized courier functionality; interoperability would require standardization of the copy-budget and expiry metadata fields.
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 →