身份
自证 DID:怎么推导、为什么有两种编码、以及那个不能重排的验证顺序。
LibertyNet 的身份是一个 DID——由公钥推导而来,不是任何人分配的。
did:svrp:n:268d4fe0
│ │ │ └── 标识符:SHA-256(公钥) 截断到 4 字节,十六进制
│ │ └──── 角色标签:n=节点,o=运营者,h=主机
│ └───────── method:svrp
└───────────── scheme没人签发它。它是算出来的。所以你不问任何人就能核对。
两种编码,同一个身份
这是最容易让人白花一下午的细节。
同一个密钥,在不同地方会以两种拼写出现:
| 形式 | 样子 | 推导 | 出现在哪 |
|---|---|---|---|
| 短 | did:svrp:n:268d4fe0 | SHA-256(密钥)[0:4] | 绑定、运营者记录、/peers |
| 全 hex | did:svrp:df9d4b9f…02d | 32 字节密钥本身 | /nodes、运行中的守护进程 |
还有一个 10 位 hex 回退形式(SHA-256(密钥)[0:5]),保留给碰撞处理。 任何只硬编码 4 字节公式的实现,在第一个 10 位 hex 身份签发的那天就会拒绝一个合法身份—— 而且报的错看起来像是伪造。
在这里做字符串比较会骗你。
"did:svrp:n:8545027b" === "did:svrp:df9d4b9f...02d" // false这是同一个节点。比较 DID 字符串会悄悄把一个节点拆成两个——在你的数据库里、 仪表盘里、计费里。而且这个 bug 在有人注意到重复之前是看不见的。
请比较底层身份:
sameIdentity(shortDid, fullDid, publicKey) // → true公钥也有两种编码
GET /nodes 返回 hex,GET /peers 返回 base58。是同样的 32 字节。
把 base58 的密钥当 hex 解析不会报错——它会产出垃圾,导致每一次 id-binding 检查都失败, 看起来就像整个网络都是伪造的。如果你的验证突然全部拒绝,几乎总是这个原因。
SDK 两种编码都接受,并在哈希前归一化。
验证顺序
这个顺序是固定的。它不是代码风格偏好,不允许重排或短路。
- 验 id-binding —— 声称的身份是否由这个公钥推导而来?不是就立刻拒绝,不要进入第 2 步。
- 验签名 —— Ed25519,对规范字节。
- 验其余全部规则 —— 有效期、DID 状态、防重放、权限范围。
- 接受 —— 以上全部通过之后才接受。
省略第 1 步是验证绕过,不是优化。 为什么
身份 ≠ 认证
值得说清楚:
- id-binding 证明这个标识符对这个密钥是良构的。这是一个关于算术的陈述。
认证证明调用方持有私钥。它要求对方签一个你选定的东西——一个新鲜的挑战—— 这样重放的签名就没用了。
一个出示了 DID 和匹配公钥的调用方,并没有证明任何关于持有的事:这两样他从公开目录里 抄就行了。完整的挑战—应答见运营者登录。
根密钥与设备密钥
运营者有一个根密钥和一个或多个设备密钥。
根密钥只签一样东西:一份 DeviceCredential,指明某个设备密钥及其权限。然后它就回到 保险箱里,离线保存。之后的每一次操作——登录、授权绑定——都由设备密钥完成。
根密钥 ──签署──▶ DeviceCredential ──其密钥签署──▶ AuthorizationCredential
(离线、冷存) (该设备可以行动) (这个节点、这些限额)要点:笔记本被偷,代价是一台设备——你吊销它。代价不是你的身份,那是不可恢复的。
快速开始里有十行不依赖任何包的代码,对着真实网络验证全部身份。