NIP-17 and the hard problem of private messaging on public relays
- The paradox nobody talks about
- What NIP-04 got wrong
- Three layers of wrapping
- What’s hidden now
- The deliverability problem
- The adoption gap
- What NIP-17 doesn’t solve
- The right tradeoff for most conversations
The paradox nobody talks about
Every note you post to #nostr is public. That’s the point. Signed events, broadcast to relays, readable by anyone. The protocol was built for this. There is no access control layer, no permissions model, no concept of a private timeline. Events go out, relays store them, clients display them.
And then people want to send DMs.
This is not a small ask. You are trying to build private communication channels on top of infrastructure that was designed, from the ground up, to be public. The relays that store your events can read the metadata. The protocol that signs your notes also signs your messages. The same transparency that makes #nostr censorship-resistant makes #privacy an engineering problem with no clean solution.
NIP-17 is the current best attempt at solving this. It replaces the old NIP-04 DM system with a three-layer encryption scheme called gift wrapping. The improvement is real and significant. But it comes with tradeoffs that are worth understanding before you trust it with anything sensitive.
What NIP-04 got wrong
The original Nostr DM system, NIP-04, used kind 4 events with AES-256-CBC encryption. The content was encrypted. Everything else was not.
The sender’s pubkey sat right there on the event, in plaintext. The recipient’s pubkey was in a p tag, also in plaintext. The event kind was 4, which might as well have been a neon sign reading “this is a private message.” Timestamps were exact. Anyone monitoring a relay could build a complete social graph of who was messaging whom, when, how often, and in what pattern. They just couldn’t read the words.
The encryption itself had problems too. AES-256-CBC was used without a MAC, meaning there was no way to detect if a message had been tampered with in transit. This exposed it to CBC padding oracle attacks. The shared secret used the raw ECDH output without running it through a proper key derivation function. And because Nostr reuses the same keypair for everything, the encryption key between any two users was static. Same key, every message, forever.
No forward secrecy. No message authentication. Complete metadata exposure. The content was hidden, but the context was wide open.
NIP-04 was deprecated. It should have been.
Three layers of wrapping
NIP-17 replaces this with a scheme built on two other specs: NIP-44 for encryption and NIP-59 for the wrapping structure. The result is a three-layer system where each layer hides something specific.
Layer 1: The rumor. You write your message as a kind 14 event. Normal Nostr event structure, p tags for recipients, content in plaintext. But here’s the thing that matters: you don’t sign it. The event has no valid signature. This is deliberate. If the rumor ever leaks, nobody can prove you wrote it. Cryptographic deniability, built into the first step.
Layer 2: The seal. The unsigned rumor gets serialized to JSON and encrypted using NIP-44 (XChaCha20 + HMAC-SHA256, audited by Cure53 in 2023). This encrypted blob becomes the content of a kind 13 event, which you sign with your real key. The seal has no tags. No p tag, no e tag, nothing. A relay looking at a seal sees who signed it, but has no idea who it’s for or what’s inside.
Layer 3: The gift wrap. The seal gets encrypted again and placed inside a kind 1059 event. This outer layer is signed by a random, single-use ephemeral key. Not your key. A throwaway key generated for this one message. The gift wrap does have a p tag pointing to the recipient, because the relay needs to know who to deliver it to. But the relay has no idea who sent it, because the signature belongs to a key that will never be used again.
Both the seal and the gift wrap get randomized timestamps, shifted by up to two days from the actual send time. The real timestamp lives inside the rumor, which only the recipient can decrypt.
For group messages, the sender creates a separate seal and gift wrap for each participant. Same rumor inside, different wrapping for each person. The cost is O(n) per message, which matters for groups but works fine for one-on-one conversations.
One validation rule is non-negotiable: clients must verify that the pubkey on the kind 13 seal matches the pubkey on the kind 14 rumor. Skip this check and you open the door to impersonation, where anyone could forge a rumor claiming to be someone else and wrap it in their own seal.
What’s hidden now
The difference between NIP-04 and NIP-17 is not incremental. It’s a different category of protection.
| What a relay sees | NIP-04 | NIP-17 |
|---|---|---|
| Message content | Encrypted | Encrypted |
| Who sent it | Visible (pubkey on event) | Hidden (ephemeral key) |
| Who receives it | Visible (p tag) |
Partially hidden (gift wrap p tag) |
| When it was sent | Exact timestamp | Randomized +/- 2 days |
| That it’s a DM | Obvious (kind 4) | Not obvious (kind 1059 is opaque) |
| Deniability | None (signed by sender) | Yes (rumor is unsigned) |
Compare this to Signal’s sealed sender, which hides the sender’s identity from Signal’s servers using a similar ephemeral-key approach. Signal hides the sender but not the recipient (servers must route to someone). NIP-17 hides both the sender and partially obscures the recipient. Signal provides forward secrecy through the Double Ratchet protocol. NIP-17 does not.
The tradeoff is structural. Signal runs on centralized servers that can enforce sophisticated key management. #nostr runs on dumb relays that store and forward events. Forward secrecy requires key ratcheting, which requires state synchronization between participants. That’s hard to do on a protocol where messages might arrive at different relays, out of order, days apart. NIP-17 made the pragmatic choice: hide the metadata you can hide, accept that some #cryptography guarantees require architecture that Nostr doesn’t have.
The deliverability problem
Here’s where the design gets uncomfortable. You’ve hidden the sender behind an ephemeral key. You’ve randomized the timestamps. You’ve made every gift wrap look identical from the outside. How does the message actually get delivered?
NIP-17’s answer is kind 10050 events: DM inbox relay lists. You publish a replaceable event listing one to three relays where you want to receive DMs. Senders look up your kind 10050, find your inbox relays, and deliver gift wraps there.
This works, but it creates tension on several fronts.
Relays don’t love gift wraps. These are opaque encrypted blobs that can’t be indexed, searched, or moderated. They have zero public utility. A relay storing gift wraps is providing a service to users at its own cost, with no way to even verify the content isn’t spam. Some relays simply refuse to store kind 1059 events.
Spam filtering is nearly impossible. Every gift wrap comes from a unique ephemeral key. There’s no sender reputation to evaluate, no history to check, no rate limit that would catch an attacker generating throwaway keys. The mitigations are proof-of-work on the wrapper (NIP-13), payment-based access, or relay-level AUTH (NIP-42) that restricts who can submit gift wraps. None of these are widely deployed.
Then there’s the access control question. Relays should only serve kind 1059 events to the recipient listed in the p tag, verified through NIP-42 AUTH. Without this, anyone can request all gift wraps on a relay and attempt to decrypt them. In practice, not all relays enforce this.
The whole system works best when inbox relay lists are small (limiting metadata exposure) but widely discoverable (so senders can find them). That’s a real tension with no perfect resolution.
The adoption gap
NIP-17 is the standard. It’s in the spec. And the ecosystem is split on it.
Amethyst (Android) has supported NIP-17 since v0.94.0. So have web clients like Coracle, noStrudel, and 0xChat. If you’re using any of these, your DMs are gift-wrapped.
Damus (iOS) still uses NIP-04. This creates a specific, documented problem: Amethyst users send NIP-17 DMs that Damus cannot decrypt. Damus users send NIP-04 DMs that Amethyst can read (it supports both for backward compatibility). The result is one-way communication failure. You think you sent a DM. The other person never sees it. Nobody gets an error message.
For a protocol that prides itself on interoperability, this is a rough edge. Damus and Amethyst are the two most popular mobile clients. A user on one platform sending a DM to a user on the other has a coin flip’s chance of it working, depending on direction. The Nostrability project has tracked this as an open interop issue since early 2025.
What NIP-17 doesn’t solve
NIP-17 is a real improvement. It is not a complete solution. Some limitations are fundamental.
No forward secrecy. If your private key is compromised, every DM you’ve ever sent or received can be decrypted. Past, present, future. NIP-17 uses static ECDH keys, which means the conversation key between any two users never changes. Signal solves this with the Double Ratchet, generating new keys with every message exchange. Nostr’s architecture makes this impractical.
No post-compromise security. Related to the above: once a key is compromised, there’s no mechanism to recover. Signal’s ratchet means that even after a key leak, security is restored after a few messages. NIP-17 has no equivalent. The compromise is permanent until you rotate your entire identity.
Traffic analysis. Relay operators can’t read your messages, but they can see that your pubkey is receiving gift wraps. They know your IP address. They can correlate the timing and volume of encrypted events with your public activity. Academic researchers have demonstrated similar statistical disclosure attacks against Signal’s sealed sender. The problem is worse on #nostr because relay operators have more visibility than Signal’s servers do.
Group scaling. Every message requires a separate seal and gift wrap per recipient. A 50-person group means 50 encryption operations and 50 events per message. The spec acknowledges this doesn’t scale past roughly 100 participants.
The spec is honest about its limits. NIP-44 itself states that for situations where forward secrecy is required, users should use dedicated encrypted messaging software and only use Nostr for exchanging contact information. That’s not a failure of NIP-17. That’s an acknowledgment that a relay-based public protocol has structural constraints that no amount of wrapping can fully overcome.
For what it’s worth, NIP-EE is an emerging proposal that adapts the MLS (Message Layer Security) protocol for Nostr. MLS provides actual forward secrecy and post-compromise security, and scales better for groups. It’s early, but it represents the next step for users who need guarantees beyond what gift wrapping can provide.
The right tradeoff for most conversations
NIP-17 does not make Nostr DMs as private as Signal messages. It was never going to. Signal runs dedicated infrastructure designed for private messaging. Nostr is a public event protocol that happens to support DMs.
What NIP-17 does is eliminate the most embarrassing problems with the old system. Your DMs no longer broadcast who you’re talking to. The timestamps are noise. The event type is hidden. The sender is hidden. The encryption is modern and audited. Deniability is built in.
For casual conversations, coordination, and the kind of messaging that makes up 99% of DM traffic, NIP-17 is the right tradeoff. It hides what can be hidden given the architecture, and it’s honest about what it can’t.
If you need forward secrecy, use Signal. If you need to send a DM on Nostr without announcing it to every relay operator, NIP-17 does the job. Just make sure the person you’re messaging isn’t on a client that’s still stuck on NIP-04.
Write a comment