From e31556163041ef95bab23d441c9a2d4ed4d1f9ee Mon Sep 17 00:00:00 2001 From: jab-r Date: Sat, 23 Aug 2025 13:47:17 -0400 Subject: [PATCH 1/5] Add NIP-XXX: Binary Attachments for Notes and DMs --- nip-xxx-binary-attachments.md | 179 ++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 nip-xxx-binary-attachments.md diff --git a/nip-xxx-binary-attachments.md b/nip-xxx-binary-attachments.md new file mode 100644 index 00000000..dd72ef4b --- /dev/null +++ b/nip-xxx-binary-attachments.md @@ -0,0 +1,179 @@ +--- +nip: XXX +title: Binary Attachments for Notes and DMs +author: Jonathan Borden (jonathan@@openhealth.org) +status: Draft +type: Standards Track +created: 2025-08-23 +license: CC0-1.0 +--- + +## Abstract + +This NIP standardizes how Nostr events reference binary attachments (images, audio, video, documents), supporting both unencrypted and encrypted forms. It defines: + +- Tag-based referencing for public events +- A JSON structure for private DMs that keeps encryption secrets private +- A per-attachment symmetric encryption scheme (AES-256-GCM) with normative sizes +- Integrity fields and basic metadata for rendering +- Optional alignment with existing NIPs (NIP-17, NIP-94, NIP-96, NIP-98) + +## Motivation + +Today, clients handle attachments inconsistently. Public events often embed arbitrary tags; private DMs sometimes inline URLs or bespoke JSON. This NIP provides a consistent, privacy-preserving way to: + +- Attach unencrypted files to public notes +- Attach encrypted files whose decryption keys are delivered privately (via DMs) or embedded inside DMs +- Ensure interoperability by keeping metadata normalized (MIME, size, filename, integrity) + +## Rationale + +- Nostr events should remain lightweight; binary bytes should live off-relay in HTTP-accessible storage. +- Unencrypted public attachments are simple URLs plus integrity metadata. +- Encrypted attachments use a per-attachment symmetric key to avoid key reuse and allow granular sharing. +- For public notes, encryption keys should not be exposed in cleartext (or, if made public intentionally, considered “obfuscated” not secure). +- For DMs, keys live inside encrypted content; tags remain metadata-only. + +## Definitions + +- **Attachment**: A binary resource referenced by an event. +- **Ciphertext attachment**: The uploaded bytes are encrypted; clients must decrypt locally to render. +- **Per-attachment key**: A random 32-byte AES-256 key generated uniquely for each attachment. + +## Specification + +### 1. Unencrypted attachments (public notes) + +Clients MAY attach resources using `attach` tags: + +``` +["attach", "", "sha256=", "m=", "size=", "fn=", "alt=", "dim=", "blurhash=<...>"] +``` + +**Required:** url, sha256, m, size +**Recommended:** fn, alt, dim, blurhash + +Clients MUST verify sha256 before rendering. + +### 2. Encrypted attachments + +**Cipher:** AES-256-GCM +- key: 32 bytes, base64 +- iv: 12 bytes, base64 +- tag: 16 bytes, base64 +- Ciphertext: raw GCM ciphertext at URL + +Integrity: sha256 MUST be computed over ciphertext. + +#### 2.1 Public notes (`eattach` tags) + +``` +["eattach", "", "sha256=", "m=", "size=", "fn=", "algo=A256GCM", "ekref=", "alt="] +``` + +- `algo` MUST be `"A256GCM"`. +- `ekref` points to where the decryption key is conveyed (DM, URL, or replaceable event). +- Including `k/iv/t` directly in tags is discouraged (no confidentiality). + +#### 2.2 Private DMs (NIP-17) + +Keys MUST live inside encrypted content, not tags. +Plaintext before DM encryption: + +```json +{ + "type": "message", + "text": "optional message", + "attachments": [ + { + "url": "https://storage.example/encrypted/blob", + "ct": "image/jpeg", + "size": 23011, + "sha256": "", + "fn": "photo.jpg", + "enc": { + "algo": "A256GCM", + "k": "", + "iv": "", + "t": "" + }, + "alt": "a cat" + } + ] +} +``` + +### 3. Relationship to Existing NIPs + +- **NIP-17 (DMs):** Keys/attachments inside encrypted payload. +- **NIP-94 (File Metadata):** Optional companion metadata. +- **NIP-96 (HTTP File Storage):** Compatible with presigned uploads. +- **NIP-98 (HTTP Auth):** May secure uploads/downloads. + +### 4. Client Behavior + +- Verify sha256 before render +- Show filename/thumbnail +- Cache ciphertext and decrypted plaintext +- Respect accessibility (`alt`) + +### 5. Security Considerations + +- Do not leak keys in public tags (no confidentiality). +- Always verify sha256 and GCM auth tag. +- Apply quotas/content scanning on downloads. + +## Examples + +### Public note with two attachments + +```json +{ + "kind": 1, + "content": "trip photos", + "tags": [ + ["attach","https://cdn.example/a.jpg","sha256=...","m=image/jpeg","size=24567","fn=beach.jpg","alt=beach","dim=1920x1080"], + ["attach","https://cdn.example/itinerary.pdf","sha256=...","m=application/pdf","size=90123","fn=itinerary.pdf"] + ] +} +``` + +### Public note with encrypted video + +```json +{ + "kind": 1, + "content": "members-only video (see your DM for the key)", + "tags": [ + ["eattach","https://cdn.example/enc/v1/xyz","sha256=...","m=video/mp4","size=8329001","fn=talk.mp4","algo=A256GCM","ekref=event:9b3e..."] + ] +} +``` + +### DM carrying encrypted attachment + +```json +{ + "type": "message", + "attachments": [ + { + "url": "https://cdn.example/enc/xyz", + "ct": "image/jpeg", + "size": 23011, + "sha256": "...", + "fn": "photo.jpg", + "enc": { + "algo": "A256GCM", + "k": "...", + "iv": "...", + "t": "..." + } + } + ] +} +``` + +## Implementation Status + +- Reference client implementation: **loxation-sw** +- Supports per-attachment AES-256-GCM, presigned upload/finalize, and UI rendering. From a0f0162947daff68ba11fe500c97c229b6ec3c6d Mon Sep 17 00:00:00 2001 From: jab-r Date: Sat, 23 Aug 2025 13:50:37 -0400 Subject: [PATCH 2/5] draft entry for Binary Attachments --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2b305a95..b1330f4d 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos - [NIP-B7: Blossom](B7.md) - [NIP-C0: Code Snippets](C0.md) - [NIP-C7: Chats](C7.md) +- [NIP-XXX: Binary Attachments | DRAFT](nip-xxx-binary-attachments.md) ## Event Kinds | kind | description | NIP | From befaa2882dda8d01f998b85dec90fba78c0588a6 Mon Sep 17 00:00:00 2001 From: jab-r Date: Tue, 26 Aug 2025 11:29:06 -0400 Subject: [PATCH 3/5] Revise author info and improve encryption details --- nip-xxx-binary-attachments.md | 131 +++++++++++++++++++++++++++++++--- 1 file changed, 120 insertions(+), 11 deletions(-) diff --git a/nip-xxx-binary-attachments.md b/nip-xxx-binary-attachments.md index dd72ef4b..43de89b6 100644 --- a/nip-xxx-binary-attachments.md +++ b/nip-xxx-binary-attachments.md @@ -1,7 +1,7 @@ --- nip: XXX title: Binary Attachments for Notes and DMs -author: Jonathan Borden (jonathan@@openhealth.org) +author: Jonathan Borden (jonathan@loxation.com) status: Draft type: Standards Track created: 2025-08-23 @@ -77,8 +77,17 @@ Integrity: sha256 MUST be computed over ciphertext. #### 2.2 Private DMs (NIP-17) -Keys MUST live inside encrypted content, not tags. -Plaintext before DM encryption: +Keys MUST live inside the DM’s encrypted content, not tags. +To avoid exposing symmetric keys in plaintext, this NIP specifies an envelope-wrapped key for DMs: + +- Payload encryption: AES-256-GCM (per-attachment K) +- Key wrapping: X25519-HKDF-AESGCM + - Sender generates an ephemeral X25519 keypair (epk, esk) + - Shared secret s = X25519(esk, recipient_static_x25519_pub) + - wrapKey = HKDF-SHA256(s, info="attachment-key-wrap", salt=random16) + - ek = AES-GCM-Encrypt(wrapKey, nonce=random12, K) + +Plaintext (before NIP‑17 DM encryption) embedding the metadata: ```json { @@ -86,16 +95,23 @@ Plaintext before DM encryption: "text": "optional message", "attachments": [ { - "url": "https://storage.example/encrypted/blob", + "url": "https://storage.example/enc/blob", "ct": "image/jpeg", "size": 23011, "sha256": "", "fn": "photo.jpg", "enc": { + "mode": "dm", "algo": "A256GCM", - "k": "", "iv": "", - "t": "" + "t": "", + "ek": "", + "epk": "", + "wrap": { + "alg": "X25519-HKDF-AESGCM", + "nonce": "", + "salt": "" + } }, "alt": "a cat" } @@ -103,12 +119,77 @@ Plaintext before DM encryption: } ``` +On receive, clients decrypt the DM, unwrap K using their static X25519 private key and epk, then decrypt the blob with K/iv/t. No plaintext symmetric keys appear in metadata. + +#### 2.3 MLS group attachments + +For MLS groups, the attachment AEAD key and nonce are derived from the MLS group secret and epoch via the MLS exporter (no ek/epk in metadata): + +- key = MLS.exporter(label="attachment", context=concat(epoch, "|", blobId or messageId), length=32) +- nonce = MLS.exporter(label="attachment-nonce", context=concat(epoch, "|", blobId or messageId), length=12) + +Metadata embedded in the MLS application message (or carried adjacent as client policy) SHOULD include: + +```json +{ + "url": "https://storage.example/enc/blob", + "ct": "image/jpeg", + "size": 23011, + "sha256": "", + "fn": "photo.jpg", + "enc": { + "mode": "mls", + "algo": "A256GCM", + "iv": "", + "t": "", + "mls": { "group_id": "", "epoch": 42 } + } +} +``` + +Receivers derive the same key/nonce via exporter using (groupId, epoch, context) and decrypt the blob. No key material is placed in metadata. + ### 3. Relationship to Existing NIPs -- **NIP-17 (DMs):** Keys/attachments inside encrypted payload. -- **NIP-94 (File Metadata):** Optional companion metadata. -- **NIP-96 (HTTP File Storage):** Compatible with presigned uploads. -- **NIP-98 (HTTP Auth):** May secure uploads/downloads. +#### 3.1 NIP-17 (DMs) +- Placement: For private messages, all attachment details that include encryption material MUST live inside the DM’s encrypted content (see 2.2). Do not place keys in public tags. +- Rendering: Receivers decrypt the DM per NIP‑17, fetch bytes, verify sha256 over ciphertext, then decrypt with enc.k/iv/t. + +#### 3.2 NIP-92 (Media Attachments) — Interoperability +- Purpose: NIP‑92 defines “imeta” tags that annotate media URLs present in the event content. Many media‑focused clients render using imeta. +- Coexistence with NIP‑XXX: + - attach/eattach MAY be used alongside NIP‑92 imeta in the same event. + - When the event content contains a media URL, publishers SHOULD include a corresponding imeta tag so NIP‑92‑aware clients render consistently. + - When both attach/eattach and imeta are present, fields SHOULD be kept consistent. +- Field mapping (informational): + - url (positional in attach/eattach) ↔ imeta: "url " + - m ↔ imeta: "m " + - size ↔ imeta: "size " (if used) + - sha256 ("sha256=" in attach/eattach) ↔ imeta: "x " (per NIP‑94) + - alt, dim, blurhash ↔ imeta: "alt …", "dim WxH", "blurhash …" + - NIP‑92 "fallback" URLs MAY be included in imeta; there is no attach/eattach equivalent. +- Encrypted media: + - The imeta "url" MUST point to the ciphertext URL (the same as in eattach). + - The imeta "x" hash MUST match the sha256 over ciphertext (same value as eattach). + - Do NOT include keys in imeta or content. +- Recommended client behavior: + - If a public note includes media URLs in content, add imeta derived from attach/eattach to maximize compatibility with NIP‑92 renderers. + - If a note does not inline the URL in content (tags‑only publishing), imeta is not applicable; attach/eattach alone is sufficient. + +#### 3.3 NIP-94 (File Metadata) +- Publication guidance (normative): + - Publishers MAY emit a NIP‑94 file‑metadata event for each public attachment (attach or eattach) using the same url/m/size/sha256. This provides durable, relay‑indexable metadata. + - Clients that publish a NIP‑94 event SHOULD reference it from the note (e.g., "e" or "a" tag per client policy). + - For encrypted attachments, NIP‑94 events MUST describe the ciphertext (url/m/size/sha256) and MUST NOT include decryption keys; keys live only in private channels (e.g., NIP‑17 DM). + - When both inline attach/eattach and a NIP‑94 record are present, clients SHOULD prefer inline fields for immediate render and also index the NIP‑94 record. +- Hash alignment: + - In attach/eattach, sha256= corresponds to the NIP‑94 "x" field value used by NIP‑92 imeta. Implementations SHOULD ensure these values match across representations. + +#### 3.4 NIP-96 (HTTP File Storage) +- Compatibility: Presigned upload/finalize flows align with NIP‑96. A NIP‑96‑compliant server can provide initiation, direct upload, and finalize endpoints returning canonical download URLs and server‑computed metadata. + +#### 3.5 NIP-98 (HTTP Auth) +- Usage: Publishers and storage providers MAY require NIP‑98 for upload/finalize/download operations. Include an Authorization header per NIP‑98; servers verify signatures and apply rate limits/quotas. ### 4. Client Behavior @@ -120,7 +201,9 @@ Plaintext before DM encryption: ### 5. Security Considerations - Do not leak keys in public tags (no confidentiality). -- Always verify sha256 and GCM auth tag. +- Do not include encryption material (k/iv/t) in NIP‑92 imeta or any public content. +- Clients MUST verify sha256 and GCM auth tag before render/decrypt. +- Clients MUST NOT auto‑resolve ekref via untrusted schemes; keys SHOULD be conveyed via private DMs (NIP‑17) or other trusted mechanisms. - Apply quotas/content scanning on downloads. ## Examples @@ -173,6 +256,32 @@ Plaintext before DM encryption: } ``` +### Public note with attach + imeta (unencrypted image) + +```json +{ + "kind": 1, + "content": "beach pic https://cdn.example/a.jpg", + "tags": [ + ["attach","https://cdn.example/a.jpg","sha256=2f3a...","m=image/jpeg","size=24567","fn=beach.jpg","alt=beach at dusk","dim=1920x1080","blurhash=..."], + ["imeta","url https://cdn.example/a.jpg","m image/jpeg","x 2f3a...","dim 1920x1080","alt beach at dusk","blurhash ..."] + ] +} +``` + +### Public note with eattach + imeta (encrypted video; key via DM) + +```json +{ + "kind": 1, + "content": "members-only video (see your DM for the key) https://cdn.example/enc/v1/xyz", + "tags": [ + ["eattach","https://cdn.example/enc/v1/xyz","sha256=55aa...","m=video/mp4","size=8329001","fn=talk.mp4","algo=A256GCM","ekref=event:9b3e..."], + ["imeta","url https://cdn.example/enc/v1/xyz","m video/mp4","x 55aa...","alt members-only talk"] + ] +} +``` + ## Implementation Status - Reference client implementation: **loxation-sw** From 494dd3e146e44fa864d53b805c28a4cacb71216f Mon Sep 17 00:00:00 2001 From: jab-r Date: Tue, 26 Aug 2025 22:13:55 -0400 Subject: [PATCH 4/5] Revise NIP for encrypted binary attachments in DMs This update revises the NIP to focus on encrypted binary attachments for private messaging, clarifying the scope, specifications, and security considerations. Changes include rewording sections, updating examples, and emphasizing the use of encryption in attachments. --- nip-xxx-binary-attachments.md | 275 +++++++++++----------------------- 1 file changed, 89 insertions(+), 186 deletions(-) diff --git a/nip-xxx-binary-attachments.md b/nip-xxx-binary-attachments.md index 43de89b6..6123dc75 100644 --- a/nip-xxx-binary-attachments.md +++ b/nip-xxx-binary-attachments.md @@ -1,6 +1,6 @@ --- nip: XXX -title: Binary Attachments for Notes and DMs +title: Encrypted Binary Attachments for DMs and MLS author: Jonathan Borden (jonathan@loxation.com) status: Draft type: Standards Track @@ -10,89 +10,51 @@ license: CC0-1.0 ## Abstract -This NIP standardizes how Nostr events reference binary attachments (images, audio, video, documents), supporting both unencrypted and encrypted forms. It defines: +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: -- Tag-based referencing for public events -- A JSON structure for private DMs that keeps encryption secrets private -- A per-attachment symmetric encryption scheme (AES-256-GCM) with normative sizes -- Integrity fields and basic metadata for rendering -- Optional alignment with existing NIPs (NIP-17, NIP-94, NIP-96, NIP-98) +- A normalized JSON structure for NIP‑17 DMs (keys live only inside the DM ciphertext) +- An MLS profile that derives per‑attachment AEAD key/nonce via the MLS exporter +- A per‑attachment AEAD scheme (AES‑256‑GCM) with normative sizes +- Integrity requirements (SHA‑256 over ciphertext) +- HTTP storage guidance (presigned upload/finalize), optionally compatible with NIP‑96 and NIP‑98 + +Public, unencrypted tags (attach/eattach) are explicitly out of scope for this profile. ## Motivation -Today, clients handle attachments inconsistently. Public events often embed arbitrary tags; private DMs sometimes inline URLs or bespoke JSON. This NIP provides a consistent, privacy-preserving way to: - -- Attach unencrypted files to public notes -- Attach encrypted files whose decryption keys are delivered privately (via DMs) or embedded inside DMs -- Ensure interoperability by keeping metadata normalized (MIME, size, filename, integrity) +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. ## Rationale -- Nostr events should remain lightweight; binary bytes should live off-relay in HTTP-accessible storage. -- Unencrypted public attachments are simple URLs plus integrity metadata. -- Encrypted attachments use a per-attachment symmetric key to avoid key reuse and allow granular sharing. -- For public notes, encryption keys should not be exposed in cleartext (or, if made public intentionally, considered “obfuscated” not secure). -- For DMs, keys live inside encrypted content; tags remain metadata-only. +- Keep symmetric keys in encrypted channels only (DM content or MLS exporter). Never in public tags or relay‑visible metadata. +- Use per‑attachment AEAD to avoid key reuse and enable granular sharing and revocation. +- Compute integrity over ciphertext so clients can verify prior to decryption and rendering. +- Keep events lightweight; store bytes off‑relay at canonical HTTPS URLs. ## Definitions -- **Attachment**: A binary resource referenced by an event. -- **Ciphertext attachment**: The uploaded bytes are encrypted; clients must decrypt locally to render. -- **Per-attachment key**: A random 32-byte AES-256 key generated uniquely for each attachment. +- **Attachment**: A binary resource referenced by a message. +- **Ciphertext attachment**: Uploaded bytes are encrypted; clients decrypt locally to render. +- **Per‑attachment key**: A random 32‑byte AES‑256 key generated uniquely per attachment (DMs). For MLS, the key is derived via the exporter. ## Specification -### 1. Unencrypted attachments (public notes) +### 1. Cipher and integrity -Clients MAY attach resources using `attach` tags: +- AEAD: AES‑256‑GCM + - key: 32 bytes (base64) [DMs only; MLS derives] + - iv/nonce: 12 bytes (base64) + - tag: 16 bytes (base64) +- Integrity: sha256 MUST be computed over ciphertext and verified before decrypt/render. -``` -["attach", "", "sha256=", "m=", "size=", "fn=", "alt=", "dim=", "blurhash=<...>"] -``` +### 2. NIP‑17 direct messages (DMs) -**Required:** url, sha256, m, size -**Recommended:** fn, alt, dim, blurhash - -Clients MUST verify sha256 before rendering. - -### 2. Encrypted attachments - -**Cipher:** AES-256-GCM -- key: 32 bytes, base64 -- iv: 12 bytes, base64 -- tag: 16 bytes, base64 -- Ciphertext: raw GCM ciphertext at URL - -Integrity: sha256 MUST be computed over ciphertext. - -#### 2.1 Public notes (`eattach` tags) - -``` -["eattach", "", "sha256=", "m=", "size=", "fn=", "algo=A256GCM", "ekref=", "alt="] -``` - -- `algo` MUST be `"A256GCM"`. -- `ekref` points to where the decryption key is conveyed (DM, URL, or replaceable event). -- Including `k/iv/t` directly in tags is discouraged (no confidentiality). - -#### 2.2 Private DMs (NIP-17) - -Keys MUST live inside the DM’s encrypted content, not tags. -To avoid exposing symmetric keys in plaintext, this NIP specifies an envelope-wrapped key for DMs: - -- Payload encryption: AES-256-GCM (per-attachment K) -- Key wrapping: X25519-HKDF-AESGCM - - Sender generates an ephemeral X25519 keypair (epk, esk) - - Shared secret s = X25519(esk, recipient_static_x25519_pub) - - wrapKey = HKDF-SHA256(s, info="attachment-key-wrap", salt=random16) - - ek = AES-GCM-Encrypt(wrapKey, nonce=random12, K) - -Plaintext (before NIP‑17 DM encryption) embedding the metadata: +Attachment parameters MUST live inside the DM’s encrypted content (not tags). The DM plaintext embeds a normalized JSON array of attachment objects: ```json { "type": "message", - "text": "optional message", + "text": "optional user text", "attachments": [ { "url": "https://storage.example/enc/blob", @@ -103,32 +65,39 @@ Plaintext (before NIP‑17 DM encryption) embedding the metadata: "enc": { "mode": "dm", "algo": "A256GCM", + "k": "", "iv": "", - "t": "", - "ek": "", - "epk": "", - "wrap": { - "alg": "X25519-HKDF-AESGCM", - "nonce": "", - "salt": "" - } + "t": "" }, - "alt": "a cat" + "alt": "a cat", + "blurhash": "..." } ] } ``` -On receive, clients decrypt the DM, unwrap K using their static X25519 private key and epk, then decrypt the blob with K/iv/t. No plaintext symmetric keys appear in metadata. +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`). -#### 2.3 MLS group attachments +Notes: +- The `size` field SHOULD reflect ciphertext length. +- Clients SHOULD cache both ciphertext and decrypted plaintext for efficient re‑rendering. -For MLS groups, the attachment AEAD key and nonce are derived from the MLS group secret and epoch via the MLS exporter (no ek/epk in metadata): +### 3. MLS group attachments -- key = MLS.exporter(label="attachment", context=concat(epoch, "|", blobId or messageId), length=32) -- nonce = MLS.exporter(label="attachment-nonce", context=concat(epoch, "|", blobId or messageId), length=12) +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. -Metadata embedded in the MLS application message (or carried adjacent as client policy) SHOULD include: +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: ```json { @@ -140,100 +109,45 @@ Metadata embedded in the MLS application message (or carried adjacent as client "enc": { "mode": "mls", "algo": "A256GCM", - "iv": "", "t": "", - "mls": { "group_id": "", "epoch": 42 } + "mls": { "group_id": "", "epoch": 42, "ctx": "" } } } ``` -Receivers derive the same key/nonce via exporter using (groupId, epoch, context) and decrypt the blob. No key material is placed in metadata. +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`. -### 3. Relationship to Existing NIPs +### 4. Out of scope -#### 3.1 NIP-17 (DMs) -- Placement: For private messages, all attachment details that include encryption material MUST live inside the DM’s encrypted content (see 2.2). Do not place keys in public tags. -- Rendering: Receivers decrypt the DM per NIP‑17, fetch bytes, verify sha256 over ciphertext, then decrypt with enc.k/iv/t. +- Public note tags for unencrypted or encrypted media (attach/eattach). +- NIP‑92 “imeta” and NIP‑94 file‑metadata records for public media. +This profile targets encrypted attachments delivered via NIP‑17 and MLS only. -#### 3.2 NIP-92 (Media Attachments) — Interoperability -- Purpose: NIP‑92 defines “imeta” tags that annotate media URLs present in the event content. Many media‑focused clients render using imeta. -- Coexistence with NIP‑XXX: - - attach/eattach MAY be used alongside NIP‑92 imeta in the same event. - - When the event content contains a media URL, publishers SHOULD include a corresponding imeta tag so NIP‑92‑aware clients render consistently. - - When both attach/eattach and imeta are present, fields SHOULD be kept consistent. -- Field mapping (informational): - - url (positional in attach/eattach) ↔ imeta: "url " - - m ↔ imeta: "m " - - size ↔ imeta: "size " (if used) - - sha256 ("sha256=" in attach/eattach) ↔ imeta: "x " (per NIP‑94) - - alt, dim, blurhash ↔ imeta: "alt …", "dim WxH", "blurhash …" - - NIP‑92 "fallback" URLs MAY be included in imeta; there is no attach/eattach equivalent. -- Encrypted media: - - The imeta "url" MUST point to the ciphertext URL (the same as in eattach). - - The imeta "x" hash MUST match the sha256 over ciphertext (same value as eattach). - - Do NOT include keys in imeta or content. -- Recommended client behavior: - - If a public note includes media URLs in content, add imeta derived from attach/eattach to maximize compatibility with NIP‑92 renderers. - - If a note does not inline the URL in content (tags‑only publishing), imeta is not applicable; attach/eattach alone is sufficient. +### 5. Storage and transport -#### 3.3 NIP-94 (File Metadata) -- Publication guidance (normative): - - Publishers MAY emit a NIP‑94 file‑metadata event for each public attachment (attach or eattach) using the same url/m/size/sha256. This provides durable, relay‑indexable metadata. - - Clients that publish a NIP‑94 event SHOULD reference it from the note (e.g., "e" or "a" tag per client policy). - - For encrypted attachments, NIP‑94 events MUST describe the ciphertext (url/m/size/sha256) and MUST NOT include decryption keys; keys live only in private channels (e.g., NIP‑17 DM). - - When both inline attach/eattach and a NIP‑94 record are present, clients SHOULD prefer inline fields for immediate render and also index the NIP‑94 record. -- Hash alignment: - - In attach/eattach, sha256= corresponds to the NIP‑94 "x" field value used by NIP‑92 imeta. Implementations SHOULD ensure these values match across representations. +- Storage: Off‑relay HTTP object storage with presigned upload + finalize flows that return canonical download URLs and server‑computed metadata (size/checksum). These flows are compatible with NIP‑96 where applicable. +- Auth: Publishers and storage providers MAY require NIP‑98 (HTTP Auth) for upload/finalize/download. +- Alternate device transports (e.g., BLE/Noise) MAY carry the same JSON payloads; this does not change the on‑wire format for Nostr DMs or MLS. -#### 3.4 NIP-96 (HTTP File Storage) -- Compatibility: Presigned upload/finalize flows align with NIP‑96. A NIP‑96‑compliant server can provide initiation, direct upload, and finalize endpoints returning canonical download URLs and server‑computed metadata. +### 6. Client behavior -#### 3.5 NIP-98 (HTTP Auth) -- Usage: Publishers and storage providers MAY require NIP‑98 for upload/finalize/download operations. Include an Authorization header per NIP‑98; servers verify signatures and apply rate limits/quotas. +- Verify `sha256` over ciphertext before decrypt/render. +- Verify GCM auth tag during decryption. +- Show filename/thumbnail; respect accessibility fields like `alt`. +- Cache intelligently; apply quotas and safe‑content policies when fetching. -### 4. Client Behavior +### 7. Security considerations -- Verify sha256 before render -- Show filename/thumbnail -- Cache ciphertext and decrypted plaintext -- Respect accessibility (`alt`) - -### 5. Security Considerations - -- Do not leak keys in public tags (no confidentiality). -- Do not include encryption material (k/iv/t) in NIP‑92 imeta or any public content. -- Clients MUST verify sha256 and GCM auth tag before render/decrypt. -- Clients MUST NOT auto‑resolve ekref via untrusted schemes; keys SHOULD be conveyed via private DMs (NIP‑17) or other trusted mechanisms. -- Apply quotas/content scanning on downloads. +- Do not place keys, IVs, or tags in public tags or content. +- Do not include encryption material in NIP‑92 `imeta` or any relay‑visible metadata. +- Treat URL‑based key delivery or external key references as non‑confidential; this profile forbids such patterns. +- Ensure unique IVs per key; per‑attachment keys simplify this, but libraries MUST still generate fresh IVs. ## Examples -### Public note with two attachments - -```json -{ - "kind": 1, - "content": "trip photos", - "tags": [ - ["attach","https://cdn.example/a.jpg","sha256=...","m=image/jpeg","size=24567","fn=beach.jpg","alt=beach","dim=1920x1080"], - ["attach","https://cdn.example/itinerary.pdf","sha256=...","m=application/pdf","size=90123","fn=itinerary.pdf"] - ] -} -``` - -### Public note with encrypted video - -```json -{ - "kind": 1, - "content": "members-only video (see your DM for the key)", - "tags": [ - ["eattach","https://cdn.example/enc/v1/xyz","sha256=...","m=video/mp4","size=8329001","fn=talk.mp4","algo=A256GCM","ekref=event:9b3e..."] - ] -} -``` - -### DM carrying encrypted attachment +### NIP‑17 DM with encrypted attachment ```json { @@ -243,46 +157,35 @@ Receivers derive the same key/nonce via exporter using (groupId, epoch, context) "url": "https://cdn.example/enc/xyz", "ct": "image/jpeg", "size": 23011, - "sha256": "...", + "sha256": "55aa...", "fn": "photo.jpg", - "enc": { - "algo": "A256GCM", - "k": "...", - "iv": "...", - "t": "..." - } + "enc": { "mode": "dm", "algo": "A256GCM", "k": "...", "iv": "...", "t": "..." } } ] } ``` -### Public note with attach + imeta (unencrypted image) +### MLS attachment metadata (ciphertext at URL; key/nonce via exporter) ```json { - "kind": 1, - "content": "beach pic https://cdn.example/a.jpg", - "tags": [ - ["attach","https://cdn.example/a.jpg","sha256=2f3a...","m=image/jpeg","size=24567","fn=beach.jpg","alt=beach at dusk","dim=1920x1080","blurhash=..."], - ["imeta","url https://cdn.example/a.jpg","m image/jpeg","x 2f3a...","dim 1920x1080","alt beach at dusk","blurhash ..."] - ] -} -``` - -### Public note with eattach + imeta (encrypted video; key via DM) - -```json -{ - "kind": 1, - "content": "members-only video (see your DM for the key) https://cdn.example/enc/v1/xyz", - "tags": [ - ["eattach","https://cdn.example/enc/v1/xyz","sha256=55aa...","m=video/mp4","size=8329001","fn=talk.mp4","algo=A256GCM","ekref=event:9b3e..."], - ["imeta","url https://cdn.example/enc/v1/xyz","m video/mp4","x 55aa...","alt members-only talk"] - ] + "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..." } + } } ``` ## Implementation Status -- Reference client implementation: **loxation-sw** -- Supports per-attachment AES-256-GCM, presigned upload/finalize, and UI rendering. +- Reference client: **loxation‑sw** +- Supports per‑attachment AES‑256‑GCM, presigned upload/finalize, NIP‑17 DM JSON payloads, and UI rendering. +- MLS exporter‑based attachments are supported in the MLS messaging flows used by Loxation. + From 3b192390959fc137ea39dc863992eeecbb590f18 Mon Sep 17 00:00:00 2001 From: jab-r Date: Wed, 27 Aug 2025 13:28:58 -0400 Subject: [PATCH 5/5] Update README with new NIP-EE and NIP-XXX entries --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b1330f4d..2e76f3d1 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,8 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos - [NIP-B7: Blossom](B7.md) - [NIP-C0: Code Snippets](C0.md) - [NIP-C7: Chats](C7.md) -- [NIP-XXX: Binary Attachments | DRAFT](nip-xxx-binary-attachments.md) +- [NIP-EE: E2EE Messaging using MLS Protocol](EE.md) +- [NIP-XXX: Encrypted Binary Attachments | DRAFT](nip-xxx-binary-attachments.md) ## Event Kinds | kind | description | NIP |