Chapter 17 - Encryption
17
Nostr Implementation Possibility - Hex № 17

Private Direct Messages

A chat scheme that hides message content, timing, and who is talking to whom, built from NIP-44 encryption and NIP-59 gift wrapping.

1 Overview

NIP-17 defines private direct messages. It hides the content of a message. It also hides who sent it, who received it, and when. NIP-17 does not invent new cryptography. It combines two existing NIPs: NIP-44 for encryption and NIP-59 for gift wrapping.

A conversation happens inside a chat room. By convention, three kinds of content flow through a chat room: kind 14 chat messages, kind 15 file messages, and kind 7 reactions, defined in NIP-25. This page calls all three message rumors.

Every message rumor is unsigned. A client seals it, then gift-wraps it, before it ever reaches a relay. Section 3 covers this chain in full. A separate event kind, 10050, tells other clients which relays to use for delivery.

Hint!

NIP-17 replaces NIP-04 encrypted direct messages, which is now deprecated. NIP-04 leaked the sender, the receiver, and the timestamp of every message in plain sight. NIP-17 hides all three.

2 Chat Rooms

The most common chat room has two members. NIP-17 also supports rooms with more members, though Section 8 recommends a different scheme past ten participants.

The sender's pubkey, plus every pubkey in a p tag, defines the room. Add one p tag, or remove one, and the result is a new room. The new room starts with a clean, empty history.

An optional subject tag names the room. Any member can rename the room by sending a new message with a new subject tag. Clients use the newest subject tag as the current name. A client does not need to repeat the subject tag on every message.

3 The Encryption Chain

NIP-17 wraps a message rumor three layers deep. Each layer hides information that the layer around it does not need to know.

  • Rumor — the message itself: a kind 14, kind 15, or kind 7 event, built and hashed like any other event, but never signed.
  • Seal — a kind 13 event. Its content holds the NIP-44-encrypted rumor. Its tags array is always empty. The sender signs the seal with their real key, so the seal proves who wrote the rumor, but nobody can read the rumor without a private key.
  • Gift wrap — a kind 1059 event. Its content holds the NIP-44-encrypted seal. It carries one p tag, naming the receiver. A brand-new, random, one-time keypair signs the gift wrap, so the gift wrap does not reveal the real sender.
Rumor kind 14 - unsigned Seal kind 13 - real sig Gift Wrap kind 1059 - random sig Relay stores it nip44.encrypt() sender key + receiver key nip44.encrypt() random key + receiver key EVENT
Fig. 17-1 — The encryption chain. Rumor becomes seal becomes gift wrap. Only the last layer ever touches a relay.
Wire format - unsigned rumor (kind 14 chat message)
{
  "id":         <usual sha256 hash, per NIP-01>
  "pubkey":     <sender pubkey>
  "created_at": <current time>
  "kind":       14
  "tags": [
    ["p", <receiver-1 pubkey>, <relay-url>],
    ["p", <receiver-2 pubkey>, <relay-url>],
    ["subject", <conversation title>]
  ],
  "content":    <message, plain text>
  -- no "sig" field. This object is never published as-is. --
}
Wire format - the full wrap chain
{
  "id": <usual hash>,
  "pubkey": wrapperPublicKey,           -- random, one-time key
  "created_at": randomTimeUpTo2DaysInThePast,
  "kind": 1059,                        -- gift wrap
  "tags": [
    ["p", receiverPublicKey, <relay-url>]      -- receiver
  ],
  "content": nip44.encrypt(
    {
      "id": <usual hash>,
      "pubkey": senderPublicKey,       -- real sender
      "created_at": randomTimeUpTo2DaysInThePast,
      "kind": 13,                      -- seal
      "tags": [],                      -- always empty
      "content": nip44.encrypt(
        unsignedMessageRumor,                -- kind 14, 15, or 7
        nip44.compute_conversation_key(senderPrivateKey, receiverPublicKey)
      ),
      "sig": <signed by senderPrivateKey>
    },
    nip44.compute_conversation_key(wrapperPrivateKey, receiverPublicKey)
  ),
  "sig": <signed by wrapperPrivateKey>
}
Warning!

Clients MUST verify that the pubkey on the kind:13 seal matches the pubkey inside the sealed rumor. Without this check, any sender can impersonate any other sender by editing the rumor's pubkey field before sealing it.

wrapperPrivateKey is a new keypair. A client generates one fresh, random keypair for every single message it wraps. A sender creates one gift wrap per receiver. It usually also creates one more gift wrap addressed to itself, so its own client can recover the conversation later.

Clients SHOULD randomize created_at by up to two days in the past, in both the seal and the gift wrap. Real message time stays hidden in the rumor. This stops a relay from grouping messages by timestamp and inferring who is talking to whom.

4 Message Kinds: Chat and File

Kind 14 — chat message. Its content MUST be plain text: no markup, no encoding. The id and created_at fields are required, computed the normal way even though the event stays unsigned.

Kind 14 tags
TagFormRule
p["p", <receiver pubkey>, <relay-url>]One per receiver. One or more required.
e["e", <parent kind-14 id>, <relay-url>]Present only on a reply. Names the direct parent message.
subject["subject", <conversation title>]Optional. Renames the room. See Section 2.
q["q", <event id or address>, <relay-url>, <pubkey, if a regular event>]Optional. Cites an event quoted inside content, using the NIP-21 nostr: scheme.

Kind 15 — file message. It sends an encrypted file by reference. The content field holds only the file's URL; the file itself is encrypted and hosted separately.

Kind 15 tags
TagMeaning
pOne per receiver, same as kind 14.
eDirect parent message, if this is a reply.
subjectOptional room name, same as kind 14.
file-typeMIME type of the file before encryption, for example image/jpeg.
encryption-algorithmAlgorithm used to encrypt the file. Only aes-gcm is defined today.
decryption-keyThe key the receiver needs to decrypt the file.
decryption-nonceThe nonce the receiver needs to decrypt the file.
xsha256 hash, hex-encoded, of the encrypted file.
oxsha256 hash, hex-encoded, of the file before encryption.
sizeOptional. Size of the encrypted file, in bytes.
dimOptional. Pixel size, as <width>x<height>.
thumbhashOptional. A thumbhash to show while the file loads.
blurhashOptional. A blurhash to show while the file loads.
thumbOptional. URL of a thumbnail, same aspect ratio, encrypted with the same key and nonce.
fallbackOptional. Zero or more backup URLs for the file, in case the main URL fails.
Tech Note

Source inconsistency: the kind 14 example's reply e tag has three elements, no marker: ["e", <id>, <relay-url>]. The kind 15 example's reply e tag has a fourth element, a "reply" marker: ["e", <id>, <relay-url>, "reply"]. This page restates both examples as written in source-nips/17.md. One 2025 change to this NIP is titled "removes the need for markers," which makes the kind 15 example's leftover marker look like an unfixed copy-paste artifact rather than an intended rule. Treat the unmarked, three-element form as current.

5 Publishing and DM Relay Lists

Kind 10050 tells other clients where to deliver DMs. It is a small, replaceable event that lists a user's preferred inbox relays.

Wire format - kind 10050 DM relay list
{
  "kind": 10050,
  "tags": [
    ["relay", "wss://inbox.nostr.wine"],
    ["relay", "wss://myrelay.nostr1.com"]
  ],
  "content": ""
  -- other fields, id/pubkey/created_at/sig, are the usual event fields --
}

The event MUST include one or more relay tags. Clients MUST publish gift wraps only to relays listed in the receiver's kind 10050 event. If a receiver has no such list, that receiver is not ready to receive messages. Clients should not try to send to them.

Client A Relay Client B from B's kind 10050 list ["EVENT", giftwrap] ["OK", id, true, ""] ["REQ","dm",{"kinds":[1059],"#p":[B]}] ["EVENT","dm", giftwrap] ["EOSE","dm"] Client B unwraps offline: gift wrap -> seal -> rumor.
Fig. 17-2 — Delivery. A publishes to relays from B's kind 10050 list. B subscribes with a #p filter and unwraps locally.

6 Relay Recommendations

Relays SHOULD protect metadata: serve kind:1059 events only to the pubkey named in the event's p tag. Relays enforce this with NIP-42 AUTH, requiring the requesting client to prove it controls that pubkey before the relay returns any results.

Clients SHOULD guide users to keep their kind:10050 list small, one to three relays. Clients SHOULD also spread that small list across as many separate relay operators as is practical, to avoid a single relay seeing every message.

7 Delete, Edit, and Disappearing Messages

NIP-59 already lets the p-tagged recipient of a gift wrap delete it. NIP-17 adds one more option: a client MAY wrap a kind:5 delete request in its own kind:1059 gift wrap, and send it to the recipient as part of the conversation. Clients SHOULD remove a deleted message from the conversation view, or mark it as deleted.

Clients MAY implement editing as a delete, followed by a new message published with the same timestamp as the original.

Clients MAY offer disappearing messages two ways: add an expiration tag (NIP-40) to each receiver's gift wrap, or simply skip generating a gift wrap addressed to the sender's own key. The expiration tag SHOULD also be set on the kind:13 seal, in case the seal ever leaks on its own.

8 Benefits, Limitations, and Spam

NIP-17's three-layer design buys real privacy properties. It also has real costs. Both matter when choosing this scheme.

Privacy and security properties
PropertyWhat it means
No metadata leakIdentities, real timestamps, kinds, and tags stay hidden from public view. Public data alone cannot link a sender to a receiver.
No public group identifiersNo shared queue, channel, or ID lets an outsider count or correlate all messages in one room.
No moderationNo admins, invitations, or bans exist at the protocol level.
No shared secretsNo secret needs to be known by every member, so no secret can leak from a careless member.
Fully recoverableAny client holding the user's private key can recover the full message history.
Optional forward secrecyUsers and clients may opt in to disappearing messages, described in Section 7.
Works on public relaysMessages flow through public relays without losing privacy. Private relays help further, but are not required.
Cold storageUsers may unilaterally share their messages with a separate key, kept only for DM backup and recovery.

The main limitation: a sender must send a separate gift wrap to every single receiver. A group chat with more than ten participants should use a scheme built for groups instead.

Tech Note

Gift wraps sign with random, throwaway keys. A relay cannot apply the usual anti-spam tools, reputation, web-of-trust, or per-key rate limits, because every gift wrap arrives from a new, unknown key. The recommended fix is the NIP-42 AUTH-based relay policy described in NIP-59's spam protection section: require a real, authenticated identity before the relay accepts or serves gift wraps.

9 Worked Example

Alice sends "Hola, que tal?" to Bob. Below is a full round trip: the plain rumor, and the two real gift wraps this NIP produces, one addressed to Bob and one addressed back to Alice, exactly as published in the NIP-17 source text (long ciphertext blobs shortened with "…").

Step 1 - unsigned rumor
{
  "pubkey": "6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93",
  "created_at": 1703015180,
  "kind": 14,
  "tags": [
    ["p", "918e2da906df4ccd12c8ac672d8335add131a4cf9d27ce42b3bb3625755f0788"]
  ],
  "content": "Hola, que tal?"
}
Step 2 - sealed with the sender's real key (kind 13)
{
  "pubkey": "6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93",
  "created_at": 1703015180,
  "kind": 13,
  "tags": [],
  "content": nip44.encrypt(rumor, conversation_key(sender, receiver)),
  "sig": <signed by sender>
}
Step 3 - gift wrap addressed to Bob (kind 1059)
{
  "id": "2886780f7349afc1344047524540ee716f7bdc1b64191699855662330bf235d8",
  "pubkey": "8f8a7ec43b77d25799281207e1a47f7a654755055788f7482653f9c9661c6d51",
  "created_at": 1703128320,
  "kind": 1059,
  "tags": [
    ["p", "918e2da906df4ccd12c8ac672d8335add131a4cf9d27ce42b3bb3625755f0788"]
  ],
  "content": "AsqzdlMsG304G8h08bE67dhAR1gFTzTckUUyuvndZ8L…U1Ob9fEJv/ibNuSfqL6E4BnN",
  "sig": "a3c6ce632b145c0869423c1afaff4a6d764a9b64dedaf15f170b944ead67227…c22bab"
}
Step 4 - gift wrap addressed back to Alice (kind 1059)
{
  "id": "162b0611a1911cfcb30f8a5502792b346e535a45658b3a31ae5c178465509721",
  "pubkey": "626be2af274b29ea4816ad672ee452b7cf96bbb4836815a55699ae402183f512",
  "created_at": 1702711587,
  "kind": 1059,
  "tags": [
    ["p", "44900586091b284416a0c001f677f9c49f7639a55c3f1e2ec130a8e1a7998e1b"]
  ],
  "content": "AsTClTzr0gzXXji7uye5UB6LYrx3HDjWGdkNaBS6BAX…lTLHiHMlTbarFOd6J/ybIb",
  "sig": "c94e74533b482aa8eeeb54ae72a5303e0b21f62909ca43c8ef06b0357412d6f…22bab"
}

Two gift wraps, near-identical in shape, carry the same rumor to two different keys: the receiver's, and the sender's own. A relay watching the wire sees two unrelated events, signed by two unrelated random keys. It cannot tell they are the same conversation.

10 Version History

NIP-17 has grown since 2024, from a two-party text chat into a scheme that also carries files and reactions. Notable changes, newest first:

  • 20262026-06Repo-wide cosmetic pass: code fences switched from jsonc to yaml; sections reordered for clarity. No rule changes.
  • 20262026-05Added the deletion section (a kind:5 delete wrapped in a kind:1059 gift wrap), and recommended NIP-42 AUTH as the standard fix for gift-wrap spam.
  • 20262026-04Added thumbhash as an optional preview field, alongside the existing blurhash.
  • 20252025-12Tagged the NIP as a relay-relevant document.
  • 20252025-10Refined wording throughout, and confirmed kind 7 reactions as a message rumor type alongside kind 14 and kind 15. A second change the same month clarified that clients must publish only to the receiver's kind 10050 relays.
  • 20252025-02Dropped the old reply-marker convention in favor of a plain e tag, and added q tags for citing events in content. Days earlier, kind 15 file messages were added.
  • 20242024-08 to 2024-12Small fixes: object formatting, JSON-to-JSONC code fences, and a clarification on field order.
  • 20242024-04fiatjaf added relay considerations and implementation notes, the same day the NIP landed.
  • Origin2024-04-24Vitor Pamplona published the seal-and-gift-wrap DM scheme as NIP-17, renumbered from an earlier draft that had circulated as NIP-24 (PR #686). The filename existed since the August 2023 repo-wide "nips over nostr" import, but held no NIP-17 content until this commit.

Original Proposal - Credits

AuthorVitor Pamplona
First commit2024-04-24 — “NIP-17 (old 24) Sealed Gift-Wrapped Messages for Private DMs and Small Group Chats” (PR #686)
Statusdraft optional relay
Source17.md on GitHub - full change history

11 See Also

  • NIP-01 — the base event format that every rumor, seal, and gift wrap builds on.
  • NIP-04 — the older, deprecated encrypted DM scheme that NIP-17 replaces.
  • NIP-44 — the versioned encryption used at both the seal and the gift-wrap layer.
  • NIP-59 — defines the rumor, seal, and gift-wrap kinds themselves, and their deletion and spam-protection rules.
  • NIP-40 — the expiration tag used for disappearing messages.
  • NIP-42 — relay AUTH, used to restrict gift-wrap delivery and to mitigate spam.
  • NIP-09 — event deletion, the base mechanism a wrapped kind:5 request relies on.
  • NIP-25 — defines kind 7 reactions, one of the three message rumor types.