GitHub

WebAuthn P256 Public Key Index Service

WebAuthn P256 公钥索引服务。数据存储在 Gnosis 链上的智能合约 (V2) 中,本服务作为读写代理,提供 REST API。

合约地址: 0xdd93420BD49baaBdFF4A363DdD300622Ae87E9c3 (Gnosis Chain)

公共端点: https://webauthnp256-publickey-index.biubiu.tools

API 参考

Base URL: https://webauthnp256-publickey-index.biubiu.tools (或自部署地址)


GET /api/challenge

获取一个随机 challenge (向后兼容)。

响应 (200):

{
  "challenge": "a1b2c3d4..."
}

POST /api/create

创建一条公钥记录。请求立即返回 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"
}

错误响应:


GET /api/create/:id

查询异步创建任务的状态。

响应 (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 次。


GET /api/query

查询公钥记录。支持两种查询方式:

方式一: 按 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 字段。

错误响应:


GET /api/stats/total

查询全网凭证总数。

响应 (200):

{
  "totalCredentials": 1234
}

GET /api/stats/sites

分页查询所有站点。

Query 参数:

参数 类型 必填 默认值 说明
page number 1 页码
pageSize number 20 每页数量 (最大 100)
order string desc 排序方向: ascdesc

响应 (200):

{
  "total": 42,
  "page": 1,
  "pageSize": 20,
  "items": [
    { "rpId": "example.com", "publicKeyCount": 5, "createdAt": 1711000000000 }
  ]
}

GET /api/stats/keys

分页查询某站点下的所有公钥。

Query 参数:

参数 类型 必填 默认值 说明
rpId string - 站点域名
page number 1 页码
pageSize number 20 每页数量 (最大 100)
order string desc 排序方向: ascdesc

响应 (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 缺失


GET /api/health

健康检查。

响应 (200): { "status": "ok" }


GET /

返回本文档的 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...          → 同上

隐私与合规

缓存策略

双层缓存:

规则:

项目结构

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=