附录 C:密码学与编码
本附录固定 OpenAgenet (OAN) 在身份、凭证、资源发布和跨节点校验中使用的密码学与编码边界。当前共享 Rust 实现以 oan-crypto、oan-credentials、oan-did-oan 为主要依据,浏览器侧 TypeScript SDK 使用 Web Cryptography API 生成 Ed25519 本地身份;任何跨语言实现都应以相同规范化输入复算相同摘要,并对算法、编码和错误状态显式处理。
C.1 支持的算法
当前 CryptoSuite 包含 Ed25519Sha256Legacy、Ed25519Sha256 和 Sm2Sm3:前两者使用 Ed25519 签名与 SHA-256 哈希,后者使用 SM2 签名与 SM3 哈希。对应验证方法类型为 Ed25519VerificationKey2020 或 SM2VerificationKey2020,证明类型为 Ed25519Signature2020 或 SM2Signature2020。算法套件必须与公钥、proof 和哈希算法一致;未知套件拒绝,旧套件只能按兼容策略保留,不能悄然降级。
| 算法套件 | 签名算法 | 哈希算法 | 典型用途 |
|---|---|---|---|
Ed25519Sha256Legacy |
Ed25519 | SHA-256 | 兼容既有凭证或摘要流程 |
Ed25519Sha256 |
Ed25519 | SHA-256 | 当前 Ed25519 证明流程 |
Sm2Sm3 |
SM2 | SM3 | 支持 SM2 的身份和证明流程 |
算法套件、验证方法、公钥编码和 proof 类型必须成组匹配;未知或被弃用的套件不能静默切换到另一套算法。
在实现选择上,Ed25519Sha256Legacy 与 Ed25519Sha256 使用同一种 Ed25519 密钥和 SHA-256 哈希,但签名输入不同:前者先对规范化 JSON 求 SHA-256,再对得到的 64 个小写十六进制字符的 UTF-8 字节签名;后者直接对规范化 JSON 的 UTF-8 字节签名。Sm2Sm3 直接对规范化 JSON 的 UTF-8 字节使用 SM2 签名,摘要函数使用 SM3。跨语言实现不能只根据签名算法名称判断输入,必须同时读取 cryptoSuite 和 hashAlgorithm。
| 选择项 | 当前实现中的判断 | 失败处理 |
|---|---|---|
cryptoSuite |
决定签名算法、哈希算法和签名输入形式 | 未识别时拒绝 |
proof.type |
与套件对应的 Ed25519Signature2020 或 SM2Signature2020 |
类型不匹配时拒绝 |
| 验证方法类型 | 从 Ed25519VerificationKey2020 或 SM2VerificationKey2020 推导套件 |
无法推导时拒绝 |
| 公钥载体 | 二选一使用 publicKeyMultibase 或 publicKeyJwk |
两者均缺失或无法解析时拒绝 |
C.2 规范化 JSON 与哈希
共享实现先将可序列化对象转换为 JSON,再按对象键的字典序递归排列,数组保持原顺序,字符串使用 JSON 转义,数字使用序列化后的表示,省略空字段的规则由 Rust/TypeScript 数据模型决定。Ed25519Sha256Legacy 对规范化 JSON 的 SHA-256 十六进制结果签名,Ed25519Sha256 和 Sm2Sm3 对规范化 JSON UTF-8 字节签名。跨语言实现必须固定 UTF-8、键排序、数字和空字段规则;任何差异都会导致摘要或签名验证失败。
{
"b": 2,
"a": {
"y": 1,
"x": "OAN"
},
"items": ["one", "two"]
}
规范化时键递归按字典序排列、数组保持原顺序,输入使用 UTF-8。对应的规范化结果为:
{
"a": {
"x": "OAN",
"y": 1
},
"b": 2,
"items": ["one", "two"]
}
跨语言测试应同时固定空值、数字表示、字符串转义和对象字段省略规则,不能仅依赖某一语言运行时的默认序列化行为。
例如,上述规范化字符串的 SHA-256 结果为 03332fb018e2f0376e263dabf4b2231e3bd70767e19fb7e263060efb68476e02。这个值只对应示例对象、示例键顺序和示例 UTF-8 编码;它不是协议固定常量。Rust 侧可使用 oan-crypto::canonical_json 与 hash_json_with_suite 产生基准,TypeScript 侧应先按相同规则递归排序,再使用 crypto.subtle.digest("SHA-256", ...) 对 UTF-8 字节求摘要。
实现时要特别区分三种变化:对象成员顺序变化在规范化后不应改变结果,数组元素顺序变化会改变结果,数字从 1 改为 1.0 是否被视为相同则取决于输入在进入规范化函数前的 JSON 数值表示。生产代码不得先把 JSON 转成带缩进的文本再直接签名,也不得依赖数据库返回字段的顺序。
C.3 JWS 与分离式签名
OAN 当前共享实现使用 DataIntegrityProof 表达分离式证明,包含 type、creator、created、proofPurpose、proofValue、cryptoSuite、hashAlgorithm 和 verificationMethod。proofValue 是 Base64url 无填充的签名值;签名输入由待签名对象的规范化结果按算法套件确定。验证方必须先确定套件、重建签名输入、验证公钥与签名,再检查 creator、verificationMethod、目标 DID、有效期和状态;不能只比较 proofValue 字符串。
{
"type": "DataIntegrityProof",
"creator": "did:oan:SKDM:ExampleResourceDid#key-1",
"created": "2026-09-07T01:02:03Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:oan:SKDM:ExampleResourceDid#key-1",
"cryptoSuite": "Ed25519Sha256",
"hashAlgorithm": "SHA-256",
"proofValue": "base64url-without-padding"
}
验签失败应区分规范化输入不一致、公钥不匹配、签名无效、证明用途不符和状态不可接受,便于调用方决定是修正请求、重新获取数据还是停止处理。
一个最小的验签顺序如下:
- 从
proof.cryptoSuite或兼容的proof.type确定套件,并确认它与验证方法、公钥格式一致。 - 从 DID 文档中按
verificationMethod定位公钥,检查方法标识、控制者和目标 DID 的关系。 - 移除待验证对象中的
proof,按套件重建规范化签名输入;遗留套件还要先计算 SHA-256 十六进制摘要。 - 对
proofValue做 Base64url 无填充解码并执行签名验证,再检查created、用途、签发者授权和凭证状态。
payload without proof -> canonical JSON -> suite-specific signature input
-> Base64url proofValue -> verify with DID public key
proofValue 能够被解码只说明编码形式可读,不能说明签名正确;签名正确也不自动说明签发者获得了根平台授权或资源仍处于可用状态。
C.4 DID 与密钥编码
资源 DID 的现行语法为 did:oan:<四位大写语义码>:<32位 Base58 后缀>,后缀使用 Bitcoin Base58 字符集并排除 0、O、I、l。did:oan 解析器还校验资源类型与语义码主体部分的对应关系。公钥可按算法使用 z 前缀的 Base58 multibase,或使用 JWK:Ed25519 使用 kty=OKP、crv=Ed25519 和 Base64url 无填充的 x,SM2 使用 kty=EC、crv=SM2 以及 x、y。私钥不得进入 DID 文档、普通协议请求或服务日志。
did:oan:<四位大写语义码>:<32位 Base58 后缀>
| 材料 | 推荐表示 | 不得出现 |
|---|---|---|
| 资源 DID | did:oan 字符串 |
错误语义码、非法 Base58 字符 |
| Ed25519 公钥 | multibase 或 Ed25519 JWK | 私钥、未声明算法的裸字符串 |
| SM2 公钥 | SM2 JWK 或实现支持的公钥表示 | 不完整的 x/y 坐标 |
| 私钥 | 客户端受保护存储 | DID 文档、普通请求、服务日志 |
publicKeyMultibase 当前由 z 前缀加 Base58 编码的公钥字节组成;Ed25519 使用 32 字节公钥,SM2 使用未压缩 SEC1 公钥字节。JWK 使用 Base64url 无填充编码的字段,Ed25519 的 x 解码后应为 32 字节,SM2 的 x 和 y 解码后各为32字节。oan-crypto 会根据验证方法或显式 cryptoSuite 解析这些载体,解析成功后再进行签名验证。
示例 DID 的语义码只负责表达主体或资源类型与应用域,不由后缀直接承载公钥。随机生成的 DID 与密钥对是两个独立生成步骤;如果业务需要可重复派生,应明确使用 DidOan::derive 的控制材料和 nonce,并将派生规则视为特定实现能力,不能把随机生成的 DID 当作可从公钥反推的标识。
C.5 VC 与 VP 编码
注册 VC 和节点授权 VC 使用 JSON 对象表达 issuer、subject、状态、签发/到期时间、claims 和 proof;共享凭证实现以 AgentRegistrationCredential、NodeAuthorizationCredential 及 DataIntegrityProof 为基础。签发时对无 proof 对象生成签名,验证时移除 proof 重建原始载荷并按 proof 指定套件验签。VP 若由外部系统呈现,应保留原始结构、凭证引用和验证结果;可解析不等于 OAN 信任策略接受,过期、吊销、主体不符或签发者未授权必须拒绝或标为不可接受。
{
"type": ["VerifiableCredential", "AgentRegistrationCredential"],
"issuer": "did:oan:REGS:RegistrarExample",
"credentialSubject": {
"id": "did:oan:SKDM:ExampleResourceDid"
},
"proof": { "type": "DataIntegrityProof", "proofValue": "..." }
}
VC 解析成功只表示结构可读;调用方仍必须验证签名、签发者授权、主体、有效期和凭证状态。VP 作为外部呈现容器时,应保留凭证来源和验证结果,不把呈现动作本身当作授权。
注册 VC 的验证对象是去除 proof 后的 AgentRegistrationCredential,节点授权 VC 的验证对象是去除 proof 后的 NodeAuthorizationCredential。验证链可用下表作为实现检查顺序:
| 检查 | 关注对象 | 不通过时的含义 |
|---|---|---|
| 结构 | type、issuer、subject、时间、claims、proof |
凭证不能解析或不能进入后续验证 |
| 签名 | proof、签发者 DID 文档中的公钥 |
凭证内容未被对应私钥证明 |
| 绑定 | issuer、subject、资源 DID、DID 文档哈希和资源包哈希 |
凭证与当前资源不对应 |
| 授权 | 签发者身份、节点角色、授权域和治理状态 | 签发者不能在当前边界内作出该声明 |
| 状态 | status、issuedAt、expiresAt 及可用的吊销事实 |
凭证已失效或不应继续使用 |
验证方应保存足以复核的凭证原文、DID 文档、验证时间和错误码;不应只保存一个“验证成功”的布尔值。
C.6 密钥备份与恢复格式
本地身份备份是客户端敏感文件,可包含版本、更新时间、默认主体/智能体标识、主体/智能体/节点记录,以及 DID 文档、公私钥 JWK 和资源 profile。TypeScript SDK 当前使用版本 1 的身份存储快照;官网注册流程下载的身份备份文件名带有时间标识,资源提交成功时还可将注册 VC、DID 文档和本地身份文件打包保存。备份文件应由用户私下保管,导入时校验 JSON 结构、版本、公钥私钥对应关系和 DID 一致性;不支持的版本、缺失密钥或解析失败不得部分加载。
{
"version": 1,
"updatedAt": "2026-09-07T01:02:03Z",
"defaultSubjectDid": "did:oan:SUBJ:ExampleSubjectDid",
"subjects": [],
"agents": [],
"importedNodes": []
}
导入流程应先完成完整性和版本检查,再一次性加载主体、智能体和节点记录;发现公钥与私钥不匹配、DID 不一致或版本不支持时,应拒绝整个备份,不能生成部分可用身份。
备份文件中的私钥 JWK 是高敏感材料。浏览器下载、导入和再次导出都应由用户主动触发,页面、注册服务节点、根平台和发现服务节点不需要接收该私钥。导入后,客户端可以用私钥对一段测试消息签名,再用备份中的公钥验证,以确认密钥对一致;该测试不得把私钥或测试签名写入日志。备份文件名中的时间标识只用于区分下载文件,不参与身份或签名计算。
{
"version": 1,
"subjects": [
{
"did": "did:oan:DVFI:7YpQm9Kx2VnRb6Ts3WfHa4Cd5Ej8LgNz",
"verificationMethodId": "did:oan:DVFI:7YpQm9Kx2VnRb6Ts3WfHa4Cd5Ej8LgNz#key-1",
"publicKeyJwk": { "kty": "OKP", "crv": "Ed25519", "x": "example-public-x" },
"privateKeyJwk": { "kty": "OKP", "crv": "Ed25519", "x": "example-public-x", "d": "example-private-d" }
}
],
"agents": [],
"nodes": []
}
上例中的密钥值是不可用的占位值,只用于说明字段位置;真实备份不得采用该示例内容,也不得在公开文档或测试输出中放入生产私钥。
C.7 DID 与资源标识符测试向量
测试向量应覆盖合法的四类资源 DID、基础设施节点 DID、组织和开发者 DID,以及语义码大小写错误、部分缺失、错误分隔符、后缀长度错误、Base58 禁用字符、额外段和资源类型不匹配。每个向量固定输入字符串、解析结果或错误类型;生成型 DID 只验证格式和类型关系,随机后缀不能要求跨次运行相同。跨语言测试应使用相同 JSON 编码和算法套件,并保存预期摘要或签名验证结果。
| 向量类别 | 输入 | 合格判据 |
|---|---|---|
| 合法 DID | 四类资源和基础设施 DID | 解析成功且类型关系正确 |
| 格式错误 | 错误大小写、分隔符或长度 | 稳定返回解析错误 |
| 禁用字符 | 0、O、I、l 等 |
拒绝,不静默修正 |
| 类型不匹配 | 语义码与资源类型不一致 | 拒绝或返回明确错误 |
测试向量应同时记录输入、预期错误和副作用判据。例如,did:oan:SKFI:7YpQm9Kx2VnRb6Ts3WfHa4Cd5Ej8LgNz 应能通过语法解析并通过 skill 类型检查;把同一 DID 作为 mcp_server 资源提交时,应得到类型不匹配错误。把后缀中的 0、O、I 或 l 替换进去时,应在解析阶段失败,客户端不应自动改写后再提交。
| 测试字段 | 示例值 | 预期结果 |
|---|---|---|
| 合法语义码 | SKFI |
解析为 SK 主体码和 FI 应用域码 |
| 合法后缀 | 7YpQm9Kx2VnRb6Ts3WfHa4Cd5Ej8LgNz |
长度为32且仅含允许的 Base58 字符 |
| 资源类型匹配 | SKFI + skill |
类型校验通过 |
| 资源类型冲突 | SKFI + mcp_server |
返回 ResourceTypeMismatch 或对应错误码 |
C.8 哈希与签名验证夹具
夹具至少包含规范化原文、规范化 JSON、算法套件、哈希算法、摘要、签名输入、公钥、proof 和预期验签结果,并分别覆盖正确载荷、字段顺序变化、数值/字符串变化、公钥变化、签名截断和 proof 套件不一致。Rust 实现可由 oan-crypto 测试产生基准,TypeScript 实现应读取同一夹具复算;夹具不得包含生产私钥,生成夹具时使用专用测试密钥并公开公钥和签名结果。
夹具建议采用一个可被 Rust 和 TypeScript 同时读取的 JSON 文件,并将输入对象与预期结果分开保存。示例结构如下:
{
"suite": "Ed25519Sha256",
"payload": { "a": 1, "b": "OAN" },
"canonicalJson": "{\"a\":1,\"b\":\"OAN\"}",
"signatureInputEncoding": "utf-8-canonical-json",
"publicKeyJwk": { "kty": "OKP", "crv": "Ed25519", "x": "test-public-key" },
"proofValue": "test-signature-base64url-no-padding",
"expected": { "canonicalJson": true, "signatureValid": true }
}
夹具中的公钥、签名和摘要必须由同一组专用测试密钥真实生成;上例的 x 和 proofValue 仍是结构占位值,不能直接作为通过测试的输入。至少应运行以下负向变体:只改变 JSON 成员顺序、改变数组顺序、改变一个字符串、替换公钥、截断签名,以及将 cryptoSuite 与 proof.type 交叉替换。正确实现应只接受原始载荷和匹配公钥的组合,并在其他变体上给出稳定失败结果。
参考来源
| 来源 | 类型 | 链接 |
|---|---|---|
oan-protocol-common |
代码仓:密码套件、密钥、签名和哈希实现 | https://github.com/wolfbrother/oan-protocol-common |
oan-sdk-ts |
代码仓:客户端密码学材料和 DID 操作 | https://github.com/OpenAgenet/oan-sdk-ts |
did:oan DID Method Specification |
标准/方法规范:DID 标识和验证方法 | https://github.com/OpenAgenet/oan-public-docs/blob/main/did-oan-specs/doc/OAN DID Method Specification.md |
| OAN Yellow Paper | arXiv 黄皮书:密码学证明和信任模型 | https://arxiv.org/abs/2606.03163 |