记录、签名与血缘授权 深度解析
这一页聚焦 MGS 如何从 CLI 输入生成 unsigned genesis,如何把 Data Integrity proof 加到 canonical JSON 上,以及如何让 parent 对 child lineage 做不循环的授权。
导入链与契约
▸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 可以共享同一组库函数。
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 合在一起,说明协议入口对“字节”和“密钥文件”都严肃处理。
核心实现,逐段拆
▸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。
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。
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 的阶段。
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}
关键流程
这条流程可以从 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。
边界 / 失败模式
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 扩展。