pull down to refresh

The paper and slides from the recent talk at Blackhat in Vegas about practical attacks on Nostr have been published.

Overview

What is Nostr?
Nostr is an open-source, decentralized social-networking protocol where clients publish signed events to one or more relay servers. Conceived by @fiatjaf in 2019, it exceeded 1.1 million registered users and 38 K weekly active users (Oct 2024). High-profile supporters—such as Jack Dorsey’s funding and Edward Snowden’s adoption—boosted visibility, while popular clients like Damus (iOS) and Amethyst (Android) have ≥100 K downloads each. Specifications, called Nostr Implementation Possibilities (NIPs), outline features including signature verification and encrypted direct messages (DMs, NIP-04); nonetheless, sizeable gaps between the texts and real-world implementations create security risk.

Results — Our Security Findings

This paper presents the first in-depth security analysis of the Nostr protocol and its popular client implementations. Our research methodology combined specification-level analysis of the Nostr NIP documents, manual code analysis of leading client implementations, dynamic testing of encrypted DM and profile handling flows, and development of proof-of-concept exploits to validate each discovered vulnerability.
Findings:
  1. Key-replacement impersonation caused by missing public-key authenticity checks.
  2. Event forgery attacks where several clients omit signature verification.
  3. Full DM forgery that combines AES-CBC without a MAC and poor key separation.
  4. Plain-text recovery of encrypted DMs by chaining CBC malleability with link previews.
  5. Inadequate cache search (Client cache poisoning) that hijacks Bitcoin tips or alters profile data.
All attacks are reproducible with our publicly available proof-of-concept code.

Disclosure

2023-06-30 Reported DM-forgery PoC & patch to Damus v1.5(8) 2023-07-01 Reported cache-poisoning bug to Damus 2023-07-15 Submitted additional attacks (Appendix E.4) 2023-11 Damus released v1.6(29) removing vulnerable cache code 2023-12 Found vulnerability on Plebstr, FreeFrom, Damus v1.6, Iris(iOS) 2023-12 Iris moves to another implementation (Snort) and the issues have been fixed (Independent works) 2024-01-23 – 02-02 Coordinated disclosure to Plebstr, FreeFrom, Damus v1.6, Iris(iOS) 2024-02 Patched FreeFrom (v1.3.6) has been released in February 2024 2024-07 Plebstr has been rebranded as Openvibe and removing vulnerable DM feature 2024-08 Reported Plaintext Recovery on DM Exploiting Link Preview 2025-05-07 Published this website

Proof of Concept

The Proof-of-concept code and accompanying instructions for Open Science are available on GitHub. https://github.com/crypto-sec-n

FAQ

Q1 Which clients are affected?

Past version of Damus (iOS), Iris (iOS), FreeFrom (iOS/Android), and Plebstr. See the details on Disclosure.

Q2 I’m a user—what can I do right now?

  • Update to the latest version of your client.
  • Disable incoming link previews in your client.
  • Switch to a client that verifies every event signature. The starting point of our main attacks was flawed client-side signature verification. We observed that the combination of signature verification flaws with other cryptographic and application implementation issues enabled Plaintext Recovery, DM forgery, profile forgery, and Bitcoin hijacking; using a client with correct signature checks mitigates these risks.
  • Avoid adding untrusted relays to your relay list.
  1. Mandatory client-side signature verification for all event.
  2. Deploy out-of-band public-key authentication or a Key-Transparency service.
  3. Migrate encrypted DMs from NIP-04 to NIP-44 (versioned AEAD payloads; reference XChaCha20-Poly1305 v2) and adopt NIP-17 + NIP-59 for private DMs to achieve authenticity, integrity, and recipient-metadata confidentiality.
  4. Re-calculate the id field before consulting any cache; never trust an id supplied by the relay.
  5. Generate link previews on the sender side. Receiver-side previews combine an integrity-flawed cipher with external HTTP fetches, enabling plaintext-recovery attacks. Previous studies also show that receiver-side previews leak the recipient’s IP address and message-open timing. Eliminating receiver-side fetching removes both confidentiality and privacy risks.

Q4 I’m a client developer—If the relay already checks signatures, can clients skip verification to save battery?

No. Relays are untrusted by design:
  • A malicious relay can feed forged events that pass unchecked on the client.
  • The NIP specification does not guarantee that every relay performs verification.
  • Existing apps (Amethyst, Iris Web) demonstrate that full verification incurs negligible delay; aggregated-signature schemes (e.g., BLS) can reduce cost even further.
  • When caching events, first verify them successfully and re-compute the id from the event payload each time you load it; do not rely solely on a relay-supplied or previously cached id. Vulnerable clients followed the “cache-after-verify” rule but still failed integrity checks because they cached only the id and skipped this re-computation step.
Bottom line: Don’t trust — verify on the client.

Q5 I’m a NIPs contributor—how should I improve the specification?

  • Enforce signature verification in NIP-01 Specify that client must verify every event’s signature and drop invalid events, and emphasize that do not trust the relay servers.
  • Normative guidance for link previews Add a section requiring sender-side preview generation (e.g. summarization, OGP fetch) and prohibit receiver-side HTTP/HTTPS fetches for previews.
  • id recalculation before caching Specify that any cache lookup must re-compute the event id from the payload rather than trusting a relay-supplied or previously cached value.
  • Public-key authenticity Introduce normative text for out-of-band key authentication or a Key Transparency service to ensure clients can trust public keys.
  • Explicit security goals and adversary model Define formal security goals—confidentiality, integrity, authenticity—and specify the adversary assumptions unique to distributed SNS.
  • Mandatory NIP baseline and implementation guidelines Clarify which NIPs form the minimum secure baseline and provide normative guidelines (e.g., signature verification timing, cache behavior) to close specification–implementation gaps.
  • Sub-protocol interaction and key separation Ensure that related sub-protocols (e.g., NIP-04 vs. NIP-46) are not duplicated without clear key separation, and document how their interactions must be handled safely.
Our attacks arose from a combination of specification flaws and implementation bugs, most of which could be addressed by fixing implementations alone. However, we believe improving the specification is also necessary, because our findings paradoxically suggest that stronger security guarantees in the spec can help prevent these implementation mistakes.

Team

Hayato KIMURA (NICT / The University of Osaka) Ryoma ITO (NICT) Kazuhiko MINEMATSU (NEC) Shogo SHIRAKI (University of Hyogo) Takanori ISOBE (The University of Osaka)
100 sats \ 5 replies \ @ek OP 4h
If I understand this right, this doesn't allow you to recover the plaintext for any message. It must be a link or not contain any whitespace.
It works by changing the first encrypted block of a message into a link to the attacker's server, and when the recipient's client fetches the link preview, it appends the rest of the message to the URL, but only up to the next whitespace.
Cool, but not arbitrary plaintext recovery afaict
reply
100 sats \ 4 replies \ @optimism 3h
This only works when message signature isn't verified?
reply
100 sats \ 3 replies \ @ek OP 2h
Mhh, good point, I would think so
But if the client uses cache-after-verify, then it won't verify the signature again for the same event id
reply
100 sats \ 2 replies \ @optimism 2h
Based on my understanding of NIP-59, the correct approach for unwrap, unseal, render would be:
  1. Check the sig on the kind 1059 (and the p tag), if no match against the given pubkey then discard because it's spoofed.
  2. Decrypt the content of the kind 1059, this results in a json string of a kind 13 and not an url.
  3. Check pubkey to be a known sender and then the sig on the decrypted kind 13 message to match that key, if no match then then discard 1 - you now have authenticated the entire message including pubkey and content to be from a known contact.
  4. Decrypt the sealed kind n (often kind 14) from the previously decrypted kind 13, which isn't signed, but that's ok because we've authenticated the seal.
PS: Always turn off link previews if you can, and if you can't just realize you don't have any privacy.

Re:
But if the client uses cache-after-verify
Cache what? That any message from a pubkey no longer needs to be verified? What's this feature?

Footnotes

  1. And notify your contact and everyone that wants to listen because this is a spoofing attack! Be the Karen!
reply
100 sats \ 1 reply \ @ek OP 2h
Re:
But if the client uses cache-after-verify
Cache what? That any message from a pubkey no longer needs to be verified? What's this feature?
It's a vulnerable performance optimization. See Q4 in FAQ above:
When caching events, first verify them successfully and re-compute the id from the event payload each time you load it; do not rely solely on a relay-supplied or previously cached id. Vulnerable clients followed the “cache-after-verify” rule but still failed integrity checks because they cached only the id and skipped this re-computation step.
reply
75 sats \ 0 replies \ @optimism 1h
I think the main cause of this is the hash being included in the message. That's the design error that puts bad ideas into people's heads.
I guess they kind of address it but probably the best thing would be to delete it from the message altogether.