Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions Filesystem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
NIP-FS
======

Private Encrypted File System
-----------------------------

`draft` `optional`

Defines 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.

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` | SHA-256 hash of the encrypted file blob |
| `t` | `"files"` — marks this as a file metadata record |
| `encrypted` | `nip44` |
| `client` | client identifier (e.g. `formstr-drive`) |

**Content:** `nip44.v2.encrypt(json, driveConversationKey)` where plaintext is:

```json
{
"name": "<filename>",
"hash": "<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>",
"deleted": <boolean, optional>,
"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 `base64(fileBytes)`: `key = derived[0:32]`, `iv = derived[32:44]`
6. Blob format (base64): `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 MUST equal the SHA-256 hash of the **encrypted** chunks as returned by the Blossom server.
- File Metadata events MUST 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 MUST skip events whose content they cannot decrypt.
- Virtual folders are derived from the `folder` field; clients MUST NOT publish separate folder events.
- When multiple events share the same `d` tag, clients MUST use the one with the highest `created_at`.

---

## Examples

File Metadata event:

```jsonc
{
"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:

```json
{
"name": "report.pdf",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 204800,
"type": "application/pdf",
"folder": "/work/docs",
"uploadedAt": 1700000001000,
"server": "https://blossom.primal.net",
"encryptionKey": "cafebabe5678...",
"chunks":[{"hash":"289y3899f23"}, {"hash": "2983ur92u9"}]
}
```
39 changes: 39 additions & 0 deletions Metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
NIP-Metadata
======

Metadata events
-----------------------------

`draft` `optional`

Defines a protocol for metadata events. Metadata events hold metadata information about other events, or entities

---
## Kind 34578 — Metadata Event

Metadata events use **kind 34578** (addressable). They are distinguished by their `d` tag and the presence of the `["t", "<sub_type>"]` tag. The content of a metadata event always has a json which is encrypted to the author's pubkey. The shape of the json depends on the sub type of the metadata event

| tag | value |
|-----|-------|
| `d` | depends on the sub-type of the metadata event |
| `t` | `"<sub-type>"` — sub-type of the event |

---


## Examples

User metadata event:

```jsonc
{
"kind": 34578,
"pubkey": "abc123...",
"created_at": 1700000000,
"tags": [
["d", "0:abc123..."],
],
"content": "<nip44-ciphertext of json encrypted to abc123>",
"sig": "..."
}
```
62 changes: 62 additions & 0 deletions User-Metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
NIP-UM
======

User-Metadata
-----------------------------

`draft` `optional`

Defines a protocol for user metadata events per NIP-Metadata. User Metadata events hold metadata information about the user such as a decoupled encryption key

---

## Kind 34578 — User Metadata Event

Stores the secret metadata information about the user inside the content field. It is identified by the `d` tag

**Tags:**

| tag | value |
|-----|-------|
| `d` | `"0:<author-pubkey>"` |

**Content:** NIP-44 encrypted to the author's own pubkey (via identity signer). Plaintext is a JSON string. Currently, the proposal is that the data contains a `encryptionKey` attribute which will have the key used for decrypting/encrypting messages, files and other entities owned by the user.

```json
{
"encryptionKey": "<hex-encoded drive private key>"
}
```

The **conversation key** used for all file metadata is:

```
conversationKey = getConversationKey(driveSecretKey, getPublicKey(driveSecretKey))
```

## Directives

- The user metadata event must have a d tag `"0:<author-pubkey>"`.
- Clients MUST encrypt and decrypt the user metadata event using the identity signer (`nip44Decrypt(authorPubkey, content)/nip44Encrypt(authorPubkey, content)`).
- Clients MAY generate and publish a new metadata event warning the user that previous events may be lost.
- When multiple events share the same `d` tag, clients MUST use the one with the highest `created_at`. Clients MAY inform the user that they found multiple metadata events

---

## Examples

User metadata event:

```jsonc
{
"kind": 34578,
"pubkey": "abc123...",
"created_at": 1700000000,
"tags": [
["d", "0:abc123..."],
],
"content": "<nip44-ciphertext of {\"encryptionKey\",\"deadbeef...\"}>",
"sig": "..."
}
```