The State of NIP-70: Why We're Rolling Back Protected Events in White Noise
by jgmontoya
At White Noise, we’re building secure, private messaging on top of the Marmot protocol which combines Nostr and MLS (Messaging Layer Security). A fundamental building block of MLS is the KeyPackage, a pre-published bundle containing a public init_key that allows other users to add you to a group asynchronously. Critically, this is done in a way that preserves privacy: external observers (including relays) cannot determine which groups a user has joined or who they’re communicating with.
Why does this matter for security?
From a cryptographic perspective, KeyPackages are a liability. If the private counterpart to an init_key sits on a device for too long, it increases the risk of a “harvest now, decrypt later” attack. If an attacker records your encrypted “Welcome” messages today and steals your private keys years from now, they could decrypt your past group joins.
To prevent this, Forward Secrecy is mandatory. Our implementation (MDK) actively manages this lifecycle:
- Consumption: When a KeyPackage is used to join a group, we zeroize (securely delete) the private
init_keyfrom the device. - Rotation: We publish fresh KeyPackages to ensure new invites can always come through.
- Cleanup: Crucially, we attempt to delete the old, spent KeyPackages from relays (using NIP-09 Kind 5 deletions) so they don’t linger as “ghost” invites that no longer work.
To ensure these deletion requests are secure (and to prevent unauthorized parties from republishing these critical packages), we turned to two specific Nostr Improvement Proposals: NIP-70 (Protected Events) and NIP-42 (Authentication).
A Quick Primer
For those less familiar with the depths of these Nostr protocol specs:
- NIP-70 (Protected Events): This standard allows a user to “protect” an event by adding a special
-tag. It signals to relays that only the original author should be allowed to publish this event, preventing third-party republishing. It’s a defense against griefing and unauthorized replacements. - NIP-42 (Authentication): This is the handshake mechanism. When a relay sees a protected event, it shouldn’t just take the client’s word for it. It should challenge the client to prove their identity via a cryptographic challenge-response flow.
Ideally, these two work in tandem: NIP-70 sets the rule (“Protect this!”), and NIP-42 provides the key (“I am allowed to edit this”).

The Reality Check
Recently, we started seeing issues where our MLS key packages (Kind 443) were being rejected by relays. We decided to investigate thoroughly. We built a custom tool to probe relay behavior specifically around NIP-70 and NIP-42.
The source code for our investigation tool is here: marmot-protocol/mdk (branch: inv/nip70-relay-tests).
What We Found
The results were disappointing. While NIP-70 allows relays to reject protected events by default, it also provides a clear path forward: implement AUTH and accept writes from verified authors. When we tested major public relays (including Damus, Primal, and nos.lol), none of them had taken that step:
- Rejection without Recourse: The relays rejected the events with messages like
blocked: event marked as protected. - No Auth Challenge: Crucially, none of these relays initiated the NIP-42
AUTHflow. They simply followed the spec’s default: reject outright.
Without the AUTH challenge, the client has no way to prove it is the owner of the key. The door is simply shut.
The “Do Nothing” Solution
This is frustrating. The protocol provides the tools for secure key lifecycle management: NIP-70 for protection, NIP-42 for authentication. But without relay support, those tools are useless. We’re left choosing between security features that don’t work and no security features at all.
As discussed in our GitHub issue #168, we are forced to disable NIP-70 protection by default in our reference implementation for now.
While the Marmot Protocol Specification (MIP-00) already treats the protected (-) tag as optional to allow for flexibility, the Marmot Dev Kit (MDK) previously enforced it by default to maximize security. We are now aligning the implementation with the reality of the relay ecosystem.
This change is implemented in MDK Pull Request #173. The standard create_key_package_for_event function in MDK now returns a Vec<Tag> without the protected tag. For users who still want to enable protection (perhaps on relays that support it), we’ve introduced create_key_package_for_event_with_options, which accepts a protected boolean flag.

Make no mistake: this is a step backward. We’re removing a security feature because the ecosystem can’t support it yet. But we can’t build a reliable messaging protocol if users can’t publish their key packages to the relays they actually use. Pragmatism wins, reluctantly.
A Call to Action for Relay Operators
NIP-70 and NIP-42 together would enable important security properties for Nostr, if relays implemented them. Ephemeral data and user-controlled deletion are security features, not nice-to-haves.
Right now, most relays have chosen the path of least resistance: reject protected events outright rather than implement AUTH. We’d like to see that change. We’d encourage relay operators to consider implementing the full AUTH flow:
- See a
protectedevent. - Send an
AUTHchallenge. - Accept the write once the client proves ownership.
Until that happens, we’ll keep building around the limitations. But we shouldn’t have to. The protocol is ready. The relays aren’t.
Back