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>
27 KiB
ipfile: Paid, Encrypted File Swarms
Version: 0.1 (draft) Status: draft. Extends the OpenSwarm core (spec.md). Nothing below is implemented.
1. Scope
ipfile is BitTorrent with three additions carried as extension messages:
- Encryption. The swarm carries ciphertext. The content key is sold separately and delivered only to a peer that has paid.
- Payment. A seeder serves pieces inside a bounded credit window and a leecher signs a cumulative voucher for every verified piece batch. Whoever seeds gets paid.
- Ownership. Every file has a key pair. The manifest that names the swarm, the price and the payout address is signed by it.
It is not a new transport. The wire is BEP 3, the extension is BEP 10, the hashes are BEP 52, discovery is BEP 5 and BEP 44, browsers are WebTorrent. A client that knows none of this can still join the swarm and, if the manifest allows, download ciphertext it cannot read.
Non-goals
Plaintext swarms (use a torrent). Hiding the size of a file. Hiding that a
swarm exists (use swarm.private to keep it off the DHT, but a tracker still
knows). Revoking a delivered key without re-encrypting. Streaming media
semantics (those are ipaudio, ipvideo, iplive).
2. Terminology
Core terms apply. In addition:
| Term | Meaning |
|---|---|
| manifest | The ipfile.manifest record: identity, roots, swarm, price, key delivery, policy. |
| revision | A manifest with a higher rev under the same file key. |
| keeper | A peer holding the content key with a delegate: true grant, allowed to grant it onward. |
| credit window | Bytes a seeder will serve beyond the last voucher before choking. |
| voucher interval | Bytes after which a leecher owes a new voucher. |
| vanilla peer | A peer that did not advertise ipfile in its extension handshake. |
3. Data model
3.1 The manifest
{
"openswarm": "0.1",
"type": "ipfile.manifest",
"file": "ed25519:0d87e09c7fea3ad6ba6c2f3e027ea47f5b245452899910948470906704c5295d",
"box": "x25519:4c4055604fe6bd8781cab6835093ad896f68f676b472d5bd6811e52f28cb18c3",
"publisher": "ed25519:5d292428e8a68946e5225136c8b10e8f33ab78a45e1663d0730996dd2b63d59e",
"fileId": "165aa20f2088f71a1880fe42c48d211b",
"keyDerivation": "derived",
"rev": 1,
"name": "interview-2026-09-05.flac",
"size": 734003200,
"contentType": "audio/flac",
"pieceLength": 1048576,
"plainRoot": "sha256:d6c3f8285b7871d6a400cba14408288a9acde679f12e1e7dc276f29ca7c493ff",
"cipherRoot": "sha256:ba8cd1a97b865a05cdc5840c63b086dd463624a831874c1ba0d7c4ce6f806f7a",
"cipher": {
"alg": "aes-256-ctr",
"keyId": "sha256:41d10f45e705e0526c9eeedf62b32dda3daaf552dd9b6a7b09e01776b05813bb"
},
"swarm": {
"infohashV1": "sha1:a3ce2180413415d7cf4268fb892b8ffd539e8459",
"infohashV2": "sha256:4b74eb43677e4d03af5fb0856333f9aa21d9a5a3bbf944b13aa7eef379c7a342",
"private": false,
"trackers": [
"wss://tracker.openwebtorrent.com",
"wss://tracker.webtorrent.dev",
"udp://tracker.opentrackr.org:1337/announce"
],
"webseeds": [
"https://gw.c0mpute.com/swarm/4b74eb43677e4d03af5fb0856333f9aa21d9a5a3bbf944b13aa7eef379c7a342/data"
]
},
"visibility": "public",
"policy": {
"vanilla": "ciphertext",
"creditBytes": 4194304,
"voucherBytes": 1048576
},
"pricing": {
"currency": "USD",
"perGib": "0.010000",
"keyUsd": "0.500000",
"split": { "publisherBps": 7000, "seederBps": 2500, "hubBps": 500 },
"keeperBps": 1000,
"hubs": ["https://coinpayportal.com/api/openswarm"],
"payTo": { "network": "eip155:8453", "address": "0xCC3b072391AE7A8d10cF00DdC5F61DB2cA5541E5" }
},
"keys": {
"modes": ["peer", "url"],
"url": "https://keys.example.com/openswarm",
"keepers": ["ed25519:d2d05fcad07ecbee6ff87c95159ec9969fce6c0fb9f41ff78c2c07ebe8a06c94"],
"owner": "HjYr1barG9BmJK6DEBSm9Rl8iGeYTZFGwfyG8AJy5g3Wk8uP9nyG1IeDaHuOT/VgYoNMmGvlCnaWw4olY6xM/0GKdzr05DqWt49benOyvAs="
},
"replication": { "min": 3, "erasure": { "k": 10, "parity": 4 } },
"meta": { "duration": 2941.2, "language": "en" },
"createdAt": "2026-09-05T18:00:00.000Z",
"updatedAt": "2026-09-05T18:00:00.000Z",
"sigs": [
{
"alg": "ed25519",
"key": "ed25519:0d87e09c7fea3ad6ba6c2f3e027ea47f5b245452899910948470906704c5295d",
"sig": "RGRH7pDpXYcagULqj+8z05pLH99Lc6yLmfdbYgtGeRpjX5l0NcT3QSq9AWBtLw+5+JJiKoZ3s4ty74q/zBSE8w=="
},
{
"alg": "ed25519",
"key": "ed25519:5d292428e8a68946e5225136c8b10e8f33ab78a45e1663d0730996dd2b63d59e",
"sig": "RGRH7pDpXYcagULqj+8z05pLH99Lc6yLmfdbYgtGeRpjX5l0NcT3QSq9AWBtLw+5+JJiKoZ3s4ty74q/zBSE8w=="
}
]
}
| Field | Rule |
|---|---|
file |
The file public key. REQUIRED. The record's first signature MUST be by this key. |
box |
The file box public key (core §4.2). REQUIRED. |
publisher |
The publisher identity. REQUIRED. A second signature by this key is REQUIRED. |
fileId |
16 bytes hex. REQUIRED. Stable across revisions. |
keyDerivation |
derived (from the publisher seed, core §4.2) or standalone. |
rev |
Integer, starts at 1, strictly increasing per file key. |
predecessor, successor |
File keys, for rotation (core §4.5). OPTIONAL. |
name |
Display name. Visible to anyone who obtains the manifest. MAY be empty. |
size |
Plaintext byte length. Ciphertext has the same length. |
contentType |
Media type of the plaintext. |
pieceLength |
Power of two, at least 16384. |
plainRoot, cipherRoot |
BEP 52 pieces roots (core §5.1). |
cipher.alg |
aes-256-ctr in this version. |
cipher.keyId |
sha256: of the content key. Identifies which key a grant carries without revealing it. |
swarm.infohashV1, swarm.infohashV2 |
Of the hybrid torrent whose one file is the ciphertext (core §5.3). |
swarm.private |
BEP 27 flag. |
swarm.trackers, swarm.webseeds |
Announce URLs and BEP 19 URLs. |
visibility |
public: any peer gets the manifest on request. pass: only a peer presenting a valid pass in scope. |
policy.vanilla |
ciphertext: serve pieces to vanilla peers free. deny: choke them. |
policy.creditBytes |
Credit window. Default 4194304. |
policy.voucherBytes |
Voucher interval. Default 1048576. MUST be at most creditBytes / 2. |
pricing |
See §7. keeperBps is taken from keyUsd, the split is applied to per-byte vouchers. |
keys.modes |
Any of peer (a key holder in the swarm grants on request), url (an HTTPS key service). |
keys.keepers |
Identities delegated to grant. Each MUST hold an ipfile.grant with delegate: true signed by the file key. |
keys.owner |
The content key sealed to box. Lets the owner recover the key from seed plus manifest. |
keys.public |
OPTIONAL. The content key itself, hex. Set only when the publisher wants anyone to decrypt (a free release, an ipdb segment). With it present, keyUsd MUST be 0.000000 and no grant is ever needed. |
replication |
Publisher's request: at least min independent seeders. erasure is advice to storage nodes, never a wire format (§9). |
meta |
Free-form. Member protocols define what they put here: ipaudio sets meta.track, ipvideo sets meta.title, ipdb sets meta.kind on segment files. |
bps values are basis points; the three in split MUST sum to 10000.
3.2 The grant
A grant carries the content key to one recipient:
{
"openswarm": "0.1",
"type": "ipfile.grant",
"file": "ed25519:0d87e09c7fea3ad6ba6c2f3e027ea47f5b245452899910948470906704c5295d",
"keyId": "sha256:41d10f45e705e0526c9eeedf62b32dda3daaf552dd9b6a7b09e01776b05813bb",
"grantor": "ed25519:d2d05fcad07ecbee6ff87c95159ec9969fce6c0fb9f41ff78c2c07ebe8a06c94",
"grantee": "x25519:de3c5a1791131231f797726a59b46b5b877628f0d10ae97fb5f119faa1ad1dc5",
"pass": "sha256:39655de63a29b02c90b236fa870fe6f8dfad1f52a8119fe4df85e88cd6ab02fd",
"delegate": false,
"expiresAt": null,
"sealed": "KrGWWxD4blHwoh7UmJedgv/CdxWz2J9cbRZpMY+09mm6FT7exyU7KjEAXmWRTLh2Xr8INCGKhv6dpjScKFGCkCrPZDyfAHE2s4E5/VXc2zQ=",
"createdAt": "2026-09-05T18:01:30.000Z",
"sigs": [
{
"alg": "ed25519",
"key": "ed25519:d2d05fcad07ecbee6ff87c95159ec9969fce6c0fb9f41ff78c2c07ebe8a06c94",
"sig": "RGRH7pDpXYcagULqj+8z05pLH99Lc6yLmfdbYgtGeRpjX5l0NcT3QSq9AWBtLw+5+JJiKoZ3s4ty74q/zBSE8w=="
}
]
}
| Field | Rule |
|---|---|
grantor |
The file key, or a keeper listed in the manifest. A grant by anyone else is invalid. |
grantee |
The recipient's box public key. |
pass |
Record id of the pass that paid keyUsd. null only when delegate is true. |
delegate |
True makes the grantee a keeper. Only the file key MAY sign a delegating grant. |
expiresAt |
Keeper delegation SHOULD expire. A paid grant is perpetual: the peer holds the key. |
sealed |
crypto_box_seal(contentKey, grantee), base64. 80 bytes for a 32-byte key. |
A grantee decrypts sealed with its box private key and checks
sha256(key) == keyId before using it.
4. Encryption
4.1 Piece encryption
The plaintext is split into pieces of pieceLength. Piece i is encrypted
independently under AES-256-CTR:
iv(i) = HKDF-SHA256(ikm = contentKey, salt = "", info = "openswarm:ipfile:iv:v1:" + decimal(i), L = 12) || 0x00000000
cipher(i) = AES-256-CTR(contentKey, iv(i), plain(i))
The 32-bit counter is big-endian and starts at zero, which is the WebCrypto
AES-CTR shape with length: 32. A piece is at most 2^32 blocks of 16
bytes, so the counter never wraps below 64 GiB per piece.
CTR is chosen over an AEAD because it is length-preserving and seekable: a
16 KiB block at offset o in piece i decrypts from counter o / 16
without touching the rest of the piece. Integrity does not come from the
cipher. It comes from two merkle trees:
- The swarm verifies every block of ciphertext against
cipherRootthrough the torrent's own v2 piece layers. A tampered block is rejected by any BitTorrent v2 client before this specification is involved. - The decrypting client verifies every plaintext piece against the plaintext
piece layer (§5.6), whose root is
plainRootin the signed manifest.
A malleable cipher whose output is pinned by a signed hash tree is not malleable in practice: any bit flip fails one of the two checks.
4.2 The content key
32 random bytes per file version. A new cipherRoot MUST use a new content
key; reusing a key across two plaintexts under the same IV derivation would
leak their XOR. The key never leaves a peer except inside a grant's sealed
box, over a key service response, or in keys.owner.
5. Wire protocol
5.1 Extension handshake and hello
After the BitTorrent handshake, both peers send the BEP 10 extension
handshake. A peer that supports this specification includes ipfile in m
and "openswarm": "0.1". Immediately after, it sends hello:
{ "t": "hello",
"v": 1,
"id": <32 bytes, Ed25519 public key>,
"box": <32 bytes, X25519 public key>,
"nonce": <16 bytes>,
"roles": ["seed"],
"hubs": ["https://coinpayportal.com/api/openswarm"],
"credit": 4194304,
"interval": 1048576,
"mtp": <optional 32 bytes, SHA-256 of the peer's MTP/1 SPKI>,
"sig": <64 bytes> }
sig is Ed25519 over:
"openswarm:hello:v1" || infohashV2 (32 bytes) || own peer_id (20 bytes) || remote peer_id (20 bytes) || nonce
Binding both peer_ids and the infohash ties the identity to this
connection in this swarm; a captured hello replays nowhere. roles is any
of seed, leech, keeper, gateway. credit and interval are the
window and the voucher interval this seeder will apply, which MUST be at
least the manifest's policy values.
A peer MUST NOT send any other ipfile message before it has sent hello
and received a valid one.
5.2 Messages
All payloads are bencoded dictionaries. r carries a record as UTF-8 JSON.
t |
Direction | Payload | Meaning |
|---|---|---|---|
hello |
both | §5.1 | Identity and terms. |
manifest_req |
any | { "rev": int or absent } |
Ask for the manifest, optionally a specific revision. |
manifest |
any | { "r": bytes } |
The manifest record. Sent unsolicited by a seeder after hello when visibility is public. |
layer_req |
leecher | {} |
Ask for the plaintext piece layer. |
layer |
seeder | { "d": bytes } |
32 bytes per piece, concatenated (§5.6). |
pass |
leecher | { "r": bytes } |
Present an ippay.pass. |
credit |
seeder | { "unpaid": int, "limit": int, "owed": string } |
Bytes served since the last accepted voucher, the window, and the amount owed. |
voucher |
leecher | { "r": bytes } |
An ippay.voucher. |
key_req |
leecher | { "keyId": bytes } |
Ask a key holder for a grant. |
grant |
key holder | { "r": bytes } |
An ipfile.grant sealed to the requester's box. |
deny |
any | { "what": string, "reason": string } |
Refuse pass, key_req or manifest_req with a registered reason. |
bye |
any | { "reason": string } |
About to disconnect. |
Registered deny and bye reasons: no-pass, pass-invalid,
pass-expired, pass-scope, pass-cap, unpaid, voucher-invalid,
voucher-stale, not-keeper, no-key, visibility, rate, done.
5.3 The paid session
leecher seeder
| BT handshake, BEP 10 handshake |
|---------------------------------------->|
|<----------------------------------------|
| hello |
|---------------------------------------->|
|<------------------------------- hello |
|<---------------------------- manifest | (public visibility)
| pass |
|---------------------------------------->| seeder verifies hub signature,
|<----------------------------- unchoke | scope, expiry, cap
| request / piece ... (vanilla) |
|<=======================================>| up to `credit` bytes unpaid
|<------------------------------ credit | unpaid >= interval: seeder asks
| voucher (cumulative) |
|---------------------------------------->| seeder verifies, unpaid resets
| request / piece ... |
|<=======================================>|
| key_req |
|---------------------------------------->| if seeder is a key holder
|<------------------------------- grant |
| bye done |
|---------------------------------------->|
Rules:
- A seeder MUST keep a leecher choked until a valid
passarrives, unlesspolicy.vanillaisciphertext, in which case an unpaid peer is treated as vanilla (§6) and gets no priority. - A seeder MUST NOT serve more than
creditbytes beyond the bytes covered by the last accepted voucher. When the window is full it chokes and sendscredit. It MAY disconnect withbye unpaidafter 60 seconds choked. - A leecher MUST send a voucher when unpaid bytes reach
interval, and only for bytes whose blocks it has verified against the v2 piece layer. A voucher is cumulative for this (pass, payee, swarm) triple (ippay§4). - A seeder MUST verify the voucher's signature, its monotonic
seq, thatbytesis at least the previous voucher'sbytes, and that the increase does not exceed what it actually served. A voucher that claims more than served is accepted for what was served and flagged; one that claims less isvoucher-staleand rejected. - A leecher MUST NOT sign a voucher for bytes it has not verified. It MAY sign for fewer than it has received; the seeder then chokes sooner. That is the only lever a leecher has against a slow seeder, and it costs nothing but throughput.
- Vouchers cover ciphertext bytes. Payment does not depend on the leecher ever obtaining the key.
5.4 Who may pay and who is paid
The voucher's payer is the pass's payer. The voucher's payee is the
seeder's hello.id. A seeder MUST reject a voucher whose payee is not its
own identity. The hub pays whoever redeems a voucher signed to them, so an
identity is a payout account. A seeder that wants to be paid registers its
identity and payout address at the hub once (ippay §6).
5.5 Key delivery
Peer mode. Any peer whose roles include keeper, or that is the file
key itself, answers key_req. It MUST verify that the requester's pass is
valid, in scope for this file, and has grant: true (meaning keyUsd was
paid). It then signs and sends a grant sealed to the requester's box. A
keeper MUST report the grant to the hub (ippay §5.4) to earn keeperBps
and to let the hub mark the pass's grant as consumed; a pass buys one grant
per file.
URL mode. GET <keys.url>/grant?file=<file key hex>&box=<x25519 hex>
with Authorization: Bearer <pass token> returns the grant record as JSON.
Without a pass it returns 402 with an x402 offer for keyUsd, so a client
that has no hub relationship can still buy a key directly (ippay §3.4).
Owner recovery. The owner decrypts keys.owner with the file box private
key derived from the seed.
5.6 The plaintext piece layer
The plaintext v2 piece layer is the sequence of per-piece SHA-256 merkle
roots (32 bytes each) whose root is plainRoot. A seeder holds it because it
had the plaintext or received it with the swarm. A leecher fetches it with
layer_req (or from a gateway at /swarm/<infohashV2>/layer) and MUST
verify that it hashes to plainRoot before trusting a single entry. After
decrypting a piece the leecher checks it against the corresponding entry.
For a 1 GiB file with 1 MiB pieces the layer is 32 KiB.
6. Coexistence with vanilla BitTorrent
A vanilla client sees a hybrid torrent with one file named by a hex string. It can download it if seeders allow. Specifically:
- The extension handshake without
ipfilemarks the peer as vanilla. - With
policy.vanilla: "ciphertext", a seeder treats it as any BitTorrent peer: normal choking algorithm, no credit accounting, no payment. It gets bytes it cannot decrypt. This is a free choice a publisher makes to widen the swarm; vanilla peers that then seed are unpaid redundancy. - With
policy.vanilla: "deny", a seeder keeps it choked and MAY disconnect. It still answersut_metadataso the peer can learn it has nothing to gain. - A vanilla peer never receives an
ipfilemessage, because BEP 10 forbids sending an extension a peer did not advertise. - The DHT, trackers and PEX carry infohashes only. A public tracker learns a swarm exists and how many peers it has, exactly as today.
- WebTorrent peers are vanilla until the page loads the
ipfileextension. bittorrented.com's player would add it withwire.use().
Encryption of the wire itself (the RC4 "message stream encryption" some clients offer) is neither required nor relied on. Confidentiality of content comes from piece encryption; confidentiality of the link, where wanted, comes from the MTP/1 tunnel (core §6.5).
7. Pricing and settlement hook
The manifest's pricing is the whole price list:
| Field | Meaning |
|---|---|
perGib |
USD per GiB of ciphertext served. Vouchers are computed from it (ippay §4.2). |
keyUsd |
USD for one grant. 0.000000 makes the key free to any pass holder. |
split |
Where each voucher's value goes: publisher, the seeder who served, the hub. |
keeperBps |
Share of keyUsd paid to a keeper that grants. The rest of keyUsd goes to the publisher. |
hubs |
Hubs the publisher accepts passes from. A seeder MUST reject a pass from a hub not listed. |
payTo |
Where the publisher's share is paid. CAIP-2 network and an address. |
royalties |
OPTIONAL. A list of { "label", "network", "address", "bps" } summing to 10000 that splits the publisher's share further. ipaudio and ipvideo records carry the same list; when both exist the manifest's wins, because the manifest is what the file key signed. |
Passes are bought over x402 exactly as @profullstack/x402-gateway sells
crawl passes today: a 402 whose body is CoinPay's x402 v2 offer, a proof in
X-PAYMENT, verify and settle through CoinPay. ippay §3 has the exchange.
A publisher who wants no payment at all sets perGib and keyUsd to
0.000000, and seeders serve for free with vouchers that carry zero; the
accounting still runs so the publisher sees who served what.
8. Publishing procedure
ip file add <path> does, in order:
- Load the publisher seed. Generate
fileId. Derive the file key and file box (core §4.2), or generate a standalone seed with--standalone-key. - Generate the content key. Compute
plainRootand the plaintext piece layer. Encrypt piece by piece; computecipherRoot. - Build the hybrid torrent: one file named by
cipherRoothex,piece lengthas chosen,privateper flag,announce-listfrom configuration,url-listfor any gateway. Compute both infohashes. - Seal the content key to the file box (
keys.owner). - Write the manifest with
rev: 1. Sign with the file key, then the publisher key. - Publish the BEP 44 pointer under the file key (core §7.2).
- Append an
ipdb.entryfor the manifest to the publisher's default feed (ipdb§4). - Start seeding. Announce to trackers. If any keeper is configured, send it a delegating grant.
A revision repeats steps 2 to 7 with rev + 1 and the same fileId. A
metadata-only revision (price, trackers, keepers) skips step 2 and keeps
cipherRoot, keyId and the swarm.
9. Storage and serving on a c0mpute node
A c0mpute storage node stores whatever it likes internally: today that is
blake3-addressed chunks with Reed-Solomon 10 data + 4 parity shards spread
over hosts. On the wire it MUST present whole ciphertext pieces verified
against cipherRoot. Erasure coding is a storage policy. replication. erasure in the manifest is the publisher saying what durability it is
paying for in a pin job; it never changes what a peer sends.
A node that holds only shards reconstructs the piece before serving it. A
node advertises c0mpute:role:storage and bids on ipfile.pin jobs
(c0mpute.md §3). Pin income and voucher income are separate: the pin job
pays for holding the bytes for a period, vouchers pay for serving them.
10. Retrieval procedure
ip file get <file key | infohashV2 | ip:// url>:
- Resolve to a manifest: BEP 44
getunder the file key, ormanifest_reqfrom a peer, or a gateway. - Verify both signatures. Verify
sha256of the canonical bytes matches any record id the caller supplied. - Choose a hub from
pricing.hubsthe client has an account with. Buy or reuse a pass in scope (ippay§3). Includegrant: trueif the key is needed. - Join the swarm. For each peer: exchange
hello, sendpass, download with sequential priority for streaming or rarest-first for bulk, sign vouchers on schedule. - Fetch and verify the plaintext piece layer. Obtain a grant from a key holder or the key URL. Decrypt pieces as they complete; verify each against the layer.
- On completion, verify the whole against
plainRoot. Sendbye done. - Keep seeding if configured. The client is now a seeder and earns.
11. Security notes specific to ipfile
- Freeloading leecher. Bounded by
creditBytes. A seeder loses at most one window per leecher identity per session and MAY refuse an identity it has seen freeload (ippay§5.5 reputation). - Freeloading seeder. Cannot happen: a voucher is only for bytes already received and verified. A seeder that stops after being paid was paid for what it sent.
- Voucher inflation. A seeder cannot forge a voucher; it lacks the
payer's key. A payer that over-states
bytespays more than it must; that is its own loss. - Double spend across seeders. A pass has a
capUsd. A payer could sign vouchers to many seeders totalling more than the cap. The hub honours redemptions in order until the cap is reached and reports the rest ascap-exceeded; each seeder's exposure is still one window. A payer that does this is reported and its passes are refused (ippay§5.5). - Content key exposure. A paying peer holds the key. Any peer can leak it. This family prices access, it does not prevent copying. Rotation is the remedy for a leaked key going forward, and only forward.
- Metadata. Size, piece length and both infohashes are public.
nameandmetaare visible to anyone who obtains the manifest;visibility: "pass"restricts that to pass holders. - Manifest substitution. The BEP 44 pointer is signed by the file key
and carries
seq; a stale pointer is refused by the DHT itself. A manifest from a peer is verified against the file key the client started from.
12. Events
| Type | Emitted by | data |
|---|---|---|
com.logicsrc.openswarm.ipfile.manifest.published.v1 |
publisher | record |
com.logicsrc.openswarm.ipfile.manifest.revised.v1 |
publisher | record |
com.logicsrc.openswarm.ipfile.grant.issued.v1 |
key holder | record (with sealed removed) |
com.logicsrc.openswarm.ipfile.session.closed.v1 |
seeder | { "swarm", "peer", "bytes", "vouchers", "reason" } |
13. Implementations
| Piece | Exists | Where | What is new |
|---|---|---|---|
| Hybrid seeder announcing to wss trackers, browser WebTorrent player, range streaming | Yes | media-streamer (src/lib/streaming/streaming.ts, src/hooks/use-webtorrent.ts) |
The ipfile extension in wire.use(), piece decryption in the player |
| DHT observation of infohashes | Yes | media-streamer/services/dht-search-api (bitmagnet), dht-infohash-crawler |
Nothing; an ipfile swarm is an opaque infohash to a crawler |
| x402 pass sale and verification | Yes | x402-gateway, CoinPay /api/x402/verify and /settle |
Pass scope and vouchers (ippay) |
| Content-addressed chunk store, erasure coding, gateway by hash | Yes | c0mpute (c0mpute-store, c0mpute-gateway) |
Serving pieces by index verified by SHA-256, the pin workload |
| Sealed box key wrapping | Yes | logicsrc credentials team vaults, OpenCreds team profile |
The grant record |
Manifest, file key derivation, piece encryption, hello, credit window, voucher exchange |
No | All of it |
14. Conformance
An ipfile implementation:
- Produces manifests per §3.1 with both signatures and a correct BEP 44 pointer.
- Encrypts and decrypts per §4 and verifies against both roots.
- Speaks every message in §5.2, refuses out-of-order messages per §5.1, and enforces §5.3 rules 2 to 5.
- Treats vanilla peers per §6 according to
policy.vanilla. - Issues grants only against a valid pass or a delegating grant per §5.5.
- Passes the published fixture swarm end to end: manifest, pass, three
vouchers, grant, decrypted output matching
plainRoot.
15. Version history
| Version | Date | Change |
|---|---|---|
| 0.1 | 2026-09-05 | Initial draft. |