WebAuthn P256 公钥索引服务。数据存储在 Gnosis 链上的智能合约 (V2) 中,本服务作为读写代理,提供 REST API。
合约地址: 0xdd93420BD49baaBdFF4A363DdD300622Ae87E9c3 (Gnosis Chain)
公共端点: https://webauthnp256-publickey-index.biubiu.tools
Base URL: https://webauthnp256-publickey-index.biubiu.tools (或自部署地址)
获取一个随机 challenge (向后兼容)。
响应 (200):
{
"challenge": "a1b2c3d4..."
}
创建一条公钥记录。请求立即返回 202, 后台异步执行链上 commit-reveal 流程。
请求体 (JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| rpId | string | 是 | 站点域名 (如 example.com) |
| credentialId | string | 是 | 凭证 ID |
| publicKey | string | 是 | P256 公钥 (hex 格式, 含 04 前缀的非压缩格式, 65 字节) |
| name | string | 是 | passkey 的显示名称 |
| walletRef | string | 否 | 钱包标识 (bytes32 hex), 默认从 publicKey 计算确定性 Safe 地址 |
| initialCredentialId | string | 否 | 初始凭证 ID, 默认等于 credentialId (密钥轮换时指向根凭证) |
| metadata | string | 否 | 附加元数据 (hex), 默认 abi.encode("VelaWalletV1", publicKey) |
请求示例:
{
"rpId": "example.com",
"credentialId": "abc123",
"publicKey": "04a1b2c3...",
"name": "我的 MacBook"
}
响应 (202 - 已入队):
{
"id": "uuid",
"status": "pending"
}
响应 (201 - 已存在, 幂等):
{
"rpId": "example.com",
"credentialId": "abc123",
"walletRef": "0x000...abc",
"publicKey": "04a1b2c3...",
"name": "我的 MacBook",
"status": "done"
}
错误响应:
400 - 参数缺失429 - 超过限流 (每 IP 每分钟 5 次)查询异步创建任务的状态。
响应 (200):
{
"id": "uuid",
"status": "pending | committing | committed | creating | done | failed",
"rpId": "example.com",
"credentialId": "abc123",
"walletRef": "0x000...abc",
"publicKey": "04a1b2c3...",
"name": "我的 MacBook",
"txHash": "0x...",
"createdAt": 1711000000000
}
status 状态机: pending → committing → committed → creating → done。失败时自动重试最多 3 次。
查询公钥记录。支持两种查询方式:
方式一: 按 rpId + credentialId
GET /api/query?rpId=example.com&credentialId=abc123
方式二: 按 walletRef
GET /api/query?walletRef=0x000...abc
成功响应 (200):
{
"rpId": "example.com",
"credentialId": "abc123",
"walletRef": "0x000...abc",
"publicKey": "04a1b2c3...",
"name": "我的 MacBook",
"initialCredentialId": "abc123",
"metadata": "0000...00",
"createdAt": 1711000000000
}
如果链上未找到但队列中有 (正在上链), 会返回队列中的数据并附带 _queue 字段。
错误响应:
400 - 参数缺失 (需要 rpId+credentialId 或 walletRef)404 - 未找到查询全网凭证总数。
响应 (200):
{
"totalCredentials": 1234
}
分页查询所有站点。
Query 参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| page | number | 否 | 1 | 页码 |
| pageSize | number | 否 | 20 | 每页数量 (最大 100) |
| order | string | 否 | desc | 排序方向: asc 或 desc |
响应 (200):
{
"total": 42,
"page": 1,
"pageSize": 20,
"items": [
{ "rpId": "example.com", "publicKeyCount": 5, "createdAt": 1711000000000 }
]
}
分页查询某站点下的所有公钥。
Query 参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| rpId | string | 是 | - | 站点域名 |
| page | number | 否 | 1 | 页码 |
| pageSize | number | 否 | 20 | 每页数量 (最大 100) |
| order | string | 否 | desc | 排序方向: asc 或 desc |
响应 (200):
{
"total": 5,
"page": 1,
"pageSize": 20,
"items": [
{
"rpId": "example.com",
"credentialId": "abc123",
"walletRef": "0x000...abc",
"publicKey": "04a1b2c3...",
"name": "我的 MacBook",
"initialCredentialId": "abc123",
"metadata": "0000...00",
"createdAt": 1711000000000
}
]
}
错误响应: 400 - rpId 缺失
健康检查。
响应 (200): { "status": "ok" }
返回本文档的 HTML 渲染版本 (GitHub 风格)。
1. POST /api/create → 202 { id, status: "pending" }
Body: { rpId, credentialId, publicKey, name }
2. GET /api/create/:id → { status: "done", txHash: "0x..." }
3. GET /api/query?rpId=...&credentialId=... → { publicKey, walletRef, ... }
或 GET /api/query?walletRef=0x... → 同上
双层缓存:
Cache-Control: public, max-age=3600 (1 小时)规则:
index.ts 主入口, Deno.serve() 路由
build.ts 构建脚本 (readme→HTML + 编译二进制)
src/
config.ts 配置 (从环境变量读取)
contract.ts 链上合约交互 (viem, Gnosis Chain)
queue.ts 异步队列 (SQLite + 后台 worker + 重试 + 限流)
rpc.ts RPC 自动故障转移 (从 ethereum-data 获取节点列表)
wallet-ref.ts walletRef 计算 (P256 公钥 → Safe 地址 → bytes32)
cache.ts 内存缓存 (TTL + 内存上限 + 淘汰)
routes/
challenge.ts GET /api/challenge
query.ts GET /api/query (支持 rpId+credentialId 和 walletRef)
create.ts POST /api/create + GET /api/create/:id
stats.ts GET /api/stats/total + /sites + /keys
scripts/
deploy.ts 部署脚本 (SSH + systemd + Cloudflare Tunnel)
deploy/ 部署工具模块
deploy/
systemd/ systemd 服务配置
.github/workflows/ci.yml CI (type-check + test)
deno task dev # 热重载开发 (自动加载 .env)
deno task test # 运行测试
deno task build # 编译为二进制
通过 deno task deploy 手动部署到服务器, 交互式选择目标。
deno task deploy # 部署: 选目标 → 配置 → 上传 → Tunnel → 健康检查
deno task deploy status # 查看远程服务状态
deno task deploy rollback # 回滚到上一版本
首次部署自动: 安装 Deno → 创建用户/目录 → 提示配置 .env → 安装 systemd 服务 → 设置 Cloudflare Tunnel。
/opt/webauthnp256-publickey-index/
releases/ 各版本目录 (YYYYMMDD-HHMMSS)
current/ -> 当前版本的符号链接
data/
.env 环境变量
queue.db 异步队列数据库
# 服务端口 (可选, 默认 11256)
PORT=11256
# 队列数据库路径 (可选)
QUEUE_DB_PATH=/opt/webauthnp256-publickey-index/data/queue.db
# 服务器钱包私钥 (创建公钥记录时需要, 用于签名链上交易)
PRIVATE_KEY=0x...
# Telegram 告警通知 (可选, 队列积压/失败/Gas 余额/Gas 价格)
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=