30817:blossom-folder-lists

Blossom Folder Lists

covers30000

Geek

published
2025-10-04

Summary

This NIP standardizes how clients publish and consume parameterized, replaceable Nostr events that model file-system-style folders for blob storage backends (e.g., Blossom, NIP-96, Satellite). Each event represents a folder and lists the blob hashes it contains, allowing multiple clients to share identical folder structures.

Motivation

Blossom and related services expose flat blob namespaces. Bloom and similar applications need a portable way to represent folders without changing storage protocols. A shared event format lets clients:

  • Synchronize folder structures across devices.
  • Coordinate folder names and memberships.
  • Extend storage tooling without proprietary metadata.

Terminology

  • Folder List Event – kind 30000 parameterized replaceable event that declares one folder and enumerates blob hashes (src/lib/folderList.ts:6).
  • Folder Identifier – the d tag value that uniquely identifies a folder event (src/lib/folderList.ts:25).
  • Normalized Folder Path – slash-delimited path produced by normalizeFolderPathInput (src/utils/blobMetadataStore.ts:539).
  • Blob Hash – lowercase SHA-256 hex digest referencing a stored object.
  • Private Folder List – encrypted kind 30000 event under the d tag private holding user-only metadata (src/lib/privateList.ts:5).

Public Folder Lists

Event Kind

Clients must publish folder definitions as kind 30000 parameterized replaceable events (per NIP-33 / NIP-51 semantics).

Identifier (d tag)

Every folder event must include:

["d", "<identifier>"]

<identifier> must be bloom-folder:<path>, where <path> is:

  • __root__ for the root folder.
  • Otherwise encodeURIComponent(<normalized-path>).

Relays and clients must treat identifiers as case-sensitive.

Folder Path Tag

["folder", "<normalized-path>"]
  • <normalized-path> must follow the normalization rules below.
  • Root folders use an empty string.

Display Name

Optional tag:

["name", "<display-name>"]

If absent, consumers should fall back to event.content when non-empty (src/lib/folderList.ts:80).

Blob Membership

For each blob, add:

["x", "<sha256>"]
  • <sha256> must be 64 lowercase hex characters.
  • Duplicate hashes in one event should be ignored.

Event Content

  • content may mirror the human-readable folder name.
  • If both ["name", ...] and content are present, the tag takes precedence.

Additional Tags

  • Implementations may add extra tags.
  • Consumers must ignore unknown tags unless otherwise agreed.

Private Folder Lists (Encrypted)

Bloom keeps a user-only catalog of blob metadata and folder assignments.

Event Kind & Identifier

  • Still kind 30000.
  • Use the parameterized identifier:
["d", "private"]

Encryption

  • content must be encrypted to the author with NIP-44, falling back to NIP-04 when NIP-44 is unavailable (src/lib/privateList.ts:144, src/lib/privateList.ts:191).
  • Consumers must decrypt with the signer's keypair.

Payload Structure

The decrypted JSON must follow this schema:

{
  "version": 1,
  "entries": [
    {
      "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "encryption": {
        "algorithm": "aes-256-gcm",
        "key": "base64-key",
        "iv": "base64-iv"
      },
      "metadata": {
        "name": "Vacation Photo.jpg",
        "type": "image/jpeg",
        "size": 2048000,
        "audio": null,
        "folderPath": "photos/trips/2025"
      },
      "servers": ["https://blossom.example", "https://files.other"],
      "updatedAt": 1719945600
    }
  ]
}

Structure Rules

  • version must be 1.
  • Every entry must include sha256. Clients ignore entries with missing sha256.
  • encryption is optional; when present, include algorithm, key, and iv.
  • metadata is optional; folderPath should follow the same normalization rules. Use null to clear a folder assignment.
  • servers is optional; Clients should trim each URL of trailing slashes.
  • updatedAt is optional but recommended for conflict resolution.
  • Clients publishing private lists overwrite the entire entries array; partial updates are undefined.

Path Normalization

When converting user input to folder paths (src/utils/blobMetadataStore.ts:520–550):

  1. Trim leading/trailing whitespace.
  2. Split on /, trim each segment, discard empty segments.
  3. Reject if any segment, after lowercasing and removing non-alphanumerics, contains the reserved keyword private.
  4. Join remaining segments with /. An empty result denotes the root.
  5. Inputs that normalize to null or undefined should be rejected.

Root Folder

Represent the root folder with:

["d", "bloom-folder:__root__"]
["folder", ""]
  • Clients should not store blob hashes in the root event; absence of hashes implies "unfiled" blobs.

Client Behavior

  • Keep only the most recent event per identifier (the one with the highest created_at value wins).
  • When applying folder membership, replace prior state with the latest event (src/lib/folderList.ts:97).
  • Remove a blob by publishing a new event without that blob's ["x", ...] tag.
  • Delete a folder by publishing the identifier with no ["x", ...] tags and an empty display name.
  • For private lists, refresh and overwrite the complete entry set (src/lib/privateList.ts:134).

Relay Recommendations

  • Support parameterized replaceable semantics keyed by (kind, pubkey, d).
  • Optionally enforce size limits to prevent oversized folder events.

Security Considerations

  • Public folder lists expose mappings from pubkeys to blob hashes; treat as public metadata.
  • Avoid placing sensitive information in folder names—blobs may reside on third-party servers.
  • For private folder lists, implementations must safeguard decrypted payloads (e.g., never log plaintext).

Examples

Example Public Folder Event (JSON Template)

{
  "kind": 30000,
  "created_at": 1719945600,
  "pubkey": "b6d1…",
  "tags": [
    ["d", "bloom-folder:photos%2Ftrips%2F2025"],
    ["folder", "photos/trips/2025"],
    ["name", "Trips 2025"],
    ["x", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
    ["x", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"]
  ],
  "content": "Trips 2025"
}

Example Root Folder Event

{
  "kind": 30000,
  "created_at": 1719945601,
  "pubkey": "b6d1…",
  "tags": [
    ["d", "bloom-folder:__root__"],
    ["folder", ""]
  ],
  "content": "  "
}

Example Private Folder List Event (Encrypted Template)

{
  "kind": 30000,
  "created_at": 1719945602,
  "pubkey": "b6d1…",
  "tags": [
    ["d", "private"]
  ],
  "content": "<encrypted nip44 ciphertext>"
}

The decrypted payload utilizes the schema shown above.

Reference Implementation

  • Public folder events: src/lib/folderList.ts
  • Path normalization helpers: src/utils/blobMetadataStore.ts:520–550
  • Private folder list serialization: src/lib/privateList.ts

Discussion

Connect a key to comment.