Blossom killed NIP-96 and nobody noticed
- The file problem
- What NIP-96 got wrong
- What Blossom actually does
- How files survive a server shutdown
- Verify, don’t trust
- What the ecosystem looks like now
- What’s still rough
- Why this matters more than it sounds
The file problem
#nostr is good at text. You write a note, sign it, send it to some relays. If one relay goes down, the note exists on others. Your identity is a keypair. The protocol is decentralized by default.
Then you post an image.
That image has to live somewhere. Not on a relay, because relays store events, not binary files. On a web server. A regular, old-fashioned HTTP server with a URL you can point to.
NIP-96 was the protocol’s answer to this. It defined a REST API for uploading files to external servers and referencing them by URL. You upload a photo to files.example.com, get back a URL, and embed that URL in your note. Simple.
Also completely centralized. The file exists in one place, identified by one URL, controlled by one operator. If that server goes down, the image is gone. Your note still exists on relays, but it now points to a dead link. The text survives. The media doesn’t.
This is the same failure mode as every other platform. Twitter shuts down a third-party image host, millions of embedded images vanish. A Tumblr CDN migration breaks old URLs, years of posts lose their visuals. NIP-96 recreated this exact fragility inside a protocol that was supposed to be different.
What NIP-96 got wrong
The problem wasn’t the REST API. The problem was location-based addressing.
A NIP-96 URL like https://files.example.com/uploads/abc123.jpg tells you where a file is. It says nothing about what the file is. If the server replaces that file with something else, the URL still resolves. If the server goes offline, the URL breaks. There is no way for a client to verify that the bytes it received are the bytes the original author intended.
NIP-96 also didn’t compose well with the rest of #nostr. It sat outside the event graph. You couldn’t zap an image host for good service. You couldn’t report a file using Nostr’s reporting mechanisms. The upload server was a black box that happened to accept authentication via NIP-98 signed events, but otherwise had no connection to the Nostr protocol.
Discovery was another gap. NIP-96 used kind:10096 events for server lists, but there was no standard fallback mechanism. If the URL in your note died, clients had no protocol-level way to find the file elsewhere. The file was the URL. The URL was the file.
The NIPs repository now lists NIP-96 as “unrecommended: replaced by blossom APIs.” It happened quietly. No dramatic deprecation announcement. The ecosystem just moved.
What Blossom actually does
Blossom stands for “Blobs Stored Simply on Mediaservers.” It was created by hzrd149 and is defined in a series of BUDs (Blossom Upgrade Documents), with NIP-B7 as the formal Nostr integration spec.
The core idea is content-addressed storage. Instead of identifying files by where they live, Blossom identifies them by what they are. Every file is addressed by its SHA-256 hash. A JPEG that hashes to a1b2c3d4... is always a1b2c3d4..., regardless of which server stores it. The hash is the file’s identity.
The API is deliberately simple.
GET /<sha256> retrieves a file by its hash. HEAD /<sha256> checks if a file exists without downloading it. PUT /upload stores a new file and returns a blob descriptor. DELETE /<sha256> removes a file. PUT /mirror tells a server to copy a file from another server.
Authentication uses signed Nostr events. No separate account system. No API keys. You prove you own a #nostr identity by signing an authorization event, and the server decides whether to accept your upload based on that identity. The same keypair you use for posting notes also controls your file storage.
This is where it starts to feel like it belongs in Nostr rather than bolted onto it.
How files survive a server shutdown
This is the part that matters.
Every Blossom user publishes a kind:10063 event listing their preferred servers. This is BUD-03, and it’s the mechanism that makes everything else work. When a client encounters a URL that ends in a 64-character hex string, it recognizes that as a SHA-256 hash. If the original URL is dead, the client checks the author’s kind:10063 list and tries the same hash on their other servers.
The file doesn’t depend on the URL. The URL is a hint. The hash is the identity.
Say you host your images on blossom.band. You also set up mirroring (BUD-04) to a self-hosted backup server at home. Every upload goes to blossom.band first, and the server automatically copies it to your mirror. Both servers now have the file, addressable by the same hash.
If blossom.band disappears tomorrow, every Blossom-aware client checks your kind:10063 list, finds your backup server, and loads the image from there. Your notes are unbroken. No dead links. No lost media.
If both servers go down, you can upload your files to a new server, update your kind:10063 list, and everything resolves again. The hash hasn’t changed. The file is the same file. Only the location changed, and locations are just hints.
Compare this to NIP-96: the URL dies, the image dies, the note is broken forever. There is no fallback. There is no recovery. The file was the URL, and the URL is gone.
Verify, don’t trust
NIP-96 URLs could serve anything. A server at files.example.com/abc123.jpg could return a completely different image than what the author originally uploaded, and no client could detect the substitution. You trusted the server to serve the right file. That’s it.
Blossom flips this. The hash in the URL is a commitment. When a client downloads a file from a Blossom server, it can compute the SHA-256 hash of the received bytes and compare it to the hash in the URL. If they match, the file is exactly what the author referenced. If they don’t, the file has been tampered with or the server is broken.
The NIP-B7 spec says clients SHOULD verify this hash after download. Not all do yet, but the mechanism is there. It’s the same trust model #bitcoin uses for transactions: don’t trust the intermediary, verify the data.
This also makes it impossible to do a silent swap. A compromised or malicious Blossom server can’t substitute a different image for the one you uploaded, because the hash won’t match and any verifying client will reject it. With NIP-96, a compromised server could swap files silently and nobody would know.
What the ecosystem looks like now
Blossom has moved past the “interesting idea” phase. Real infrastructure exists.
Blossom.band is backed by nostr.build’s infrastructure with a global CDN. Each user gets a dedicated subdomain. Free uploads are capped at 20 MiB per file, paid plans go to 100 MiB. Blosstr runs a managed hosting service. For self-hosting, hzrd149 released Umbrel Blob Box, which runs on UmbrelOS. Several community-built servers exist in Go, TypeScript, and Rust.
On the client side, Primal, Amethyst, and noStrudel support Blossom uploads. Primal’s media storage runs entirely on the Blossom protocol. Their apps use the BUD-05 /media endpoint, which strips EXIF metadata from uploads before storage, a privacy feature baked into the spec rather than bolted on after the fact.
Developer tooling has caught up too. NDK-Blossom adds Blossom support to the Nostr Development Kit, including automatic server selection, URL healing (finding files on alternative servers when the original is down), and mirroring. The nostr-blossom Rust crate and blossom-client-sdk in JavaScript cover most implementation needs.
BUD-09 added a reporting endpoint. Clients can submit signed NIP-56 report events with file hashes, giving server operators a protocol-native way to handle content moderation. This was one of the composability gaps that NIP-96 never addressed.
What’s still rough
I’m not going to pretend Blossom has no problems.
Not every Nostr client supports it yet. Some still only speak NIP-96, and a few servers (like nostrcheck-server) support both protocols to bridge the gap. The migration is happening, but it’s not finished.
Storage economics are unsolved. Blossom servers need to pay for bandwidth and disk space. Free tiers exist, but they’re subsidized by operators or limited by upload caps. There’s no protocol-level payment mechanism. You can’t zap a Blossom server for storage the way you might zap a relay operator. Someone is paying for your files to exist, and that “someone” is usually running at a loss or charging through a separate subscription.
The community debate about whether Blossom should have been NIP-96 v2 is real. Critics point out that the two specs overlap heavily and that Blossom’s changes (different kind number for server lists, slightly different JSON response format) created unnecessary incompatibility. They argue the same goals could have been achieved by extending NIP-96 instead of replacing it. The counter-argument is that content-addressed storage is a fundamentally different model, and trying to retrofit it into NIP-96’s URL-based assumptions would have been worse than starting clean.
I lean toward the clean-break side. Retrofitting content-addressing into a location-addressed spec would have created a spec that was neither one thing nor the other. Sometimes you need to break compatibility to get the design right.
Why this matters more than it sounds
“Your images outlive the server” sounds like marketing copy. Here’s what it means in practice.
Every note you’ve ever posted with a NIP-96 image is a ticking clock. The server that hosts the file might run for ten years. It might shut down next month. You have no control over that timeline, and when it happens, part of your publishing history disappears.
With Blossom, the file’s identity is a mathematical fact. The SHA-256 hash of your image doesn’t change when you switch servers. It doesn’t change when you add mirrors. It doesn’t change when the original host goes offline. As long as the bytes exist somewhere, any client can find and verify them.
This is #decentralization applied to media, not just text. Nostr already got this right for events. Blossom extends the same principle to the files those events reference. Your notes and your images now follow the same trust model: sign it, distribute it, verify it.
NIP-96 was a reasonable first attempt at a hard problem. Blossom is the answer that actually fits the protocol’s architecture. The transition happened without fanfare, which is probably why it’s worth writing about. The most important infrastructure changes are usually the ones nobody notices until they need them.
#nostr #decentralization #bitcoin
Write a comment