Skip to content

NIP-94: file encryption - #2437

Open
alexgleason wants to merge 1 commit into
nostr-protocol:masterfrom
alexgleason:nip94-encrypt
Open

NIP-94: file encryption#2437
alexgleason wants to merge 1 commit into
nostr-protocol:masterfrom
alexgleason:nip94-encrypt

Conversation

@alexgleason

Copy link
Copy Markdown
Member

It would be nice to encrypt files uploaded to public Blossom servers. Then the server operator doesn't even know what they are.

This copies the NIP-17 encryption properties into NIP-94 so it can be used in imeta too.

@vitorpamplona

vitorpamplona commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

But then everybody will see the secret, and it won't be private anymore, right? What's the point?

@alexgleason

Copy link
Copy Markdown
Member Author

But then everybody will see the secret, and it won't be private anymore, right? What's the point?

It requires 2 pieces of information to view the file. That means the file is not viewable by direct URL. Therefore things like DMCA takedown notices will require gymnastics that server operators might not even be obligated to perform. It makes it safer to host free public infrastructure.

@staab

staab commented Aug 12, 2026

Copy link
Copy Markdown
Member

I tried this at one point. Then switched to auth-protected blossom. Then dropped it all because the hash might as well be a password. This approach looks fine though, I have no objection to it being added. Your point about operators not knowing what something is probably worth something.

alexgleason added a commit to soapbox-pub/ditto that referenced this pull request Aug 12, 2026
Implements the encryption properties proposed for NIP-94 in
nostr-protocol/nips#2437: `encryption-algorithm` (aes-gcm),
`decryption-key`, and `decryption-nonce`, in flat kind-1063 tags and in
`imeta`. When a file is encrypted, `m` describes the plaintext, `x` is
the ciphertext hash, and `ox` is the plaintext hash.

The blob on the media server is ciphertext, so we fetch it, decrypt it
in the browser, and hand renderers an object URL. Decryption fails
closed: `ox` is verified after decrypting so a swapped blob is rejected,
and a file we can't decrypt renders a notice rather than falling back to
its URL, which would only paint garbage. Object URLs are revoked when the
source changes or the component unmounts.

Wired into the inline images, galleries, lightbox, and video/audio
embeds in notes; the kind-1063 card; and the profile media collage.
Thumbnails share the file's key and nonce but are separate blobs, so
`companionEncryption` drops the `ox` and `m` that describe the file
itself — otherwise a perfectly good poster fails verification.

Downloads decrypt first. Saving the ciphertext would hand the user a blob
they have no way to open, and the content-addressed name has no
extension, so one is derived from the decrypted MIME type.

Encrypting on upload is not implemented — this is the display half.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants