Developer guide (full text) · in Chinese for now. The developer guide is bilingual, and AI assistants can follow this document as is (Markdown).
开发者指南
2026-10-16 由主编写窗口编写(CHANGELOG 第 291 条,D-249);2026-10-17 第 312 条按签名指针(§6.5,D-283)改原第 8 节(现第 16 节)第 3 项;第 315 条按直接网页托管(§6.6,D-281、D-286~D-288)加同一节第 5 项;2026-10-22(UTC+8)按 D-335~D-353 改成开发者指南:前面加「做产品」的 8 节(两种模版、存 3 家、权限、文件网址、改和删、费用、做不到的、客户端库),原来的须知挪到第 9~18 节;2026-10-23(UTC+8)按 D-354 加示例代码的下载地址,按 D-356 在第 8 节加中继网关的接口一览,按 D-359 在接口一览后的说明和第 16 节第 5 项加网站统计。写给基于 TapeRelay 做产品(网盘、视频站、网页应用)的开发者。规范见 spec/SPEC.md(英文版 spec/SPEC.en.md);运营方看 docs/OPERATORS.md;给 AI 的版本见 DEVELOPERS-FOR-AI.md。
一句话#
中继网关替你存文件、传文件;权限靠容器(持有人的签名),不靠 API key;每个文件同时存 3 家不同运营方。 TapeRelay 不替你运行程序;公开文件谁都能读,而且收不回来;密钥、密码不要放进来,要保密的内容用加密文件。
1. 两种做法#
| 模版 A:用开发者的容器 | 模版 B:用用户的容器 | |
|---|---|---|
| 适合 | 网盘、内容平台:用户不用钱包,用你自己的账号体系登录 | DeWEB 视频站、个人网站:用户用自己的钱包、自己的容器 |
| 文件记在 | 你(开发者)的容器名下 | 用户的容器名下 |
| 谁签名 | 你的后台:持有一个专用钱包的私钥,浏览器把要发的请求交给后台,后台核对后逐个签名(文件不经过后台) | 用户的钱包授权一把会话密钥,授权里写明应用(你的容器)和范围(例如上传、更新列表) |
| 删除 | 后台先隐藏(能恢复),到期后批量释放 | 用户的钱包在每台各签一次 |
| 你要有 | 一个后台(Node.js,示例 100 多行) | 只要静态网页,没有后台 |
| 示例 | examples/template-a/ |
examples/template-b/ |
两种可以混用:例如平台的公共内容放在你的容器(A),用户自己的作品放在用户的容器(B)。官方前端(ui/app/)就是一个完整的 B 做法。
示例代码(两个模版和它们的测试,连同用到的客户端库)下载:curl -fsSL https://taperelay.hivehubs.app/download/examples | tar -xz,解开是 taperelay-examples/;在里面的 TapeRelay/ 目录先 npm ci --ignore-scripts(要 Node.js 20.19 以上),本文的路径都相对这个目录。
模版 A 的专用钱包:私钥只放在后台的密钥文件里(示例从 TapeRelay/ 目录里的 .env 读)。后台被攻破,攻击者就能做容器持有人能做的一切(删光文件、把容器转走),所以这个钱包只当这一个容器的持有人,只充一点付手续费的 BNB;不要用放钱的钱包。
2. 存 3 家:怎么挑、怎么传#
- 挑:从运营方名单(官方的在
https://taperelay.hivehubs.app/api/operators)随机挑 3 家不同运营方(同一主机的几个名字算一台,名字、编号、主机有一样相同的算一家):只挑连得上、是 TapeRelay、没宣布退出、默认配额够、价格不超过你设的上限、支持你要的功能(/v1/info的features)的;注册表部署之后只挑在保的(规范 §3.3,D-342)。客户端库:parseOperatorList、checkOperator、pickOperators。挑好的写进容器的发现文件(链上,一笔交易)。 - 传:同时传 3 台,存好 2 台就算存好,告诉用户「已存好」;第 3 台在后台隔 5 秒、30 秒、2 分钟再试(
uploadToServers,D-343)。服务器之间不互相复制。 - 补:慢的那台只在页面开着时接着传。之后核对、补齐:
checkReplicas问每台记着没有,repairReplicas先认领、认领不到的从存着的台读出再传。模版 A 的后台定时补;模版 B 由用户打开页面时「核对并补齐」。 - 换:一家一直不行(连不上、宣布退出),另挑一家(
pickOperators的exclude写在用的几台),改发现文件(一笔交易),再补齐。换下来的那台上的不会自动删。 - 读:一台不通换下一台(§5.4、§6.4)。
3. 权限:靠容器,不靠 API key#
服务器只认两种签名:容器持有人的钱包,和持有人授权的会话密钥(§8)。没有账号、没有 API key。
- 会话密钥的授权(
authorizeSession)写明:哪几台服务器、多长时间(最长 24 小时)、应用(你的容器地址,只用于显示和统计,网页自己填的,不能证明是谁)、范围:
| 范围 | 能做什么 |
|---|---|
objects |
上传、认领、列出、查用量 |
send |
以这个容器的名义交出文件通知 |
inbox |
拉取收件箱、标记已读 |
pointers |
写签名指针 |
只要你用得到的范围;钱包签名时用户看得到应用和范围。释放(删除)只认钱包,哪种授权都不行(§5.3)。
- 还没升级的服务器(
/v1/info的features没有scopes):splitByScopes把它们分出来,authorizeSession对它们另签一份旧格式的(钱包多签一次),旧格式的只能当objects用。 - 模版 A:后台不发会话密钥,每个请求都用钱包直接签。浏览器用
remoteAuth把要发的请求(服务器、方法、路径、请求体的哈希、时间、随机数)交给后台;后台用createRemoteSigner核对是自己的容器、自己的服务器、只是上传和认领、时间相差 2 分钟以内,再核对是这个用户登记过的文件,才签(D-345)。签名绑定服务器和内容,300 秒内只能用一次。
4. 播放和下载:按文件读取的网址#
GET /v1/files/{容器地址}/{清单哈希}(§6.7,D-344;客户端库fileUrl):不要签名,网页的<video src>、<img src>直接用,支持单段Range(视频能拖动进度)。服务器支持时/v1/info的features有files。- 只给记在这个容器名下的公开文件;服务器按清单逐块核对之后才发。图片(SVG 除外)、音频、视频、纯文本按原类型给,其余一律当下载;所有回复带
Content-Security-Policy: sandbox。 - 浏览器不核对这种网址读到的内容:运营方可以给别的。给用户看这种网址时写明(
FILE_URL_NOTE);要核对的用客户端库读(readFile,逐块核对哈希)。加密文件不能这样播。 - 流量记在这个容器名下,
/v1/usage的downloads给出最近 30 天的合计;只记不收费。
5. 改和删#
- 对象的内容永远不变(§5.1)。「改」就是传新版本、再改指向:你自己的索引(数据库、列表文件),或签名指针(§6.5,D-348)。模版 B 的频道就是一个公开的列表文件,由签名指针
videos指着。 - 删除 = 释放(§5.3):只认持有人的钱包,一次最多 1,000 个对象,每台各释放一次。释放之前用量照算。释放只去掉自己名下的记录:按文件读取的网址变成 404,字节由运营方按政策删(没有别的容器记着的,最多再留
/v1/info的retention.afterLastReferenceDays天,默认 30),删之前知道哈希的人照样能按哈希读到。要保密的内容先加密。 - 共用的对象:块按 1 MiB 切、按哈希存,内容相同或有相同片段的文件共用对象。删一个文件时,别的文件还在用的对象不能释放(两个模版都这样做)。
- 模版 A:后台先隐藏,
releaseAfterHours之后定时批量释放(D-338)。模版 B:用户的钱包在每台签一次(3 家 3 次,D-347);用户在钱包里拒绝了,留着下次「整理」再释放。
6. 费用#
- 存储按容器买套餐(
docs/OPERATORS.md第三节):模版 A 你买自己容器的;模版 B 你可以替用户的容器买(付款合约的buy、buyMany可以给任意容器买,一次付款买几家),也可以让用户自己买(D-346)。每家有免费的默认配额(/v1/info的quota)。 - 写发现文件要付 BNB 手续费(模版 B 由用户的钱包付)。
- 下载流量目前不收费,运营方会限流;
downloads只记按文件读取的,绕开它直接按对象读就不记,所以不能用来可靠地向你收费。
7. 做不到的#
- 「确保不丢」只能降低概率:运营方会按政策删、会关门,服务器之间不复制;模版 B 只有用户本人能补副本。
- 不能原地修改:只能传新版本、改指向。
- 删除不等于从网络上消失:读取不要签名,谁都能下载、认领同样的内容;释放后运营方还留一段时间。
- 模版 B 里用户不在场时,你管不了用户的文件:补副本、换服务器、删除都要用户打开页面(删除、换服务器还要用户的钱包)。
- 普通的
<video src>地址浏览器不核对,加密视频不能这样播。 - 下载流量没法可靠地向你收费(见上一节)。
- 在 TapeKit 外壳里运行的网页另有限制(链外请求默认拦截等,§13.4),开通月费、能不能替别人付开通费,见
docs/FOR-TAPEOUT.md。
8. 客户端库速查#
client/src/index.mjs,普通 JavaScript,浏览器和 Node.js 都能用(只依赖 @noble)。浏览器里的网址布局照 examples/static.mjs。
| 做什么 | 用什么 |
|---|---|
| 连服务器 | createClient({ fetch });client.info(名字)、client.usage(名字, 授权) |
| 挑运营方 | parseOperatorList、checkOperator、pickOperators({ count: 3, features, exclude }) |
| 发现文件 | discoveryJson、serverEntryOf、parseDiscovery、writeSiteFile(链上,交易)、DISCOVERY_PATH |
| 授权 | keyWallet(Node 里用私钥)、eip1193Wallet(浏览器钱包);authorizeSession({ app, scopes })、splitByScopes、sessionAuth、walletAuth |
| 模版 A 的签名 | 浏览器 remoteAuth({ account, endpoint });后台 createRemoteSigner({ wallet, account, servers }) 的 check、sign |
| 上传 | planPublicFile(文件, { withManifest: true })、uploadToServers(client, 服务器, 授权, 文件, { plan, onProgress }) → { stored, ok, pending, settled } |
| 读 | fileUrl(给 <video>,不核对)、readFile、readObject(核对)、fileObjects(一个文件的全部对象) |
| 副本 | checkReplicas、repairReplicas |
| 删除 | client.release(名字, 钱包授权, 对象) |
| 指针 | makePointer、signPointer、publishPointer、readPointer |
中继网关的接口#
每台中继网关都提供这些接口(规范附录 A)。一般不用直接调:客户端库会签名、核对。权限一栏:公开不要签名;写范围的,认持有人的钱包或有这个范围的会话密钥;仅钱包只认持有人的钱包。
| 接口 | 做什么 | 客户端库 | 权限 |
|---|---|---|---|
GET /v1/info |
服务器信息:配额、价格、支持的功能 | client.info |
公开 |
GET /v1/usage/{account} |
用量和配额 | client.usage |
objects |
POST /v1/objects |
上传一个对象 | client.upload |
objects |
POST /v1/objects/claim |
认领服务器上已有的对象 | client.claim |
objects |
GET /v1/objects |
列出记在名下的对象 | client.list |
objects |
GET /v1/objects/{sha256} |
读一个对象,支持 Range |
client.getObject |
公开 |
HEAD /v1/objects/{sha256} |
查是否存在、多大 | client.has |
公开 |
POST /v1/objects/release |
释放(删除),一次最多 1,000 个 | client.release |
仅钱包 |
GET /v1/files/{account}/{manifest} |
读整个文件,可以直接播放(第 4 节;也支持 HEAD) |
fileUrl |
公开 |
PUT /v1/pointers/{account}/{name} |
写签名指针 | client.putPointer |
pointers |
GET /v1/pointers/{account}/{name} |
读签名指针 | client.getPointer |
公开 |
POST /v1/outbox |
发通知,由自己的服务器转交 | client.submitNotice |
send |
GET /v1/outbox/{id} |
查投递状态 | client.outboxStatus |
公开 |
GET /v1/inbox/{account} |
拉取收到的通知 | client.inbox |
inbox |
POST /v1/inbox/{account}/read |
标记已读 | client.markRead |
inbox |
POST /v1/revocations |
撤销会话密钥(持有人的签名在请求体里) | client.revoke |
仅钱包 |
{account} 是容器地址(eip155:56:0x…),{manifest} 是清单对象的 SHA-256。/v1/files 和按范围授权,只在 /v1/info 的 features 列出 files、scopes 的服务器上能用。另有服务器之间转交通知的 POST /v1/inbox/{account}、给网站排行汇总用的 GET /v1/stats/sites(§10.7),和运营方自己用的 /v1/admin/*,开发者用不到。
9. 能放什么、不能放什么#
| 能放 | 不能放 |
|---|---|
| 网页(HTML、JS、CSS、WebAssembly)、图片、视频、文档、数据文件 | 后台程序、数据库服务、定时任务(TapeRelay 不运行它们) |
| 只给自己或指定的人看的内容(用加密文件) | 任何密钥、密码、API Key、私钥、助记词(放进公开文件就等于公开) |
10. 公开文件谁都能读#
- 读取不要签名:知道文件的哈希(或拿到分享链接)就能下载(规范 §5.4)。链接会被转发、被搜索、被缓存。
- 上传之后收不回来:删除只是让运营方「可以」删掉字节(§5.3),别人可能早已下载、缓存了。
- 所以公开文件里绝不能有密钥。这一点和放在任何网站托管、CDN 上一样:写进前端代码的 API Key,本来就会暴露给每个访客。
- 参考前端在上传公开文件之前会查常见的密钥写法,查到了先问你(D-249;客户端库的
scanForSecrets)。它只认几种明确的写法、查不全(例如助记词不查),不能代替你自己检查。
11. 密钥放在哪里#
- 放在你自己的后台或云函数里:网页去调你的后台,由后台带着密钥去调第三方服务。
- 网页里只放本来就能公开的 Key:限定了域名、只读、额度很小的那种。
- 节点网址里带着密钥的(例如某些区块链节点服务的网址),同样只能放在你的后台。
12. 要保密的内容:加密文件#
- 在本机加密后上传,运营方和网络上的人只看到乱码(规范 §7);上传时可以「包给」别的容器,只有它们和你解得开。
- 加密适合「自己或指定的人看」,不适合藏网站的秘密:如果网页要在访客的浏览器里解密,就得把钥匙交给访客,等于公开。
- 能看出来的:文件大概多大(只把最后一块补齐)、什么时候上传、存在哪个容器下。
13. 谁能写、怎样自动部署#
- 只有容器持有人的钱包,或持有人授权的会话密钥,能上传、认领(§8)。会话密钥最长 24 小时、只能在授权里写的服务器上用、只能做授权里写的范围(第 3 节)、可以撤销。
- 删除(释放)每次都要持有人的钱包直接签,会话密钥签的不收(§5.3):会话密钥被偷,也删不了你的文件。
- 自动部署用会话密钥,不要把钱包私钥放进部署脚本、CI 的环境变量或代码仓库。
- 要知道(第 295 条,I-228 第 5 点):会话密钥最长 24 小时,每一把都要持有人的钱包签一次授权(§8.3)。所以自动部署每天都要有人用钱包签一次新的授权,把新的会话密钥交给部署脚本;过期了部署就会报「授权无效」。不要为了省事把钱包私钥放进脚本。
- 范围里有
send的会话密钥能以你的容器的名义发文件通知;收件人会看到「这条通知由会话密钥签发」和授权的有效期(§9.7)。
14. 运营方能看到什么#
| 能看到 | 看不到 |
|---|---|
| 谁(IP 地址)、什么时候读了哪个文件、文件多大 | 加密文件的内容 |
| 文件是哪个容器存的、什么时候存的 | 你的钱包私钥、会话密钥的私钥 |
| 文件通知:谁发给谁、通知的是哪个文件(通知不加密,§12) |
运营方可能因法律原因下架某个文件(读取回 451,客户端换下一台,§5.4):这也是要多存几家的原因之一(第 2 节、第 17 节)。
15. TapeRelay 能不能运行程序#
服务器上不能,也不应该。 原因:
- 核对不了结果:TapeRelay 的一切都能按哈希、签名核对,所以你不必信任运营方。程序的运行结果没法这样核对,只能信运营方,就丢掉了最根本的保证。
- 安全和滥用:在运营方的机器上跑别人的代码,要隔离、要限资源,还要防挖矿和攻击,第三方运营方负担很重。
- 定位:TapeRelay 学的是 SMTP/IMAP 的分工,只管「存」和「送」;v0.1 只按存储量计费,算力没法计费。
浏览器里能。 存进去的网页、脚本,会在访问者的浏览器里运行,跟静态网站托管一样。推荐的分工:
| 放在哪里 | 放什么 |
|---|---|
| TapeRelay | 界面(静态网页)、图片视频、公开的数据文件;要保密的数据用加密文件 |
| 链上合约 | 需要大家都信得过的逻辑和状态(转账、记账、权限) |
| 你自己的服务器 | 密钥、要调第三方服务的后台、需要保密的计算 |
16. 在浏览器里运行的隐患#
- 钓鱼网页:谁都能存一个冒充别人的页面。教用户认准你的容器名字(tape:// 地址),不要只看页面长得像不像。
- 网站之间要互相隔离:否则一个网站的脚本可能读到另一个网站存在浏览器里的东西。这由 TapeKit 外壳和网关负责;本协议的网关送出文件时带 TapeKit 给链上网站的那组安全设置(内容安全策略、nosniff 等,§13.2)。参考前端对收到的文件只给下载,不在页面里打开。
- 网页更新的权限:按哈希存的网页别人改不了。用「签名指针」(规范 §6.5,第 312 条起有)让一个名字换成新的版本时,谁能签指针谁就能推送更新:
- 网站的指针只用钱包签,不要交给会话密钥(参考前端「发布到名字」勾「用钱包签」)。会话密钥最长 24 小时、可以撤销,但读的人查不到撤销:会话密钥被偷,小偷签的指针在那份授权到期之前都算数;撤销之后等授权到期(最长 24 小时),再用钱包发布一次就改回来了(D-283:版号不得大于签名时间,谁都签不出能长期压住你的版号)。
- 要「谁读到的都一定是最新的」,用链上的那种放法(规范 §6.2 第 1 种):指针存在运营方的服务器上,读的人问遍你的每台服务器、取最新的,但一台服务器能少给(只给旧的);你的服务器都被拔掉时,读的人就读不到。
- 容器转手之后,原来的持有人签的指针一律作废;新持有人发布时版号取当时的秒数,最多等 5 分钟。
- 名字的位子删不掉:每个容器在每台服务器上最多 100 个名字,规范里还没有删名字的办法(I-232 第 3 点、I-234 第 2 点,以后再定)。会话密钥被偷(授权到期之前,最长 24 小时)、或者容器转手之前的原持有人,都能把 100 个位子写满;之后你只能改指这 100 个名字,起不了新名字,撤销、转手、等到期都腾不出位子。所以要紧的名字(例如
www)先用钱包发布一次占住,会话密钥只交给你信得过的设备。
- 流量:读取目前不计费,热门网页可能把免费运营方的流量拖垮,运营方会限流。热门内容请选能承受流量的运营方,或者多存几家。
- 直接网页托管(规范 §6.6,第 315 条起有;运营方自愿开):把一个文件夹作为网站上传后,开了托管的运营方给出能在普通浏览器里打开的网址。要知道:
- 普通浏览器不核对:看到的就是运营方给的,运营方能改、能换掉整个网站。只放无关紧要的东西;要核对的,让人用参考前端(或以后的 TapeKit 外壳)按名字打开,逐个文件核对哈希。
- 网址里的名字:按名字的网址是「容器地址--名字」,总是这个名字最新的一版;告诉访客认准里面的容器地址,谁都能放一个长得像的网站。
- 浏览量公开(规范 §10.7,D-359):开了托管的运营方默认公布按名字的网站每天的浏览量和流量(同一个访客每天最多算 10 次),官方的网站排行(
https://taperelay.hivehubs.app/sites)把各家的数字加在一起排,列出容器地址和名字;按哈希的网址不统计。数字是运营方自报的。 - 不要用 cookie 存要紧的东西:同一个托管域名下的网站互不同源,但托管域名还没加入公共后缀列表时,别的网站的脚本能写 cookie 给你的网站(D-288)。登录凭证之类的放在你自己的后台。
- 按名字的网站,同一个网址的内容会换主人(I-237 第 5 点):「容器地址--名字」这个网址给的是这个名字最新的指针指的网站。容器转手、会话密钥被偷时,新的内容就在同一个网址下,能读到访客浏览器里这个网址下存的东西(localStorage、IndexedDB、cookie)。按名字的网站不要在浏览器里存要紧的东西;按哈希的网址内容固定,没有这个问题。
- 路径照 TapeKit:访问目录自动找
index.html;单页应用把「退回的页面」设成index.html(fallbackPath:没有扩展名的路径找不到时退回它);notFound是都找不到时给的 404 页。一个网站最多 2,000 个文件(D-281)。
17. 让文件一直在#
- 存 3 家不同运营方(第 2 节,D-336):一家关了另外的还在;「核对副本」能补上缺的。
- 运营方要退出时,要提前 90 天在链上声明(D-244,合约原型已写、还没部署);看到提醒就把文件搬到别家。
- 发现文件里的服务器名单是你在链上写的(§3):换运营方就改它。
18. 上线前的检查清单#
- 公开文件里没有密钥、密码、私钥、助记词、带密钥的网址(包括
.env、配置文件、源码映射.map)。 - 要保密的数据用了加密文件,并确认不需要在访客浏览器里解密。
- 第三方服务的密钥放在自己的后台,网页里只有可以公开的 Key。
- 部署用会话密钥,钱包私钥不在任何脚本、仓库、CI 里。
- 文件存了 3 家不同运营方,慢的那台有人补(模版 A 的后台定时补,模版 B 提醒用户「核对并补齐」)。
- 会话密钥的授权只要用得到的范围;模版 A 的专用钱包只当这一个容器的持有人。
- 给用户看按文件读取的网址时写明浏览器不核对(
FILE_URL_NOTE)。 - 想清楚运营方能看到什么(第 14 节),访问记录对你的用户是否敏感。