30817:nkbip-07

Hierarchical Blossom Piece Indexes

NKBIP-07: hierarchical kind 32176 indexes for multi-source Blossom files — ordered piece hashes via #b, ≤1000 a/b tags per event.

Silberengel

published
2026-08-18

NKBIP-07: Hierarchical Blossom Piece Indexes

This NKBIP defines a parameterized replaceable index kind 32176 for large binary payloads distributed via Blossom (content-addressed HTTP blobs), using a table-of-contents pattern analogous to [[NKBIP-01]] publication indexes. This creates a Nostr-native blob distribution similar to popular file distribution systems.

Motivation

Blossom stores blobs by sha256 on media servers. Swarm-like blob distribution needs:

  1. A content id and ordered piece list
  2. Multi-source fetch (many Blossom servers / mirrors)
  3. Discovery without putting file bytes on relays

NKBIP-01 already solved hierarchical indexing for text via kind 30040 a-tags. NKBIP-07 reuses that shape for blob pieces.

Piece hashes use a single-letter tag so relays that implement NIP-01 tag filters can answer queries such as #b: ["bc56e8d587133521b0da617b0475d18f9a8afacc45ae67549b8c2673273dec50"] (which indexes contain this blob?).

Event kind

Kind Name Role
32176 Blossom Piece Index Parameterized replaceable hierarchical index

Blob bytes are not required in event content. They live on Blossom servers and are retrieved with GET /<sha256> (BUD-01).

Kind 32176: Blossom Piece Index

Requirements

  • content MUST be empty ("") or contain a text transcript of the blob contents
  • If content is non-empty, a transcript tag SHOULD describe the format (txt, vtt, srt, or json). If that tag is missing or not one of those four, clients MUST treat the transcript as txt.
  • MUST include d (NIP-54-style: lowercase letters, numbers, hyphens)
  • MUST include title
  • Uniquely identified by kind + pubkey + d
  • Any a tags MUST be in display / assembly order
  • An index MAY have zero a tags (stub / announcement only)
  • Each index event MUST contain at most 1000 a tags
  • Each index event MUST contain at most 1000 b tags

a tag format

["a", "<kind:pubkey:dtag>", "<relay hint>", "<label>"]
  • [1] — address of a child kind 32176 index
  • [2] — optional relay hint
  • [3] — optional legible label (not an event id)

Hierarchy

Every kind 32176 event is a node in a tree:

Role Meaning
Root Entry index clients resolve by d / naddr
Branch Has one or more child indexes via a
Leaf Contributes piece bytes via b and has no child a tags needed for assembly
root 32176
  └─ branch 32176   (≤1000 a → branches or leaves)
       └─ leaf 32176  (≤1000 b → piece hashes)

A node MAY be both branch and piece-bearing: it can carry local b tags and child a tags. There is no separate event kind for leaves.

Clients MUST walk with a cycle/depth guard (breadth-first or depth-first). Indexes MUST NOT form cycles.

Assembly order

For each node, clients MUST assemble bytes in this order:

  1. Local b tags, in tag order
  2. Then each child from a, in tag order (recurse)

If a node has only b tags, it is a leaf. If it has only a tags, it is a pure branch. If it has both, apply the order above.

Piece pointers (b)

Pieces MUST be listed with the single-letter tag b (NIP-01 #b discovery):

["b", "<sha256>", "<length-bytes>"]
  • [1] — lowercase hex SHA-256 of the piece bytes
  • [2] — piece length in bytes (decimal string); SHOULD be present

b tags MUST appear in assembly order. Within one tree, piece sizing conventions MUST be consistent. If a node would need more than 1000 pieces, split into additional leaf/branch children and reference them from the parent via a.

Every tree SHOULD also carry the full-file digest (distinct from per-piece b):

["x", "<sha256-of-complete-file>"]

x MAY be repeated on descendants. size (total bytes) SHOULD appear on the root when known.

Blossom servers

Indexes MAY list preferred media servers:

["blossom", "https://cdn.example"]

Clients SHOULD also consult the author’s kind 10063 Blossom server list (BUD-03) and any r mirrors. Fetch pieces with GET /<sha256> (BUD-01); optional PUT /mirror (BUD-04) after download to become another source.

Optional metadata tags

Tag Meaning
summary Short description
size Total file size in bytes (decimal string)
type Hint: file, archive, media, …
transcript Transcript format when content is not empty: txt, vtt, srt, json. Default txt if omitted
i External id (sha256:…, magnet:…, etc.)
t Topics
image Preview image URL
T / N Normalized title/author slugs (same convention as d-tags, see [[NKBIP-01]])

Worked example: Gone with the Wind (1939)

Illustrative Blossom tree for a ~3 GiB H.264 file of the film Gone with the Wind. Digests are deterministic examples (SHA-256 of label strings), not hashes of real copyrighted media.

Symbol Value
Author pubkey 3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690
Full-file x f40069245c728a3dbfab8ce4efee8f14d77b425c74a00f3ac06b810b166d6a83
MP4 init / header 05b12ab19cb0f2567054543e061a65aea32cfc9514cc6589e61ab1776e4564ea (4096 B)
Piece 0 bc56e8d587133521b0da617b0475d18f9a8afacc45ae67549b8c2673273dec50 (1048576 B)
Piece 1 41f0c7a44ba21e4532c46dc316fb822a3b71ecf4aa62daeb7919628cf5d8bfa2 (1048576 B)
Piece 2 1b13f2bf2e8c265263cba6d73665e4a82b037fe7b4e194d3c187ac57644ea9d2 (1048576 B)
Piece 3 59809cbdde09122d9398736a1b15a70655a4fc2ae7ba21025d4c2bace4d506f4 (1048576 B)
Piece 4 a0b115beda4c834c8eb34b620cedf4387583f076802717d129bf407be2282eb4 (1048576 B)
Piece 5 ba664b3efc97c992f973f02e4e196474bef26a9af1a19e1d6d9d69a7d4c1b890 (1048576 B)
Total size 3221225472 (3 GiB)
Piece size 1 MiB (1048576) → 3072 pieces
Fan-out ≤1000 b per leaf → 4 scene leaves (see tree)

Tree (production shape) — children named for iconic scenes, in film order:

gone-with-the-wind-1939                         (root)
├─ …-twelve-oaks                                (leaf: 1000 × b)  Twelve Oaks barbecue
├─ …-atlanta-burning                            (leaf: 1000 × b)  Burning of Atlanta
├─ …-never-hungry-again                         (leaf: 1000 × b)  “I’ll never be hungry again”
└─ …-frankly-my-dear                            (leaf: 72 × b)    Rhett’s farewell

gone-with-the-wind-1939-mixed                   (optional: local header + scenes)
├─ b: MP4 init segment (4 KiB)
├─ a → twelve-oaks
├─ a → atlanta-burning
├─ a → never-hungry-again
└─ a → frankly-my-dear

JSON samples below show only the first few b tags on each leaf; a real Twelve Oaks leaf would list 1000 b tags.

Example root

{
  "kind": 32176,
  "pubkey": "3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690",
  "created_at": 1735689600,
  "content": "",
  "tags": [
    ["d", "gone-with-the-wind-1939"],
    ["title", "Gone with the Wind (1939)"],
    ["T", "gone-with-the-wind-1939"],
    ["N", "victor-fleming"],
    ["x", "f40069245c728a3dbfab8ce4efee8f14d77b425c74a00f3ac06b810b166d6a83"],
    ["size", "3221225472"],
    ["type", "media"],
    ["summary", "1939 epic; ~3 GiB H.264, 1 MiB Blossom pieces, scene-named leaves"],
    ["t", "classic-film"],
    ["t", "drama"],
    ["image", "https://cdn.example/posters/gone-with-the-wind-1939.jpg"],
    ["i", "sha256:f40069245c728a3dbfab8ce4efee8f14d77b425c74a00f3ac06b810b166d6a83"],
    ["blossom", "https://cdn.example"],
    ["blossom", "https://blossom.example"],
    [
      "a",
      "32176:3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690:gone-with-the-wind-1939-twelve-oaks",
      "wss://thecitadel.nostr1.com",
      "Twelve Oaks"
    ],
    [
      "a",
      "32176:3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690:gone-with-the-wind-1939-atlanta-burning",
      "wss://nos.lol",
      "Atlanta Burning"
    ],
    [
      "a",
      "32176:3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690:gone-with-the-wind-1939-never-hungry-again",
      "wss://relay.damus.io",
      "I'll Never Be Hungry Again"
    ],
    [
      "a",
      "32176:3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690:gone-with-the-wind-1939-frankly-my-dear",
      "wss://thecitadel.nostr1.com",
      "Frankly, My Dear"
    ]
  ]
}

Root address (replaceable coordinate):

32176:3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690:gone-with-the-wind-1939

Example leaf — Twelve Oaks (first ~1000 MiB)

{
  "kind": 32176,
  "pubkey": "3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690",
  "created_at": 1735689601,
  "content": "",
  "tags": [
    ["d", "gone-with-the-wind-1939-twelve-oaks"],
    ["title", "Gone with the Wind — Twelve Oaks"],
    ["x", "f40069245c728a3dbfab8ce4efee8f14d77b425c74a00f3ac06b810b166d6a83"],
    ["b", "bc56e8d587133521b0da617b0475d18f9a8afacc45ae67549b8c2673273dec50", "1048576"],
    ["b", "41f0c7a44ba21e4532c46dc316fb822a3b71ecf4aa62daeb7919628cf5d8bfa2", "1048576"],
    ["b", "1b13f2bf2e8c265263cba6d73665e4a82b037fe7b4e194d3c187ac57644ea9d2", "1048576"],
    ["blossom", "https://cdn.example"]
  ]
}

(Real event: 1000 b tags for this scene’s byte range; only the first three are shown.)

Example leaf — Atlanta Burning

{
  "kind": 32176,
  "pubkey": "3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690",
  "created_at": 1735689602,
  "content": "",
  "tags": [
    ["d", "gone-with-the-wind-1939-atlanta-burning"],
    ["title", "Gone with the Wind — Atlanta Burning"],
    ["x", "f40069245c728a3dbfab8ce4efee8f14d77b425c74a00f3ac06b810b166d6a83"],
    ["b", "59809cbdde09122d9398736a1b15a70655a4fc2ae7ba21025d4c2bace4d506f4", "1048576"],
    ["b", "a0b115beda4c834c8eb34b620cedf4387583f076802717d129bf407be2282eb4", "1048576"],
    ["b", "ba664b3efc97c992f973f02e4e196474bef26a9af1a19e1d6d9d69a7d4c1b890", "1048576"],
    ["blossom", "https://cdn.example"]
  ]
}

(Real event: next 1000 pieces; digests above stand in for the first three of that range.)

Assembly from the root (no local b): Twelve Oaks ‖ Atlanta Burning ‖ I’ll Never Be Hungry Again ‖ Frankly, My Dear (3072 × 1 MiB = 3 GiB).

Example mixed branch (init segment + scenes)

Useful when the first bytes are an MP4/fMP4 init segment kept on the parent:

{
  "kind": 32176,
  "pubkey": "3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690",
  "created_at": 1735689610,
  "content": "",
  "tags": [
    ["d", "gone-with-the-wind-1939-mixed"],
    ["title", "Gone with the Wind (1939) — mixed index"],
    ["x", "f40069245c728a3dbfab8ce4efee8f14d77b425c74a00f3ac06b810b166d6a83"],
    ["size", "3221225472"],
    ["type", "media"],
    ["b", "05b12ab19cb0f2567054543e061a65aea32cfc9514cc6589e61ab1776e4564ea", "4096"],
    [
      "a",
      "32176:3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690:gone-with-the-wind-1939-twelve-oaks",
      "wss://thecitadel.nostr1.com",
      "Twelve Oaks"
    ],
    [
      "a",
      "32176:3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690:gone-with-the-wind-1939-atlanta-burning",
      "wss://nos.lol",
      "Atlanta Burning"
    ],
    [
      "a",
      "32176:3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690:gone-with-the-wind-1939-never-hungry-again",
      "wss://relay.damus.io",
      "I'll Never Be Hungry Again"
    ],
    [
      "a",
      "32176:3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690:gone-with-the-wind-1939-frankly-my-dear",
      "wss://thecitadel.nostr1.com",
      "Frankly, My Dear"
    ]
  ]
}

Assembly: init ‖ Twelve Oaks ‖ Atlanta Burning ‖ I’ll Never Be Hungry Again ‖ Frankly, My Dear.

Example with captions (transcript)

content holds searchable WebVTT; media bytes stay on Blossom:

{
  "kind": 32176,
  "pubkey": "3e1ad0f3a5d3c12245db7788546c43ade3d97c6e046c594f6017cd6cd4164690",
  "created_at": 1735689620,
  "content": "WEBVTT\n\n00:00:01.000 --> 00:00:04.000\nFrankly, my dear, I don't give a damn.\n",
  "tags": [
    ["d", "gone-with-the-wind-1939-captions-en"],
    ["title", "Gone with the Wind (1939) — English captions"],
    ["transcript", "vtt"],
    ["type", "media"],
    ["T", "gone-with-the-wind-1939-captions-en"],
    ["x", "f40069245c728a3dbfab8ce4efee8f14d77b425c74a00f3ac06b810b166d6a83"],
    ["b", "bc56e8d587133521b0da617b0475d18f9a8afacc45ae67549b8c2673273dec50", "1048576"],
    ["blossom", "https://cdn.example"]
  ]
}

Client algorithm (normative sketch)

  1. Resolve root 32176 by d / naddr.
  2. Recursively process each node: emit local b pieces in order, then recurse each a child in order (≤1000 children per node); reject cycles.
  3. Download missing pieces in parallel from Blossom servers; verify each SHA-256.
  4. Concatenate; verify full-file x if present.
  5. Optionally mirror pieces to a configured Blossom server.

Example: Imwald web client

Relationship to other specs

  • NKBIP-01 — same empty-content hierarchical a-index pattern for publications (30040 / 30041).
  • Blossom BUD-01 / BUD-03 / BUD-04 — blob GET, server lists, mirroring.
  • NIP-01 — single-letter #b filters for piece-hash discovery.
  • Not BitTorrent — no info-hash/DHT requirement; swarming is multiserver (and optional user Blossom nodes) over HTTP.

Review notes (non-normative)

  • Prefer empty content for the video bytes; put searchable dialogue in a separate captions index with transcript (as above), or keep VTT only in a leaf event. Missing transcript means txt.
  • #b is the discovery hook; #x is useful when relays index it, but not all do — do not rely on #x alone.
  • Keep piece size conventions consistent inside one tree (example: 1 MiB media pieces).
  • Cap of 1000 a / 1000 b per event forces fan-out for multi-GB films (here four scene leaves for a 3 GiB encode), same scaling idea as NKBIP-01 section indexes.
  • Prefer human a labels / d suffixes people recognize (scene names) over opaque part-000 counters when the split is editorial, not only mechanical.

Cited links

Discussion

Connect a key to comment.