This NIP profile standardizes encrypted binary attachments (images, audio, video, documents) for private Nostr messaging only: NIP‑17 direct messages and MLS groups. It defines:
Public, unencrypted tags (attach/eattach) are explicitly out of scope for this profile.
Clients and servers need a consistent, interoperable, privacy‑preserving mechanism for sharing files in private contexts. This document scopes the solution to encrypted‑only delivery via NIP‑17 (1:1) and MLS (1:group), which matches our deployment and avoids ambiguity and leakage associated with public note tags.
Attachment parameters MUST live inside the DM’s encrypted content (not tags). The DM plaintext embeds a normalized JSON array of attachment objects:
{
"type": "message",
"text": "optional user text",
"attachments": [
{
"url": "https://storage.example/enc/blob",
"ct": "image/jpeg",
"size": 23011,
"sha256": "<hex_of_ciphertext>",
"fn": "photo.jpg",
"enc": {
"mode": "dm",
"algo": "A256GCM",
"k": "<b64-32-bytes>",
"iv": "<b64-12-bytes>",
"t": "<b64-16-bytes>"
},
"alt": "a cat",
"blurhash": "..."
}
]
}Receiver processing: 1) Decrypt the DM per NIP‑17.
2) Fetch the ciphertext bytes from url.
3) Verify sha256 over ciphertext.
4) Decrypt with enc.k/iv/t.
5) Render using ct, fn, alt, and
optional hints (e.g., blurhash).
Notes: - The size field SHOULD reflect ciphertext
length.
- Clients SHOULD cache both ciphertext and decrypted plaintext for
efficient re‑rendering.
For MLS application messages, the attachment AEAD key and nonce are derived via the MLS exporter; no symmetric key material is placed in relay‑visible metadata.
Key/nonce derivation (normative): - key =
MLS.exporter(label=“attachment”, context=concat(epoch, “|”, ctx),
length=32)
- nonce = MLS.exporter(label=“attachment-nonce”, context=concat(epoch,
“|”, ctx), length=12)
Where ctx is a stable, mutually known identifier for
this attachment (e.g., server blobId or a message‑scoped
attachmentId). Publishers MUST include enough metadata for
receivers to compute the same ctx.
Attachment metadata embedded in or adjacent to the MLS application message SHOULD include:
{
"url": "https://storage.example/enc/blob",
"ct": "image/jpeg",
"size": 23011,
"sha256": "<hex_of_ciphertext>",
"fn": "photo.jpg",
"enc": {
"mode": "mls",
"algo": "A256GCM",
"t": "<b64-16-bytes>",
"mls": { "group_id": "<groupId>", "epoch": 42, "ctx": "<blobId|attachmentId>" }
}
}Receiver processing: 1) Use MLS state for
group_id/epoch to derive key and nonce with
the exporter and ctx.
2) Fetch ciphertext, verify sha256, then decrypt with
derived key/nonce and verify auth tag t.
sha256 over ciphertext before
decrypt/render.alt.imeta or
any relay‑visible metadata.{
"type": "message",
"attachments": [
{
"url": "https://cdn.example/enc/xyz",
"ct": "image/jpeg",
"size": 23011,
"sha256": "55aa...",
"fn": "photo.jpg",
"enc": { "mode": "dm", "algo": "A256GCM", "k": "...", "iv": "...", "t": "..." }
}
]
}{
"url": "https://cdn.example/enc/xyz",
"ct": "video/mp4",
"size": 8329001,
"sha256": "2f3a...",
"fn": "talk.mp4",
"enc": {
"mode": "mls",
"algo": "A256GCM",
"t": "....",
"mls": { "group_id": "deadbeef", "epoch": 42, "ctx": "blob:e3b0c442..." }
}
}