NIPs by PolleramaCommunity NIPs, surfaced by trustConnect
npub19yw8tkfh530...

NIP-XX: Encrypted Workspace

Published Mar 20, 2026
kind 30800kind 30801kind 30802kind 30078

NIP-XX: Encrypted Workspace

A protocol for private, encrypted productivity workspaces synced via Nostr relays. All content is end-to-end encrypted with NIP-44 before leaving the client — relay operators see only opaque ciphertext.

Kinds 30800, 30801, and 30802 were defined by NIP-XX: Encrypted File Sync. This document extends that foundation with encrypted structured data via kind 30078, a formal vault index manifest format with delete/restore/orphan semantics, and a defined security model.

Summary

KindNameTypePurpose
30800File ContentAddressableEncrypted document body
30801Vault IndexAddressableEncrypted workspace manifest
30802Shared DocumentAddressableDocument encrypted to a recipient
30078Structured DataAddressableEncrypted database rows, tasks, preferences

Kind 30800 — File Content

Stores the encrypted content of a single document. The content field is always a NIP-44 ciphertext — never plaintext.

Tags:

TagRequiredDescription
dYesStable UUID-style file identifier (e.g. doc-m3xk7f2)
encryptedYesAlways "nip44"
altYesNIP-31 fallback: "Encrypted note - NIP-44"

Decrypted payload:

{
  "title": "Meeting Notes",
  "path": "/notes/meeting-notes.md",
  "content": "# Meeting Notes\n\nDiscussion points...",
  "modified": 1742483921,
  "version": 5,
  "checksum": "a3f1",
  "contentType": "text/markdown"
}
FieldTypeRequiredDescription
titlestringYesHuman-readable document title
pathstringYesVirtual filesystem path (e.g. /notes/meeting-notes.md)
contentstringYesDocument body (typically Markdown)
modifiednumberYesUnix timestamp of last modification
versionnumberNoIncrementing version counter
checksumstringNoShort content integrity checksum
contentTypestringNoMIME type, defaults to text/markdown

Encryption:

event.content = nip44.encrypt(authorPubkey, JSON.stringify(payload))

Self-encryption — the conversation key is derived from the author's own keypair. Only the author can decrypt their own documents.


Kind 30801 — Vault Index

The authoritative manifest of all documents in a workspace. One event per user per vault. Clients treat this as the source of truth for workspace state.

  • A file is active if it appears in files[]
  • A file is deleted if it appears in deleted[]
  • A file is orphaned if it exists on the relay but in neither array (recoverable via relay scan)

Tags:

TagRequiredDescription
dYesVault identifier (e.g. "default-vault")
encryptedYesAlways "nip44"
altYesNIP-31 fallback: "Encrypted vault index - NIP-44"

Decrypted payload:

{
  "name": "My Workspace",
  "description": "Private encrypted documents synced via Nostr",
  "files": [
    {
      "d": "doc-m3xk7f2",
      "path": "/notes/meeting-notes.md",
      "title": "Meeting Notes",
      "modified": 1742483921,
      "eventId": "abcdef1234..."
    }
  ],
  "deleted": [
    {
      "d": "doc-a1b2c3",
      "path": "/notes/old-draft.md",
      "title": "Old Draft",
      "deletedAt": 1742480000,
      "lastEventId": "abcdef5678..."
    }
  ],
  "updated": 1742483921
}

**files[] entry:**

FieldTypeRequiredDescription
dstringYesMatches the d tag of the corresponding kind 30800 event
pathstringYesVirtual filesystem path
titlestringYesDisplay title
modifiednumberYesUnix timestamp of last modification
eventIdstringNoEvent ID of the most recent kind 30800 event

**deleted[] entry (tombstone):**

FieldTypeRequiredDescription
dstringYesMatches the d tag of the deleted kind 30800 event
pathstringYesLast known path before deletion
titlestringYesLast known title before deletion
deletedAtnumberYesUnix timestamp of deletion
lastEventIdstringNoEvent ID of the final kind 30800 version

Top-level fields:

FieldTypeRequiredDescription
namestringNoWorkspace display name
descriptionstringNoWorkspace description
filesarrayYesActive file references
deletedarrayNoTombstones for deleted files
updatednumberYesUnix timestamp of last index update
settingsobjectNoWorkspace-level client settings

Delete / Restore semantics:

  • Delete — remove the entry from files[], add a tombstone to deleted[]. The underlying kind 30800 event on the relay is never modified (Nostr is append-only).
  • Restore — remove the tombstone from deleted[], re-add the entry to files[].
  • Orphan recovery — kind 30800 events that exist on the relay but appear in neither array can be discovered by scanning and re-imported into the index.

Kind 30802 — Shared Document

A document shared from one user to another. Uses the same decrypted payload schema as kind 30800, but the NIP-44 conversation key is derived from the author's private key and the recipient's public key rather than self-encryption.

Tags:

TagRequiredDescription
dYesStable document identifier
pYesRecipient's pubkey
encryptedYesAlways "nip44"
altYesNIP-31 fallback: "Encrypted shared document - NIP-44"

Encryption:

event.content = nip44.encrypt(recipientPubkey, JSON.stringify(payload))

Kind 30078 — Structured Data (NIP-78)

Encrypted structured data such as database rows, tasks, kanban cards, and application preferences. Reuses the NIP-78 kind with t tags for namespacing and relay-level filtering.

Tags:

TagRequiredDescription
dYesEntry identifier (e.g. task-m3xk7f2)
tYesEntry type: "task", "preference", "row", etc.
tNoStatus slug: "todo", "in-progress", "done"
encryptedYesAlways "nip44"
altYesNIP-31 fallback: "Encrypted structured data - NIP-44"

Example decrypted task payload:

{
  "id": "task-m3xk7f2",
  "type": "task",
  "task": "Write release notes",
  "status": "in-progress",
  "assignee": "npub1...",
  "due": "2026-04-01",
  "modified": 1742483921
}

Encryption Model

All private content uses NIP-44 self-encryption unless otherwise specified:

event.content = nip44.encrypt(authorPubkey, JSON.stringify(payload))

The conversation key is derived from the author's own keypair. Only the author can decrypt their own data. Relay operators, other users, and network observers see only ciphertext.

Kind 30802 (shared documents) uses the recipient's pubkey instead:

event.content = nip44.encrypt(recipientPubkey, JSON.stringify(payload))

Key properties:

  • No plaintext content is ever stored in event fields visible to relays
  • Tags contain only UUID-style identifiers, the "nip44" signal, and generic NIP-31 descriptions — never user content
  • Works with any NIP-07 compatible signer; the private key is never exposed to the client application

Security Requirements

Author filtering

All queries for private workspace data MUST include authors: [userPubkey]. Nostr is permissionless — without this filter, any actor can publish events with these kind numbers and inject content into a workspace.

// ✅ Correct
nostr.query([{
  kinds: [30800, 30801],
  authors: [user.pubkey],
}]);

// ❌ Unsafe — accepts events from any publisher
nostr.query([{
  kinds: [30800, 30801],
}]);

No sensitive data in tags

Tags must never contain document titles, file paths, or any user-generated content. Only UUID-style d identifiers, the "nip44" encryption signal, recipient pubkeys (p), category t tags, and NIP-31 alt descriptions are permitted in plaintext tags.


Standard NIPs Referenced

NIPPurpose
NIP-44Versioned encryption used for all private content
NIP-31alt tags on all custom events for client discoverability
NIP-65Relay list management (user-configurable read/write relays)
NIP-78Application-specific addressable data (kind 30078)
NIP-07Signer interface for encryption/decryption
NIP-19Bech32 identifiers for routing and linking