Skip to the content
Back to Nymchat

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

KindUsed forNotes
0Profile metadataStandard Nostr profile: name, picture, banner, about, lud16.
5Deletion requestsNIP-09.
7ReactionsNIP-25, with a ['k', originalKind] tag so a reaction is filed against the right channel type.
13SealThe middle layer of a NIP-17 message.
14Private message rumorUnsigned, 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.
1059Gift wrapNIP-59. What actually reaches a relay for private messages and group chats.
1984ReportingNIP-56.
9734 / 9735Zap request and zap receiptNIP-57.
10030Custom emoji pack listsNIP-30.
20000Geohash channel messageEphemeral, tagged ['g', geohash]. A thread reply adds a NIP-10 marked ['e', rootId, '', 'root'] tag.
23333Named channel messageEphemeral, tagged ['d', channel]. A thread reply adds a NIP-10 marked ['e', rootId, '', 'root'] tag.
24420 / 24421Public typing indicators and read receiptsNymchat-specific, for public channels.
25051 / 25052Peer-to-peer signaling and file-transfer statusNymchat-specific.
30030Custom emoji setsNIP-30.
30078Application dataNIP-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).
69420Private read receipts and delivery statusNymchat-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

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

Cryptography

WhereWhat
Identity and signaturessecp256k1 with Schnorr signatures (BIP-340), as Nostr specifies.
Private messages and group chatsNIP-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 usersThe 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 sessionsNoise XX — X25519, ChaCha20-Poly1305 (IETF), Ed25519 for announce signatures, SHA-256 and HMAC underneath.
Identity at restAES-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 transfersWebRTC's own DTLS-SRTP, negotiated over NIP-17-wrapped signaling.
RandomnessThe 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.

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 is AGPL-3.0 and complete — there is no closed component to take on trust.