Knowledge base Under the hood
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 | Login with a browser extension. |
| NIP-09 | Deletion requests. |
| NIP-10 | Marked reply tags — how a channel thread reply points at its root message. |
| NIP-13 | Proof of work. Outgoing messages are mined to at least 16 bits. |
| NIP-17 | Private direct messages, and the basis for group chats. |
| NIP-25 | Reactions. |
| NIP-30 | Custom emoji. |
| NIP-40 | Expiration timestamps — disappearing messages. |
| NIP-44 | Versioned encryption, used for seals, wraps and settings sync. |
| NIP-46 | Remote signing — bunker://. |
| NIP-56 | Reporting. |
| NIP-57 | Lightning zaps. |
| NIP-59 | Gift wraps. |
| NIP-78 | Arbitrary application data. |
| NIP-98 | 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. |
| 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_atis 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.
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
is AGPL-3.0 and complete — there is no closed component to take on trust.