BitTorrent distributes bytes better than any CDN and has never been a product: nobody is paid to seed, and nothing in it is private. Every Profullstack media property answers that with a central HTTP proxy and a pass system bolted on, and pays for every byte it serves. OpenSwarm is an add-on to BitTorrent, carried as BEP 10 extension messages, that fixes both. The swarm carries AES-256-CTR ciphertext whose integrity is pinned by two SHA-256 merkle roots in a signed manifest, so a tracker or DHT node learns an infohash and a size. A leecher buys a pass over x402 in USDC (the same exchange x402-gateway runs for crawl passes, settled by CoinPay), a seeder serves inside a bounded credit window, and the leecher signs a cumulative voucher for every verified batch. Whoever seeds gets paid. Vanilla clients remain valid members and browsers remain first-class peers. Adding a file mints a key pair for it, derived from one publisher seed by default so there is one thing to back up: the public half is the file's identity and its BEP 46 key, the private half signs the manifest and authorises grants and payout changes, and a separate content key encrypts the bytes and is sealed to paying peers. The family: a core (records, keys, hashing, transports, discovery, events), ipfile, ippay, ipdb (a signed hash-chained catalogue with heads on the DHT), ipaudio, ipvideo, iplive (paid relays with backpressure) and ipname (Moshpit pins and DNS TXT). Plus the c0mpute.com integration with seven workload types and thirteen use cases, a proposed ip CLI, conformance profiles, a security model and an FAQ. Registered on the site the way OpenCreds is: nav entry, docs registry, sitemap, a /openswarm landing page, PRD 0005, and the MCP prd_next_id expectation moved to 0006. Specs only. No code, no schemas, no reference implementation. Claude-Session: https://claude.ai/code/session_01YafYxayh7Gqe5MWNNQMev2 Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
15 KiB
ipdb: The Replicated Catalogue
Version: 0.1 (draft) Status: draft. Extends the OpenSwarm core (spec.md). Nothing below is implemented.
1. Scope
ipdb is the catalogue: the place a client asks "what is this swarm", "what
has this publisher released", "what is in this playlist". It defines:
- A feed: a signed, hash-linked, append-only log owned by one key.
- An entry: one log item that puts or deletes a record under a key.
- Heads: how the latest state of a feed is announced on the DHT.
- Replication: how feeds move between peers, as
ipfileswarms and as gossip on swarm connections. - Queries: a JSON query shape a local replica and a gateway both answer.
It is a database of records, not of bytes. A manifest is in ipdb; the
file it describes is in an ipfile swarm.
Non-goals
Multi-writer documents. A feed has one writer. Two people editing one playlist is two feeds and a view over both, or a future CRDT record type. Global search ranking. Consistency across feeds; each feed is consistent with itself and that is all that is promised.
2. Terminology
| Term | Meaning |
|---|---|
| feed | A log identified by (feed key, name). |
| feed key | An Ed25519 key. A publisher's default feed uses the publisher key itself. |
| entry | A record of type ipdb.entry, sequenced and chained. |
| key | A string unique within a feed that later entries overwrite. |
| head | The latest entry of a feed. |
| segment | A batch of entries stored as one file for replication. |
| replica | A peer holding some feeds, verified, queryable. |
| follow | A replica's decision to keep a feed current. |
3. Data model
3.1 Feed keys
The default feed is the publisher key with name default. Named feeds
derive from the publisher seed the same way files do:
feedSeed = HKDF-SHA256(ikm = publisher seed, salt = utf8(name), info = "openswarm:feed:v1")
feed key = Ed25519 keypair from feedSeed
A feed reference is written ed25519:<hex>/<name>; the default feed is
ed25519:<hex>/default. Names match ^[a-z0-9][a-z0-9-]{0,63}$.
3.2 Entry
{
"openswarm": "0.1",
"type": "ipdb.entry",
"feed": "ed25519:5d292428e8a68946e5225136c8b10e8f33ab78a45e1663d0730996dd2b63d59e",
"name": "default",
"seq": 12,
"prev": "sha256:ba8cd1a97b865a05cdc5840c63b086dd463624a831874c1ba0d7c4ce6f806f7a",
"op": "put",
"key": "file:ed25519:0d87e09c7fea3ad6ba6c2f3e027ea47f5b245452899910948470906704c5295d",
"kind": "ipfile.manifest",
"subject": "sha256:41d10f45e705e0526c9eeedf62b32dda3daaf552dd9b6a7b09e01776b05813bb",
"record": {
"openswarm": "0.1",
"type": "ipfile.manifest",
"file": "ed25519:0d87e09c7fea3ad6ba6c2f3e027ea47f5b245452899910948470906704c5295d",
"rev": 1,
"createdAt": "2026-09-05T18:00:00.000Z",
"sigs": []
},
"createdAt": "2026-09-05T18:00:05.000Z",
"sigs": [
{
"alg": "ed25519",
"key": "ed25519:5d292428e8a68946e5225136c8b10e8f33ab78a45e1663d0730996dd2b63d59e",
"sig": "RGRH7pDpXYcagULqj+8z05pLH99Lc6yLmfdbYgtGeRpjX5l0NcT3QSq9AWBtLw+5+JJiKoZ3s4ty74q/zBSE8w=="
}
]
}
(The embedded manifest is abbreviated here; in a real entry it is the complete record with its signatures.)
| Field | Rule |
|---|---|
feed, name |
The feed. The entry MUST be signed by feed. |
seq |
1 for the first entry, then prev.seq + 1. |
prev |
Record id of entry seq - 1, or null for seq 1. |
op |
put or del. |
key |
The logical key. Convention: <noun>:<identifier>, e.g. file:ed25519:..., track:sha256:..., playlist:my-mix. |
kind |
The embedded record's type. Absent for del. |
subject |
The embedded record's id. Absent for del. A replica MUST recompute it and refuse a mismatch. |
record |
The complete embedded record. Absent for del. Its own signatures MUST verify. |
An entry is at most 256 KiB. A record larger than that is published as an
ipfile and the entry embeds an ipfile.manifest pointing at it, with
kind naming the payload type in record.meta.kind.
3.3 State
The state of a feed is the map key -> latest put record after applying
entries in seq order, with del removing the key. A replica exposes both
the log (for audit and replication) and the state (for queries). A record
that was overwritten stays in the log; that is what makes a manifest
revision history free.
Across feeds there is no merge. A query over several feeds returns matches
from each; the feed of every result is returned with it.
3.4 Head
{
"openswarm": "0.1",
"type": "ipdb.head",
"feed": "ed25519:5d292428e8a68946e5225136c8b10e8f33ab78a45e1663d0730996dd2b63d59e",
"name": "default",
"seq": 12,
"head": "sha256:41d10f45e705e0526c9eeedf62b32dda3daaf552dd9b6a7b09e01776b05813bb",
"segments": [
{ "from": 1, "to": 10, "file": "ed25519:d2d05fcad07ecbee6ff87c95159ec9969fce6c0fb9f41ff78c2c07ebe8a06c94" }
],
"createdAt": "2026-09-05T18:00:05.000Z",
"sigs": [
{
"alg": "ed25519",
"key": "ed25519:5d292428e8a68946e5225136c8b10e8f33ab78a45e1663d0730996dd2b63d59e",
"sig": "RGRH7pDpXYcagULqj+8z05pLH99Lc6yLmfdbYgtGeRpjX5l0NcT3QSq9AWBtLw+5+JJiKoZ3s4ty74q/zBSE8w=="
}
]
}
segments lists the ipfile swarms that hold sealed ranges of the log
(§5.1). Entries after the last segment are fetched by gossip (§5.2).
4. Publishing
ip db put <key> <record.json> appends an entry. ip file add appends one
automatically under file:<file key> in the default feed. ip audio publish, ip video publish and ip live create do the same for their
records. A publisher never edits a feed by hand; every tool that creates a
record appends it.
Rules for the writer:
seqMUST be strictly sequential. A gap is a fork.- Two entries with the same
seqfrom the same feed are a fork. A replica that observes a fork MUST keep both, mark the feed forked from thatseq, and stop applying either branch to state until the feed publishes anipdb.entrywithop: "put",key: "ipdb:resolve", and a record naming the surviving branch head. Forks happen when a seed is restored on two machines; they are detected, not prevented. - A
delof a key that was never put is valid and does nothing.
5. Replication
5.1 Segments as ipfile swarms
Every 1000 entries, or daily, the writer seals entries from..to as one
file: newline-delimited canonical JSON, one entry per line. It publishes the
file as an ipfile with pricing.perGib and keyUsd at 0.000000 and
keys.public set to the content key, so the swarm is readable by anyone
while staying an ordinary ipfile for seeders, gateways and c0mpute pin
jobs. The head lists the segment. Readers verify each line's chain as they
apply it, so a segment served by a stranger is safe.
5.2 Gossip on swarm connections
Peers that advertise ipdb in the BEP 10 handshake exchange heads on any
swarm connection:
t |
Payload | Meaning |
|---|---|---|
heads |
{ "f": [ { "k": <32 bytes>, "n": string, "s": int } ] } |
Feeds I follow and the seq I have. At most 64 per message. |
entries_req |
{ "k": <32 bytes>, "n": string, "from": int, "to": int } |
Send me entries in the range. At most 100. |
entries |
{ "r": [bytes] } |
Entries as JSON, in seq order. |
head |
{ "r": bytes } |
An ipdb.head record, when the sender has a newer one. |
A peer sends heads once after hello and again whenever a followed feed
advances. A peer that receives a heads with a higher s for a feed it
follows sends entries_req. Gossip fills the tail after the last segment
and keeps replicas close to real time without any peer being special.
5.3 DHT announcement
The head is a BEP 44 mutable item (core §7.3):
k = feed key
salt = "ipdb:" + name
seq = head seq
v = bencoded { "h": <head record id, 32 bytes>, "s": <seq> }
A client with only a feed reference does get on the DHT, obtains h,
then asks any peer or gateway for the head record by id, then segments and
tail. A gateway also serves GET /db/feeds/<key>/<name>/head.
5.4 Following
ip db follow ed25519:<hex>/default marks a feed followed: the replica
keeps its head current by DHT polling (every 10 minutes, or on head
gossip), fetches segments, and applies entries. A c0mpute node with role
index follows every feed it is paid to follow (c0mpute.md §3).
6. Queries
6.1 Shape
{
"type": "ipaudio.track",
"feeds": ["ed25519:5d292428e8a68946e5225136c8b10e8f33ab78a45e1663d0730996dd2b63d59e/default"],
"where": [
["record.artist", "==", "Ada"],
["record.durationMs", ">=", 120000]
],
"orderBy": [["record.createdAt", "desc"]],
"limit": 20,
"cursor": null
}
| Field | Rule |
|---|---|
type |
The embedded record type to match. REQUIRED. |
feeds |
Feed references to search. null means every followed feed. |
where |
Conjunction of [path, op, value]. Paths dot into the entry; record. reaches the embedded record. |
orderBy |
List of `[path, "asc" |
limit |
1 to 200. |
cursor |
Opaque, from a previous result. |
Operators: ==, !=, <, <=, >, >=, in, contains (array
membership or substring), prefix. No joins, no aggregation in 0.1; a
rating average is computed by the caller from the entries it gets back.
6.2 Result
{
"results": [
{
"feed": "ed25519:5d292428e8a68946e5225136c8b10e8f33ab78a45e1663d0730996dd2b63d59e/default",
"key": "track:sha256:d6c3f8285b7871d6a400cba14408288a9acde679f12e1e7dc276f29ca7c493ff",
"seq": 14,
"subject": "sha256:d6c3f8285b7871d6a400cba14408288a9acde679f12e1e7dc276f29ca7c493ff",
"record": { "openswarm": "0.1", "type": "ipaudio.track", "sigs": [] }
}
],
"cursor": "eyJzZXEiOjE0fQ",
"asOf": { "ed25519:5d292428e8a68946e5225136c8b10e8f33ab78a45e1663d0730996dd2b63d59e/default": 27 }
}
asOf reports the head seq of each feed the answer was computed from, so a
caller can tell a stale replica from an empty one.
6.3 Over HTTP
POST /db/query on a gateway with the query as the body. A gateway MAY
require a pass (Authorization: Bearer) and MAY sell one over x402 for
queries the way a hub sells swarm passes; the reference price is
0.000100 USD per query. A public gateway with no charge is the common case
and is what bittorrented.com's /dht browse would become for ipfile
swarms it can resolve.
7. Record kinds carried
ipdb carries any record type. These are the ones the family defines and
the keys they use:
| Kind | Key convention | Defined in |
|---|---|---|
ipfile.manifest |
file:<file key> |
ipfile |
ipaudio.release |
release:<record id of first revision> |
ipaudio |
ipaudio.track |
track:<record id of first revision> |
ipaudio |
ipvideo.title |
title:<record id of first revision> |
ipvideo |
iplive.channel |
channel:<channel key> |
iplive |
ipname.pin |
name:<moshpit name> |
ipname |
ipdb.playlist |
playlist:<slug> |
this document §7.1 |
ipdb.rating |
rating:<subject record id> |
this document §7.2 |
7.1 Playlist
{
"openswarm": "0.1",
"type": "ipdb.playlist",
"slug": "late-night",
"title": "Late night",
"items": [
{ "kind": "ipaudio.track", "subject": "sha256:d6c3f8285b7871d6a400cba14408288a9acde679f12e1e7dc276f29ca7c493ff", "feed": "ed25519:5d292428e8a68946e5225136c8b10e8f33ab78a45e1663d0730996dd2b63d59e/default" }
],
"createdAt": "2026-09-05T19:00:00.000Z",
"sigs": []
}
A playlist item references a record by id and the feed it was found in, so
a reader can fetch it even if the playlist author's replica is gone. The
media-streamer collections table maps onto this one to one.
7.2 Rating
{
"openswarm": "0.1",
"type": "ipdb.rating",
"subject": "sha256:d6c3f8285b7871d6a400cba14408288a9acde679f12e1e7dc276f29ca7c493ff",
"value": 4,
"createdAt": "2026-09-05T19:05:00.000Z",
"sigs": []
}
value is 1 to 5. One rating per subject per feed, by the key convention.
An aggregate is query type ipdb.rating where record.subject == ... over
the feeds a caller trusts; a gateway MAY cache the count and mean.
8. Local storage
A replica stores entries in a local database. The reference layout is
SQLite with tables feeds(key, name, seq, head, forked_at),
entries(feed, name, seq, id, prev, op, key, kind, subject, json),
state(feed, name, key, seq, kind, subject), and one FTS table over
record.title, record.artist, record.name for the contains
operator. This is the layout the ip CLI would use; a gateway on Postgres
does the same with jsonb. Nothing in the wire depends on it.
9. Security notes
- Authenticity. Every entry is signed by the feed key and chained. A replica cannot insert, reorder or drop an entry without breaking the chain to the announced head.
- Freshness. The DHT head carries
seq; BEP 44 refuses a lowerseq. A replica that shows an old head is stale, not lying, andasOfsays so. - Embedded records verify on their own signatures. A feed cannot put
someone else's manifest under its own key and have it trusted as theirs;
the manifest's
publishersignature is the authority, the feed entry is the announcement. - Spam. A gateway that follows any feed anyone asks for will be filled.
Following is a decision;
c0mputeindex jobs are paid per followed feed for that reason. - Privacy. A feed is public by construction. A private catalogue is a
segment published as a paid
ipfile(nokeys.public), and the entries are then readable only by pass holders. The head still reveals the seq.
10. Events
| Type | Emitted by |
|---|---|
com.logicsrc.openswarm.ipdb.entry.appended.v1 |
writer |
com.logicsrc.openswarm.ipdb.head.published.v1 |
writer |
com.logicsrc.openswarm.ipdb.fork.detected.v1 |
replica |
11. Implementations
| Piece | Exists | Where | What is new |
|---|---|---|---|
| Infohash catalogue with browse and search | Yes (central) | media-streamer dht_torrents, /api/dht/browse, services/dht-search-api |
Learning what an infohash is from a signed feed instead of BEP 9 names |
| Collections, playlists, favourites | Yes (central) | media-streamer collections, collection_items |
The ipdb.playlist record as the portable form |
| Signed append-only log | Partly | Moshpit moshpit_tld_log (server side, seq cursor) |
The feed as a client-verifiable chain with DHT heads |
| Entry, head, segments, gossip, query shape | No | All of it |
12. Conformance
A replica verifies chains and embedded signatures per §3, detects forks
per §4, replicates by segments and gossip per §5, and answers every
operator in §6.1. A writer never emits a gap. A gateway serves POST /db/query and the head route in §5.3.
13. Version history
| Version | Date | Change |
|---|---|---|
| 0.1 | 2026-09-05 | Initial draft. |