§ · Nested detail page

记录、签名与血缘授权 深度解析

这一页聚焦 MGS 如何从 CLI 输入生成 unsigned genesis,如何把 Data Integrity proof 加到 canonical JSON 上,以及如何让 parent 对 child lineage 做不循环的授权。

§ 00 · IMPORT CHAIN

导入链与契约

▸CLI 是协议对象的最短入口

入口 cmd/mgs/main.go:37 把所有命令统一分发,cmd/mgs/main.go:43、cmd/mgs/main.go:45、cmd/mgs/main.go:47、cmd/mgs/main.go:49、cmd/mgs/main.go:51 和 cmd/mgs/main.go:57 分别暴露 keygen、init-genesis、sign、authorize-edge、gene 和 verify。这个分发层看似普通,但它规定了用户能进入协议的路径:先生成 key,再构造 genesis,再签名,再验证或登记。后续 record/proof 逻辑都不直接依赖 HTTP,因此 CLI 和 registry 可以共享同一组库函数。

cmd/mgs/main.go · L37-L78Go
37func run(args []string, stdout, stderr io.Writer) error {
38  if len(args) == 0 { usage(stderr); return errors.New("a command is required") }
42  switch args[0] {
43  case "keygen": return runKeygen(args[1:], stdout, stderr)
45  case "init-genesis": return runInitGenesis(args[1:], stdout, stderr)
47  case "sign": return runSign(args[1:], stdout, stderr)
49  case "authorize-edge": return runAuthorize(args[1:], stdout, stderr)
57  case "verify": return runVerify(args[1:], stdout, stderr)
63  case "registry-serve": return runRegistryServe(args[1:], stdout, stderr)
74  default: usage(stderr); return fmt.Errorf("unknown command %q", args[0])
78}

从测试看,作者没有只测 parser。cmd/mgs/main_test.go:33 开始的 round trip 会创建 key、生成 genesis、签名、verify,再输出 Gene;cmd/mgs/main_test.go:47 还明确检查私钥文件权限。这个测试和 cmd/mgs/main.go:123 的 init-genesis 合在一起,说明协议入口对“字节”和“密钥文件”都严肃处理。

§ 01 · CORE IMPL

核心实现,逐段拆

▸Genesis 校验先拒绝不合格语义

mgs/record.go:80 是 genesis 的主入口。它先要求版本和 recordType 对齐,然后校验 identity、controller 和 artifact commitments。特别是 mgs/record.go:105 到 mgs/record.go:108 要求 genesisKeyId 的 controller 等于 controller.did;mgs/record.go:123 到 mgs/record.go:138 则把 resolved、unresolved、authorized 三种 parentage 状态拆开。CLI 里的 cmd/mgs/main.go:151 到 cmd/mgs/main.go:165 会构造最小 GenesisRecord,再由 cmd/mgs/main.go:172 立刻走 ValidateGenesis。

mgs/record.go · L80-L149Go
80func ValidateGenesis(raw []byte, requireProof bool) (*GenesisRecord, error) {
85  if record.MGS != Version { return nil, fmt.Errorf(...) }
91  if record.Identity.Name == "" || record.Identity.Namespace == "" || record.Identity.Version == "" { ... }
99  if !subjectTypes[record.Identity.SubjectType] { return nil, fmt.Errorf(...) }
102 if record.Controller.DID == "" || record.Controller.GenesisKeyID == "" { ... }
109 if err := validateTimestamp(record.Genesis.Timestamp); err != nil { ... }
123 for index, edge := range record.Parentage {
127   if edge.Unresolved { ... continue }
136   if edge.Authorized != (edge.AuthorizationProof != nil) { ... }
140 if requireProof && record.Proof == nil { return nil, errors.New(...) }
149 return &record, nil

▸签名覆盖 canonical document 与 proof config

mgs/proof.go:78 的 SignDocument 会拒绝已经包含 proof 的文档,避免二次签名覆盖语义。签名前它在 mgs/proof.go:101 调用 proofHashData;proofHashData 在 mgs/proof.go:194 到 mgs/proof.go:222 分别 canonicalize document 和 proof options,然后拼接两个 SHA-256。验签在 mgs/proof.go:171 解码 proofValue,在 mgs/proof.go:184 resolve public key,在 mgs/proof.go:188 做 Ed25519 verify。这个流程让 cmd/mgs/main.go:47 的 sign 命令不会变成简单 JSON append。

mgs/proof.go · L194-L222Go
194func proofHashData(unsecured []byte, options ProofOptions) ([]byte, error) {
195  documentCanonical, err := Canonicalize(unsecured)
199  optionsObject := map[string]any{
200    "type": options.Type,
201    "cryptosuite": options.Cryptosuite,
203    "verificationMethod": options.VerificationMethod,
217  proofConfigHash := sha256.Sum256(optionsCanonical)
218  transformedDocumentHash := sha256.Sum256(documentCanonical)
220  hashData = append(hashData, proofConfigHash[:]...)
221  hashData = append(hashData, transformedDocumentHash[:]...)
222  return hashData, nil

▸Lineage authorization 拆掉循环依赖

如果 child genesis 里要包含 parent 的授权 proof,parent 签名时不能把这个 proof 自己也算进去。mgs/lineage.go:24 到 mgs/lineage.go:53 的 ChildCommitment 删除顶层 proof 和所有 authorizationProof,再做 canonical digest。mgs/lineage.go:56 到 mgs/lineage.go:85 构造 parent 要签的 deterministic authorization document;mgs/lineage.go:90 到 mgs/lineage.go:121 只把 proof map 放回指定 edge。这个设计让 cmd/mgs/main.go:49 的 authorize-edge 可以安全地运行在 child record 仍未最终 signed 的阶段。

mgs/lineage.go · L24-L53Go
24// ChildCommitment hashes the genesis with the top-level proof and every
25// authorizationProof removed.
27func ChildCommitment(genesis []byte) (string, error) {
35  delete(object, "proof")
36  if parentage, exists := object["parentage"]; exists {
41    for index, rawEdge := range edges {
46      delete(edge, "authorizationProof")
49  canonical, err := encodeObject(object)
53  return Digest(canonical)
54}
§ 02 · KEY FLOW

关键流程

run init-genesis -> load controller key -> build GenesisRecord -> ValidateGenesis(requireProof=false) -> sign -> SignRecord checks recordType -> SignDocument adds eddsa-jcs-2022 proof -> VerifyGenesis recomputes canonical hash -> Gene returns content-addressed identity

这条流程可以从 cmd/mgs/main.go:123、cmd/mgs/main.go:140、cmd/mgs/main.go:151、cmd/mgs/main.go:172、mgs/record.go:171、mgs/proof.go:78、mgs/record.go:152 和 mgs/record.go:168 连起来看。测试 cmd/mgs/main_test.go:50 到 cmd/mgs/main_test.go:75 对应这条路径,确保真实命令能产出以 zQm 开头的 Gene。

§ 03 · EDGE CASES

边界 / 失败模式

  • mgs/record.go:127 对 unresolved parentage 有严格组合约束,防止一个 edge 同时声明 unresolved 又夹带 parentGene 或 authorizationProof。
  • mgs/proof.go:90 会拒绝已经带 proof 的文档,避免签名对象被重复包装。
  • mgs/proof.go:145 到 mgs/proof.go:151 要求 proof @context 与 document @context 一致,减少上下文偷换。
  • mgs/lineage.go:176 到 mgs/lineage.go:185 要求授权 proof signer 是 parent controller 且使用 parent genesis key。
  • cmd/mgs/main_test.go:79 到 cmd/mgs/main_test.go:86 还测试未知命令必须失败并输出 usage,避免 CLI 静默吞错。
读者应该带走的点

Machine Genome 的 proof 不是“签一个 JSON 文件”这么简单,而是把 canonicalization、proof config、DID method 和 record-specific controller 关系绑在一起。

仍需补齐的点

当前 profile 偏向 did:key 和 genesis key,跨组织 key rotation 与更多 DID method 支持还需要后续治理和 resolver 扩展。