{"id":"eaae5355252a64add9e8b39ef50b0e14c7d8c02002c8fcd99ac50ac16efdc439","pubkey":"a22dbe51e3c719ad8b2cdc3e4ffc2176f20373f3027b67a1dc6c6e976fbcd6d8","created_at":1784833123,"kind":30817,"tags":[["d","nip-fs"],["title","NIP-FS"],["k","34578","Metadata"],["client","better-nips"]],"content":"Private Encrypted File System(NIP-FS)\n-----------------------------\n\n`draft` `optional`\n\nDefines a protocol for a private encrypted file drive using [Blossom](https://github.com/hzrd149/blossom) blob servers for file storage and Nostr relays for an encrypted file index.\n\nThe file metadata event uses the Metadata event per NIP-Metadata with subtype as `files`\n\n---\n\n## Kind 34578 — File Metadata Subtype\n\nOne event per file, encrypted with the encryption key stored in the user metadata event.\n\n**Tags:**\n\n| tag | value |\n|-----|-------|\n| `d` | random id for the file for the user. preferably 6-8 chars. |\n| `t` | `\"files\"` — marks this as a file metadata record |\n| `encrypted` | `nip44` |\n| `client` | client identifier (e.g. `formstr-drive`) |\n\n**Content:** `nip44.v2.encrypt(json, conversationKey_derivedFromUserMetadataEvent)` where plaintext is:\n\n```json\n{\n  \"name\": \"<filename>\",\n  \"unencryptedFileHash\": \"<optional sha256 hex of the original file>\",\n  \"size\": <bytes>,\n  \"type\": \"<MIME type>\",\n  \"folder\": \"<virtual path, e.g. /docs/work>\",\n  \"uploadedAt\": <unix timestamp ms>,\n  \"server\": \"<blossom server base URL>\",\n  \"encryptionKey\": \"<hex-encoded per-file private key>\",\n  \"encryptionAlgorithm\": \"<encryption-algorithm-used>(aes-gcm)\",\n  \"previewHash\": \"<sha256 hex, optional>\",\n  \"chunks\": List<{\"hash\": \"<hash of the encrypted chunk>\", \"server\": \"<optional blossom server base URL, if the chunk was sent to another server>\"}>\n}\n```\n\n---\n\n## File Encryption\n\nEach **chunk** of a file is encrypted with a per-file ephemeral keypair using AES-GCM with NIP-44 v2 HKDF key derivation:\n\n1. Generate a random keypair `(sk, pk)` for the file\n2. `conversationKey = getConversationKey(sk, pk)`\n3. Generate random `nonce` (32 bytes)\n4. `HKDF-SHA256(conversationKey, salt=nonce, info=\"nip44-v2\")` → 44 bytes.\n5. AES-GCM encrypt `fileBytes`: `key = derived[0:32]`, `iv = derived[32:44]`\n6. Blob format : `0x02 || nonce (32 bytes) || ciphertext`\n7. Store `hex(sk)` as `encryptionKey` in the file metadata content\n\n---\n\n## Directives\n\n- File Metadata events MUST be encrypted with `nip44.v2.encrypt` using the encryption key directly (not via the identity signer).\n- The `d` tag of a File Metadata event SHOULD be a random id which is unique within the user's scope.\n- File Metadata events SHOULD carry `[\"t\", \"files\"]` to distinguish them from other metadata events.\n- To rename or move a file to another folder: publish a new File Metadata event with the same `d` tag.\n- Delete: Delete all the chunks from the blossom server, the preview(if applicable) and the file metadata event\n- Clients MAY verify the encryption algorithm before attempting to decrypt.\n- Clients MUST skip events whose content they cannot decrypt.\n- Virtual folders are derived from the `folder` field;\n- When multiple events share the same `d` tag, clients MUST use the one with the highest `created_at`.\n- Clients MAY verify the unencrypted file hash with the decrypted file hash while downloading the file.\n\n---\n\n## Examples\n\nFile Metadata event:\n\n```jsonc\n{\n  \"kind\": 34578,\n  \"pubkey\": \"abc123...\",\n  \"created_at\": 1700000001,\n  \"tags\": [\n    [\"d\", \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"],\n    [\"t\", \"files\"],\n    [\"encrypted\", \"nip44\"],\n    [\"client\", \"formstr-drive\"]\n  ],\n  \"content\": \"<nip44-ciphertext using drive conversation key>\",\n  \"sig\": \"...\"\n}\n```\n\nDecrypted File Metadata content:\n\n```json\n{\n  \"name\": \"report.pdf\",\n  \"unencryptedFileHash\": \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n  \"size\": 204800,\n  \"type\": \"application/pdf\",\n  \"folder\": \"/work/docs\",\n  \"uploadedAt\": 1700000001000,\n  \"server\": \"https://blossom.primal.net\",\n  \"encryptionAlgorithm\": \"aes-gcm\",\n  \"encryptionKey\": \"cafebabe5678...\",\n  \"chunks\":[{\"hash\":\"289y3899f23\"}, {\"hash\": \"2983ur92u9\"}]\n}\n```","sig":"aaedf65ac868cb577ce86b164ca2e43db073e6caf3596e8e7012241f0190f309ab5812cc18e201a354973cc0dabf4d3c05b2111583050e7878573a9242545e9a"}