# Protocol and events

What Nymchat actually puts on the wire, for anyone building against it, auditing it, or reading events from another Nostr client.

## Event kinds

| Kind | Used for | Notes |
| --- | --- | --- |
| `0` | Profile metadata | Standard Nostr profile: name, picture, banner, about, `lud16`. |
| `5` | Deletion requests | NIP-09. |
| `7` | Reactions | NIP-25, with a `['k', originalKind]` tag so a reaction is filed against the right channel type. |
| `13` | Seal | The middle layer of a NIP-17 message. |
| `14` | Private message rumor | Unsigned, and never published on its own. A thread reply carries a `['nymthread', rootId]` tag inside the encrypted rumor, referencing the root's shared message id. |
| `1059` | Gift wrap | NIP-59. What actually reaches a relay for private messages and group chats. |
| `1984` | Reporting | NIP-56. |
| `9734` / `9735` | Zap request and zap receipt | NIP-57. |
| `10030` | Custom emoji pack lists | NIP-30. |
| `20000` | Geohash channel message | Ephemeral, tagged `['g', geohash]`. A thread reply adds a NIP-10 marked `['e', rootId, '', 'root']` tag. |
| `23333` | Named channel message | Ephemeral, tagged `['d', channel]`. A thread reply adds a NIP-10 marked `['e', rootId, '', 'root']` tag. |
| `24420` / `24421` | Public typing indicators and read receipts | Nymchat-specific, for public channels. |
| `25051` / `25052` | Peer-to-peer signaling and file-transfer status | Nymchat-specific. |
| `30030` | Custom emoji sets | NIP-30. |
| `30078` | Application data | NIP-78. Polls, encrypted settings sync, and post-quantum key announcements (tagged `nym-pq`, carrying an ML-KEM-768 public key with a NIP-40 expiration). |
| `69420` | Private read receipts and delivery status | Nymchat-specific, carried as a rumor inside a gift wrap. A separate kind from `14` deliberately, so other NIP-17 clients do not render blank direct messages for every receipt. |

## NIPs Nymchat implements

| NIP | What it gives Nymchat |
| --- | --- |
| [NIP-07](https://github.com/nostr-protocol/nips/blob/master/07.md) | Login with a browser extension. |
| [NIP-09](https://github.com/nostr-protocol/nips/blob/master/09.md) | Deletion requests. |
| [NIP-10](https://github.com/nostr-protocol/nips/blob/master/10.md) | Marked reply tags — how a channel thread reply points at its root message. |
| [NIP-13](https://github.com/nostr-protocol/nips/blob/master/13.md) | Proof of work. Outgoing messages are mined to at least 16 bits. |
| [NIP-17](https://github.com/nostr-protocol/nips/blob/master/17.md) | Private direct messages, and the basis for group chats. |
| [NIP-25](https://github.com/nostr-protocol/nips/blob/master/25.md) | Reactions. |
| [NIP-30](https://github.com/nostr-protocol/nips/blob/master/30.md) | Custom emoji. |
| [NIP-40](https://github.com/nostr-protocol/nips/blob/master/40.md) | Expiration timestamps — disappearing messages. |
| [NIP-44](https://github.com/nostr-protocol/nips/blob/master/44.md) | Versioned encryption, used for seals, wraps and settings sync. |
| [NIP-46](https://github.com/nostr-protocol/nips/blob/master/46.md) | Remote signing — `bunker://`. |
| [NIP-56](https://github.com/nostr-protocol/nips/blob/master/56.md) | Reporting. |
| [NIP-57](https://github.com/nostr-protocol/nips/blob/master/57.md) | Lightning zaps. |
| [NIP-59](https://github.com/nostr-protocol/nips/blob/master/59.md) | Gift wraps. |
| [NIP-78](https://github.com/nostr-protocol/nips/blob/master/78.md) | Arbitrary application data. |
| [NIP-98](https://github.com/nostr-protocol/nips/blob/master/98.md) | HTTP auth, for the hosted endpoints. |

## Cryptography

| Where | What |
| --- | --- |
| Identity and signatures | secp256k1 with Schnorr signatures (BIP-340), as Nostr specifies. |
| Private messages and group chats | NIP-44 v2: ECDH on secp256k1, HKDF-SHA256, ChaCha20 with HMAC-SHA256, and padding so ciphertext length does not give the message length away. |
| Private messages and group chats, between Nymchat users | The same NIP-44 v2 payload, unmodified, sealed a second time inside ChaCha20-Poly1305 keyed from an ML-KEM-768 (FIPS 203) encapsulation through HKDF-SHA256. The encapsulation ciphertext and both identity keys are bound in as associated data, and the result carries a `pq2.` prefix so a reader picks the path by inspecting the payload. Both layers must be broken to recover a message. See [quantum-resistant encryption](https://nymchat.app/docs/post-quantum/). |
| Bluetooth mesh sessions | Noise XX — X25519, ChaCha20-Poly1305 (IETF), Ed25519 for announce signatures, SHA-256 and HMAC underneath. |
| Identity at rest | AES-GCM-256. The key comes from PBKDF2-SHA256 at 310,000 iterations for a password or PIN, or from a WebAuthn PRF output through HKDF-SHA256 for a passkey or biometric. |
| Calls and file transfers | WebRTC's own DTLS-SRTP, negotiated over NIP-17-wrapped signaling. |
| Randomness | The platform CSPRNG throughout — keys, nonces, and the gift-wrap timestamp jitter. |

### Metadata protection

Two details in the gift-wrap path are worth calling out, because they are what separate NIP-59 from "encrypted, but obviously from you to them":

- Every wrap is signed by a **one-time key** generated for that message alone, so no two wraps share a sender.
- Every wrap's `created_at` is **randomly backdated by up to two hours** using the CSPRNG, so send and receive times cannot be lined up.

Group chats add rotating ephemeral *recipient* keys on top of that — see [How group encryption works](https://nymchat.app/docs/groups/#group-crypto).

> **Reading the source**
>
> The crypto primitives are in `js/nym-crypto.js` and `js/modules/key-vault.js` in the web app, and under `android-ios-app/lib/core/crypto/` and `lib/services/mesh/noise/` in the Flutter app. The [repository](https://github.com/Spl0itable/NYM) is AGPL-3.0 and complete — there is no closed component to take on trust.
