API 文档
面向用户的公共 API
下载接入方式
你可以按实际场景选择下载接入方式。如果你是在网页、官网、论坛、公告页或前端页面里放下载按钮,推荐跳转主站验证页;如果你是在脚本、客户端、CI、自动更新器或后端程序里自动下载文件,使用程序 API 链路。
方式一:跳转主站验证页下载
这种方式适合网页下载按钮。你不需要自己处理挑战、PoW、下载令牌,也不需要关心当前由哪个下载节点提供文件。
需要特别注意:这里要跳转的是主站地址,不是下载节点地址。外部前端应该把用户带到主站的验证页面,由主站完成验证、授权和节点选择,最后再跳转到真实下载节点开始下载。
/{project_id}/{version}/{file_name}验证页地址示例
https://mirror.example.com/fcl/1.3.0.9/FCL-release-1.3.0.9-arm64-v8a.apk
这里的 https://mirror.example.com 应该是主站公共入口,不是某个节点的 public_download_base_url。
如果不知道项目是哪一个,可以先查询项目列表
这个接口可以用来获取 project_id、项目展示名和项目可用状态。前端一般只需要在初始化下载页、项目选择器或外部下载列表时调用它。
/api/public/v1/projectsExample Request
GET /api/public/v1/projects
/api/public/v1/projects/{project_id}/assets拿到 project_id 后,前端可以查询该项目下有哪些版本和文件。返回结果里会包含 version、file_name、architecture、system、size_bytes、digest_sha256 和 available 等字段。
Example Request
GET /api/public/v1/projects/example/assets
Example Response
{
"status": "success",
"data": {
"assets": [
{
"asset_id": "asset_123",
"version": "v1.2.3",
"file_name": "example-windows-amd64.zip",
"architecture": "amd64",
"system": "win",
"available": true
}
]
}
}
前端可以根据这些字段生成下载按钮。用户点击按钮时,跳转到主站验证页:
https://mirror.example.com/example/v1.2.3/example-windows-amd64.zip
- 展示下载验证页面。
- 在浏览器中完成下载挑战计算。
- 向主节点领取短时下载授权。
- 选择可用下载节点。
- 跳转到真实下载地址开始下载。
外部网站不要直接拼接节点下载地址,也不要调用程序下载用的 /api/public/v2/api/* 接口来替代这个流程。
方式二:程序调用 API 下载
这种方式适合命令行工具、自动更新器、CI 脚本、下载器或后端服务。程序需要自己查询资产、完成 API PoW 验证,然后携带下载令牌访问下载节点。
第一步:查询项目列表
/api/public/v1/projects查询已启用且至少存在可展示 Release 的项目列表,拿到后续要使用的 project_id。
| 参数 | 类型 | 描述 |
|---|---|---|
| 无 | ||
Example Request
GET /api/public/v1/projects
Example Response
{
"status": "success",
"message": "查询成功",
"request_id": "req_...",
"data": {
"projects": [
{"project_id": "example", "display_name": "示例项目", "available": true}
]
}
}
第二步:查询项目资产
/api/public/v1/projects/{project_id}/assets程序应选择一个 available=true 的资产,并记录它的 asset_id。如果 available=false,表示当前没有可用下载节点持有这个文件,程序应该稍后重试。
| 参数 | 类型 | 描述 |
|---|---|---|
| project_id | Path | 项目标识 |
Example Request
GET /api/public/v1/projects/example/assets
Example Response
{
"status": "success",
"data": {
"assets": [
{"asset_id": "asset_123", "file_name": "example.zip", "architecture": "amd64", "system": "win", "available": true}
]
}
}
第三步:创建 API V2 顺序工作量挑战
API V1 计算与验证方式弃用提醒:旧版第三步接口 /api/public/v1/api/challenges 和旧版第五步接口 /api/public/v1/api/authorizations 使用 SHA-256 前导零 nonce 搜索,将在后续版本弃用。API V2 已改为 RSA repeated-squaring 顺序模平方并提交 384 字节定长 solution;请求和响应字段也随之改变,不能只替换接口路径。现有 V1 兼容暂时继续可用,但不代表长期可用,后续将会择机停用与删除。新客户端和新集成应直接实现下面的 V2 计算与验证方式。
/api/public/v2/api/challenges为指定资产创建 3072 位 RSA repeated-squaring 挑战。响应中的 modulus 和 base 是 384 字节无符号大端整数的无填充 base64url 编码。
| 参数 | 类型 | 描述 |
|---|---|---|
| asset_id | JSON | 要下载的资产标识 |
Example Request
POST /api/public/v2/api/challenges
{"asset_id":"asset_123"}
Example Response
{
"status": "success",
"data": {
"challenge_id": "challenge_123",
"algorithm": "rsa-repeated-squaring-v1",
"modulus_id": "模数标识",
"modulus": "512 字符 base64url 整数",
"base": "512 字符 base64url 整数",
"iterations": 96000,
"encoding": "base64url-uint-be-384"
}
}
第四步:顺序计算 solution
从 y = base 开始,严格执行 iterations 次 y = y² mod modulus,再把 y 编码为 384 字节定长大端 base64url。不得提交十进制、十六进制或可变长整数。
展开了解 RSA repeated-squaring 的计算原理
这一步计算的是顺序工作量证明,不是普通密码哈希。挑战给出 RSA 模数 N、起始值 base 和最终迭代数 iterations;客户端只能按顺序使用前一次结果继续模平方。
计算过程
y = decode_unsigned_big_endian(base)
重复 iterations 次:
y = (y × y) mod N
solution = base64url_no_padding(unsigned_big_endian_384(y))
modulus、base 和 solution 都必须是恰好 384 字节的无符号大端整数,再编码成无填充 base64url,因此线上字符串长度固定为 512。即使结果前面是零,也不能删掉前导零字节。
为什么必须顺序执行
第 i+1 次平方依赖第 i 次的完整结果。数学上最终值等于 base^(2^iterations) mod N,但客户端不知道 RSA 模数的陷门,不能把指数按欧拉函数化简;直接构造 2^iterations 也不能绕过这些依赖。多线程拆分不同区间后无法独立合并,所以应使用单条顺序循环。
服务端如何确认结果
主节点持有只存在于内存中的 RSA 陷门,可以快速得到同一最终值,并在创建挑战时保存定长结果的摘要。授权时服务端先检查挑战版本、算法、来源、资产、客户端前缀、有效期和一次性状态,再校验 0 < solution < N 以及结果摘要。RSA 陷门和预期答案不会发送给客户端或写入 PoW 遥测。
实现时最容易出错的地方
- 循环次数必须正好等于响应里的最终 iterations,不能使用本地默认值。
- 每轮都必须先平方再对 N 取模,不能改成哈希、乘以 base 或并行 nonce 搜索。
- 解码和编码都使用无符号大端;输出必须左侧补零到 384 字节。
- base64url 使用 -、_ 且不带 = padding。
- 挑战有有效期并绑定资产和客户端来源;失败后应重新创建挑战,不要跨资产或跨 API 版本复用。
# Mirror Server API V2 repeated-squaring 原理
## 用途
程序下载在调用 `POST /api/public/v2/api/challenges` 后,需要根据响应计算 `solution`,再提交到 `POST /api/public/v2/api/authorizations`。这是顺序工作量证明,不是 SHA 哈希 nonce 搜索。
## 挑战输入
- `algorithm` 必须是 `rsa-repeated-squaring-v1`。
- `encoding` 必须是 `base64url-uint-be-384`。
- `modulus`:RSA 模数 N,384 字节无符号大端整数的无填充 base64url,字符串长度 512。
- `base`:起始值,编码规则与 modulus 相同。
- `iterations`:服务端给出的最终迭代数,已经包含文件大小分档和风控倍率。
- `challenge_id` 和 `asset_id` 必须原样用于后续授权请求。
## 顺序算法
```text
N = decode_base64url_unsigned_big_endian_384(modulus)
y = decode_base64url_unsigned_big_endian_384(base)
repeat exactly iterations times:
y = (y * y) mod N
solution_bytes = unsigned_big_endian(y, exactly 384 bytes, left padded with zeroes)
solution = base64url_without_padding(solution_bytes)
```
最终值在数学上是 `base^(2^iterations) mod N`。但客户端不知道 RSA 模数的陷门,无法化简指数;每轮又依赖前一轮结果,因此不能把迭代区间拆给多个线程后再合并。正确实现是一条顺序模平方循环。
## 编码边界
1. modulus、base、solution 在线上都固定为 384 字节、512 个 base64url 字符。
2. 整数按无符号大端解释,不能使用十进制、十六进制或可变长字节串。
3. solution 前导零必须保留到 384 字节。
4. base64url 使用 `-` 和 `_`,不得包含 `=` padding。
5. solution 必须满足 `0 < solution < N`。
## 授权请求
```json
{
"challenge_id": "挑战响应中的 challenge_id",
"asset_id": "创建挑战时使用的 asset_id",
"solution": "512 字符定长 base64url 结果"
}
```
挑战会绑定 API V2、算法、资产、客户端网络前缀和有效期,并且成功授权后只能消费一次。不要跨资产、跨 Web/API 或跨 V1/V2 复用挑战;挑战过期或失败后应重新创建。
## 服务端校验原理
主节点持有仅存在于内存中的 RSA 陷门,可以快速计算相同最终值。创建挑战时保存的是定长预期结果的摘要;授权时先检查挑战绑定关系和生命周期,再检查 solution 的定长编码、数值范围和摘要。RSA 陷门、预期答案和 solution 都不会写入独立 PoW 遥测日志。
第五步:提交 solution 并领取下载授权
/api/public/v2/api/authorizations提交顺序工作量结果并领取短时、单节点绑定的下载授权。telemetry 可选且只用于统计,不影响授权。API V1 在配置开启时仍保持原 SHA-256 合同。
| 参数 | 类型 | 描述 |
|---|---|---|
| challenge_id | JSON | 挑战标识 |
| asset_id | JSON | 资产标识 |
| nonce | JSON | 满足前导零要求的 nonce |
Example Request
POST /api/public/v2/api/authorizations
{"challenge_id":"challenge_123","asset_id":"asset_123","solution":"512 字符 base64url 整数"}
Example Response
{
"status": "success",
"data": {
"authorization_id": "auth_123",
"download_url": "https://node.example/example/v1.2.3/example-windows-amd64.zip",
"download_token": "43 字符短时随机令牌",
"expires_at": "2026-05-28T12:05:00Z"
}
}
第六步:请求下载节点
程序应直接访问授权响应里的 download_url,并通过请求头携带下载令牌。这里的 download_url 通常指向下载节点;程序调用 API 的这条链路里,下载文件时访问节点地址是正确的。
{download_url}Example Request
GET {download_url}
Authorization: Bearer <download_token>
如果需要断点续传,可以使用单段 Range:
GET {download_url}
Authorization: Bearer <download_token>
Range: bytes=1048576-2097151
其他接口
下面这些接口不是程序下载流程的步骤,只用于订阅、状态查询或排障。
封禁列表订阅
/api/public/v1/blocklist.txt/api/public/v1/blocklist.json返回当前生效的公共下载封禁列表。内容包含 quota.yaml 静态黑名单和本站手动/自动封禁记录,不包含远程订阅源快照,响应在服务端缓存 60 秒。
TXT Example Response
# [枫源镜像封禁] 封禁原因: traffic_limit_exceeded, 来源: local_auto_ban, 封禁后尝试次数: 3
2.59.169.232
JSON Example Response
{
"status": "success",
"data": {
"blocks": [
{"entry": "2.59.169.232", "reason": "traffic_limit_exceeded", "attempts_after_block": 3, "blocked_at": "2026-06-21T12:00:00Z"}
]
}
}
更新日志
/api/public/v1/changelog按时间倒序查询本站更新记录。minimum_level 可选 info、notice、warn 或 critical;q 搜索标题和可见描述;limit 默认 20、最大 50。存在下一批时响应返回不透明的 next_cursor。
Example Request
GET /api/public/v1/changelog?minimum_level=notice&q=下载&limit=20
/api/public/v1/authorizations/{authorization_id}携带对应下载令牌查询授权状态、过期时间、公开节点名和已入账真实发送字节。
| 参数 | 类型 | 描述 |
|---|---|---|
| authorization_id | Path | 授权标识 |
| Authorization | Header | Bearer <download_token> |
Example Request
GET /api/public/v1/authorizations/auth_123
Authorization: Bearer <download_token>
Example Response
{
"status": "success",
"data": {
"authorization_id": "auth_123",
"state": "issued",
"bytes_accounting_enabled": true,
"sent_bytes": 1048576
}
}