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