最小可用的 VeriRandom 重放存证服务。服务端接收客户端导出的 DrawProof,用与客户端相同的 C# 验证内核重放,检查 proof 自洽性,然后用 ECDSA P-256 签发回执,并为每个客户端维护一个只追加的链锚。
已实现:
POST /v1/proofs/attest:普通OfflineReproducible证明的重放、校验、签名、链锚GET /v1/keys:keyId和 SPKI 公钥(用于填进客户端)GET /healthz:数据库和密钥健康检查- SQLite 幂等存储:
proofId重复提交返回原回执 - 链状态:
genesis/continuous/duplicate/behind/gap/broken/conflict/unchained - xUnit 测试:内核重放、结果一致性、回执签名与绑定、链锚
暂不包含:
- 正式公证
POST /v1/notarizations - RFC 3161 TSA(客户端自己申请,服务端不需要)
- 透明度日志 / Merkle root 发布
- 限流、配额、管理后台
- Windows 证书库 / CNG 非导出密钥(当前是 PFX)
- 开发机:.NET 10 SDK
- 部署:Windows Server + IIS + .NET 10 Hosting Bundle(Hosting Bundle 已包含 .NET Runtime、ASP.NET Core Runtime 和 ANCM,不需要再单独装运行时)
- 环境变量
WITNESS_KEY_PASSWORD,至少 12 个字符
$env:WITNESS_KEY_PASSWORD = "change-me-please-12345"
dotnet run --project src/VeriRandom.Witness开发环境 appsettings.Development.json 打开了 AutoGenerateKey,首次启动会在 data/keys/witness.pfx 生成 P-256 密钥。生产默认 AutoGenerateKey=false,请显式提供 PFX。
获取公钥:
Invoke-RestMethod http://localhost:5080/v1/keys把返回的 publicKey 原样填入客户端 WitnessClient.WitnessPublicKey 常量,把 WitnessServiceUrl 指向服务根地址(例如 https://witness.example.com/)。
dotnet build VeriRandom.Witness.sln -c Release
dotnet test VeriRandom.Witness.sln -c Release.github/workflows/build.yml 会:
- checkout 本仓库
- 按
VERIRANDOM_KERNEL_SHA把主仓库 checkout 到.deps/verirandom - 只 sparse-checkout 三个 Verification 目录
- build / test / publish
如果主仓库是私有的,在 workflow 的 checkout 步骤加 token: ${{ secrets.VERIRANDOM_READ_TOKEN }}。NuGet.config 已清空本机 Visual Studio 的 fallback 目录,CI 和本地都只使用 nuget.org。
Visual Studio 版本要求:项目目标是
net10.0,只能用 Visual Studio 2026(18.0+) 打开和构建。Visual Studio 2022 17.14 最高只支持net9.0,会报“找不到指定的 SDK / Microsoft.NET.Sdk.Web”。如果暂时不想升级 VS,可以用dotnetCLI 或 VS Code;不要把项目降级到 net9.0,它已经结束支持。
仓库已经在本地初始化并完成首个提交,但还没有 remote:
git remote add origin https://github.com/<你的账号>/VeriRandom-Witness.git
git push -u origin main推送后 workflow 会自动跑。建议在仓库的 Settings → Secrets and variables → Actions → Variables 里设置 VERIRANDOM_KERNEL_SHA,以后升级内核只改变量,不改 workflow;未设置时使用 workflow 里的固定 SHA 兜底。
服务端不引用整个 SecRandom.Core(会带入 Avalonia 等 UI 依赖),只链接验证相关源文件。
- 固定提交记录在
kernel.lock.json - 本地开发:
.\scripts\fetch-kernel.ps1,或直接用同级的..\SecRandom - CI:签出
WinFunan/VeriRandom到.deps/verirandom,只拉三个 Verification 目录 - 升级:修改
kernel.lock.json和 CI 的VERIRANDOM_KERNEL_SHA,跑完整测试
文件清单在 src/VeriRandom.Witness.Kernel/VeriRandom.Witness.Kernel.csproj,保持显式列表,不要改成 glob。
请求头:
X-SecRandom-Client-Id: <guid>
Content-Type: application/json
请求体:客户端原样发送的 DrawProof JSON。
成功响应:
{
"token": "<base64url-payload>.<base64url-der-signature>",
"keyId": "p256-...",
"chain": {
"status": "continuous",
"headIndex": 2,
"headHash": "...",
"breaks": 0
}
}失败响应:
{ "error": "proof_inconsistent", "detail": "..." }{
"keyId": "p256-...",
"algorithm": "ECDSA-P256-SHA256",
"publicKey": "<SPKI base64>",
"publicKeyFormat": "spki-base64",
"tokenFormat": "base64url(json-payload).base64url(der-signature)",
"attestationUrl": "/v1/proofs/attest"
}数据库可读且密钥已加载时返回 200;否则返回 503。
dotnet publish src/VeriRandom.Witness/VeriRandom.Witness.csproj `
-c Release -r win-x64 --self-contained false -o artifacts/publish部署要点:
- 新建独立站点和独立应用池,不要挂在现有 Web 应用下。
- 应用池:No Managed Code、1 个 worker、关闭 Web Garden、关闭 Overlapped Recycle。
- 给应用池身份:
- 发布目录:读/执行
data目录:修改- PFX 文件:读
web.config已包含hostingModel="inprocess"和maxAllowedContentLength=8MB。- 通过 IIS 应用池环境变量或机器级环境变量设置
WITNESS_KEY_PASSWORD。不要把密码写进appsettings.json。 - 必须只开 HTTPS。如果前面有 Cloudflare,用 Full (strict),并限制源站只接受 Cloudflare IP 或走 Cloudflare Tunnel。
- 备份
data/keys/witness.pfx和data/witness.db。PFX 丢了就没法继续用同一把钥匙签名。
服务端签名的 canonical hash 是 AttestedProofHash.Compute,它逐字段复制了客户端 WitnessClient.ComputeAttestedProofHash:
- 字段顺序不能变
null写成-1- 字符串长度、
int32、int64都用小端 - 时间用
CreatedAtUtc.UtcDateTime.Ticks chain、beacon、witness.receipt、witness.timestampToken不参与该 hash
改动这里必须同时改客户端并跑跨端测试。
- 当前服务是单机 SQLite;不要开 Web Garden,也不要在多台机器上同时写同一个数据库。
- 服务是“你自己运营的见证”,不是独立第三方。它能证明“服务端在某时刻签收了这份 proof”,不能替代独立运营者或本地重放。
- 生产环境建议把 PFX 换成 Windows 证书库/CNG 非导出密钥,并制定轮换与备份方案。
- 公网部署前至少加限流(ASP.NET Core RateLimiter 或 Cloudflare WAF),避免签名接口被滥用。
dotnet test VeriRandom.Witness.sln -c ReleaseGPL-3.0-only,因为链接了 VeriRandom / SecRandom 的验证源码。见 LICENSE 和 THIRD_PARTY_NOTICES.md。