# BitChat Private Envelope Format vs NIP-17 and NIP-44: Complete Technical Comparison

> Discover the technical differences between BitChat Private Envelope and Nostr NIP-17 and NIP-44. Learn about its XChaCha20 encryption and incompatibility with standard clients.

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

---

**BitChat implements a proprietary "private-envelope" protocol that reuses NIP-17 gift-wrap event kinds (1059, 13, 14) but diverges from both NIP-17 and NIP-44 by employing XChaCha20-Poly1305 encryption, custom HKDF parameters, and a `v2:` payload prefix, rendering it incompatible with standard Nostr clients.**

BitChat, developed by permissionlesstech, provides end-to-end encrypted messaging over Nostr relays. According to the source code in `permissionlesstech/bitchat`, the protocol wraps messages in a custom cryptographic envelope that superficially resembles the NIP-17 (Gift Wrap) standard while using encryption primitives that differ from NIP-44 (Encrypted Payloads). Understanding these distinctions is critical for developers attempting to integrate with or audit the BitChat protocol.

## Event Structure and Message Kinds

Standard **NIP-17** defines a gift-wrap protocol that hides metadata by wrapping an encrypted payload in a kind 1059 event. The inner payload (kind 13 "seal" and kind 14 "rumor") contains the actual message and metadata.

BitChat adopts these exact kind numbers for relay transport, as defined in [`NostrProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrProtocol.swift) (lines 20-30):

- **Kind 1059**: Outer gift-wrap event stored on relays
- **Kind 13**: Inner "seal" containing the encrypted content
- **Kind 14**: "Rumor" containing the plaintext message details (inside the seal)

However, while NIP-17 typically uses **NIP-44** for the encryption layer inside the seal, BitChat replaces this with a custom cipher suite.

## Encryption Primitives: XChaCha20 vs ChaCha20

The most significant divergence occurs at the cryptographic primitive level.

**NIP-44** specifies **ChaCha20-Poly1305** with a 12-byte nonce for payload encryption. BitChat instead implements **XChaCha20-Poly1305**, which uses a 24-byte nonce to mitigate nonce-collision risks.

The `encrypt` function in [`NostrProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrProtocol.swift) (lines 90-102) handles this using 24-byte random nonces. This choice provides enhanced security margins for long-lived message histories but breaks interoperability with NIP-44 implementations.

## Key Derivation and HKDF Parameters

Both protocols use **HKDF-SHA256** for symmetric key derivation from the ECDH shared secret, but the parameters differ critically:

- **NIP-44**: Uses the literal string `"nip44"` as the **salt** parameter to HKDF.
- **BitChat**: Uses the string `"nip44-v2"` as the **info** parameter (not salt) to HKDF.

As implemented in `derivePrivateEnvelopeKey` (lines 106-112 of [`NostrProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrProtocol.swift)), BitChat's derivation produces a different output key than NIP-44 even when given identical sender and recipient keys. This incompatible key derivation prevents cross-protocol decryption.

## Payload Layout and Version Markers

BitChat enforces strict payload identification through version prefixes:

- **NIP-44**: Uses the `v4:` prefix followed by base64url-encoded `nonce(12 bytes)‖ciphertext‖tag(16 bytes)`.
- **BitChat**: Uses the `v2:` prefix followed by base64url-encoded `nonce(24 bytes)‖ciphertext‖tag(16 bytes)`.

The `encrypt` and `decrypt` functions (lines 121-128 and 132-138) explicitly check for this `v2:` marker. Standard NIP-44 `v4:` payloads are rejected during the decryption process, ensuring the client only processes BitChat-specific envelopes.

## Authentication and Trust Model

**NIP-44** authenticates messages solely through the ChaCha20-Poly1305 cipher tag; the sender's identity is proven by the Nostr event signature on the outer wrapper.

BitChat adds an additional authentication layer. The inner **seal** (kind 13) is cryptographically signed by the sender's **real identity key** before encryption. The `createSeal` function (lines 77-84) handles this signing, and `decryptPrivateMessage` verifies the signature (lines 31-35) to confirm sender authenticity before exposing the message content. This provides non-repudiation inside the encrypted envelope, even if the outer gift-wrap uses randomized metadata.

## Swift Implementation Examples

### Creating a Private Message

The following creates a BitChat-compatible gift-wrap (kind 1059) ready for relay publication:

```swift
// Create a private message (outer kind 1059) for a recipient
let event = try NostrProtocol.createPrivateMessage(
    content: "Hello, secure world!",
    recipientPubkey: recipientHex,
    senderIdentity: myNostrIdentity
)

// `event` is a fully-signed NostrEvent ready for relay publication.
// The payload (`event.content`) begins with "v2:" and contains the XChaCha20-Poly1305 ciphertext.
// See the creation flow in NostrProtocol.swift lines 45-95.

```

### Decrypting a Received Envelope

Decryption unwraps the gift-wrap, verifies the inner seal signature, and returns the plaintext:

```swift
// Assume `giftWrap` is the NostrEvent fetched from the relay
let (plainText, senderPubkey, timestamp) = try NostrProtocol.decryptPrivateMessage(
    giftWrap: giftWrap,
    recipientIdentity: myNostrIdentity
)

// `plainText` holds the original message, `senderPubkey` is the authenticated sender,
// and `timestamp` is the true message time (the outer event's randomized timestamp is ignored).
// Decryption logic follows the steps in NostrProtocol.swift decryptPrivateMessage function.

```

## Summary

- **Event Kinds**: BitChat uses NIP-17 kinds (1059, 13, 14) for transport but proprietary encryption for content.
- **Cipher**: XChaCha20-Poly1305 (24-byte nonce) replaces NIP-44's ChaCha20-Poly1305 (12-byte nonce).
- **Key Derivation**: HKDF with info parameter `"nip44-v2"` replaces NIP-44's salt parameter `"nip44"`.
- **Payload Marker**: `v2:` prefix identifies BitChat envelopes, incompatible with NIP-44's `v4:`.
- **Authentication**: Inner seal signatures provide sender verification beyond the cipher authentication tag.
- **Forward Secrecy**: Neither BitChat nor NIP-44 provide forward secrecy; both rely on static keys for ECDH.

## Frequently Asked Questions

### Is BitChat compatible with standard NIP-17 clients?

No. While BitChat uses the same event kinds as NIP-17 (1059 for gift-wrap, 13/14 for seal/rumor), the encryption layer uses XChaCha20-Poly1305 and the `v2:` prefix rather than the NIP-44 encryption expected by NIP-17 implementations. Standard clients will fail to decrypt BitChat messages.

### Why does BitChat use "v2" instead of NIP-44's "v4"?

The `v2:` marker indicates the use of XChaCha20-Poly1305 with 24-byte nonces and the custom HKDF derivation with `"nip44-v2"` info. This distinguishes BitChat's format from NIP-44's `v4:` (ChaCha20-Poly1305 with 12-byte nonces) and previous BitChat experimental versions.

### Does BitChat provide better security than NIP-44?

BitChat uses XChaCha20-Poly1305 which allows safer random nonce generation due to the 192-bit nonce space, reducing collision probability compared to ChaCha20's 96-bit nonces. However, like NIP-44, it lacks **forward secrecy**—compromise of a private key allows decryption of all past messages, as noted in the [`NostrProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrProtocol.swift) header comments.

### Can BitChat users receive messages from standard Nostr direct message clients?

No. BitChat specifically filters for kind 1059 events with content prefixed by `v2:`. Standard NIP-44 encrypted messages (typically kind 4 with `v4:` content or gift-wraps using standard NIP-44 encryption) are rejected by the `decryptPrivateMessage` validation logic.