NIPs by PolleramaCommunity NIPs, surfaced by trustConnect
npub15gkmu50rcuv...

NIP-FS

Published Jul 23, 2026
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:

tagvalue
drandom id for the file for the user. preferably 6-8 chars.
t"files" — marks this as a file metadata record
encryptednip44
clientclient 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:

  1. Generate a random keypair (sk, pk) for the file
  2. conversationKey = getConversationKey(sk, pk)
  3. Generate random nonce (32 bytes)
  4. HKDF-SHA256(conversationKey, salt=nonce, info="nip44-v2") → 44 bytes.
  5. AES-GCM encrypt fileBytes: key = derived[0:32], iv = derived[32:44]
  6. Blob format : 0x02 || nonce (32 bytes) || ciphertext
  7. Store hex(sk) as encryptionKey in the file metadata content

Directives

  • File Metadata events MUST be encrypted with nip44.v2.encrypt using the encryption key directly (not via the identity signer).
  • The d tag 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 d tag.
  • 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 folder field;
  • When multiple events share the same d tag, clients MUST use the one with the highest created_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"}]
}