**去中心化身份(DID)**是由用户自己生成密钥对、在可验证注册表上锚定的全球唯一标识符,不依赖任何单一机构发放;**可验证凭证(VC)**是发行方对主体属性的一次数字签发(学历、年龄、会员资格),持有方自主决定向谁出示、出示多少。两者组合实现"发行方签发、持有方控制、验证方核验"的数据最小化身份体系,链在其中扮演锚定与撤销注册表,而非身份数据库。
入住酒店要证明"已成年",你递出的却是整张身份证:姓名、住址、证号全部交给前台与它背后的系统。从此这家酒店知道你住哪、叫什么、何时来过——为证明布尔值,你交出了全部字段。传统身份体系的结构性浪费正在于此:证明的粒度永远是"整份证件",收集方拿到的永远多于验证所需,多出来的部分成了数据泄露案的库存。去中心化身份的动机由此而来:让"证明什么"与"暴露什么"可以被精确裁剪——证明已成年而不透露生日,证明学历有效而不透露成绩单,证明账户活跃而不透露余额。
分工设计是理解这套体系的地图。发行方(大学、政府、雇主)用私钥给用户的属性签发凭证,之后不再参与每次验证。持有方(用户)把凭证存进自己的钱包,像揣着一叠盖了章的卡片,向谁出示、出示哪张自己决定。验证方(酒店、网站)只核验签名与撤销状态,拿不到发行方的数据库。区块链在其中的角色刻意克制:当锚定注册表(验证发行方公钥真伪)与撤销注册表(查凭证是否被吊销),不存任何个人数据——这条克制恰好回应了第 5.7 节"个人数据上链"的合规红线。
DID 标识符形如"方法名冒号加唯一串",解析后得到一份 DID 文档:里面列出验证密钥(用于签名验证)、授权密钥(密钥找回)、服务端点(在哪获取更多信息)。凭证则是标准化的 JSON 结构。把签发与验证流程写成可运行的模型:
import hashlib, json class Issuer: """发行方:为用户属性签发可验证凭证""" def __init__(self, name): self.name = name self.sk, self.pk = 1999, None # 教学用"私钥",真实系统为密钥对 self.pk = pow(65537, self.sk, 1000003) # 占位派生,仅示意结构 self.revoked = set() def issue(self, holder_did, claims): credential = { "发行方": self.name, "主体": holder_did, "属性": claims, # 如 学历=硕士 · 出生=1996-03 "状态": {"吊销": False}, } credential["签名"] = self._sign(credential) return credential def _sign(self, payload): blob = json.dumps(payload, ensure_ascii=False, sort_keys=True) return hashlib.sha256((blob + str(self.sk)).encode()).hexdigest()[:16] def revoke(self, credential): self.revoked.add(credential["签名"]) credential["状态"]["吊销"] = True class Verifier: """验证方:核验签名与吊销状态,只见到出示的字段""" def verify(self, credential, issuer, disclosed): sig_ok = issuer._sign({k: v for k, v in credential.items() if k != "签名"}) == credential["签名"] revoked = credential["签名"] in issuer.revoked return { "签名有效": sig_ok, "未被吊销": not revoked, "本次所见字段": disclosed, # 持有方选择披露的子集 } university = Issuer("示范大学") holder_did = "did:demo:8f3a2b" vc = university.issue(holder_did, {"学历": "硕士", "专业": "密码学", "毕业年份": 2021}) # 持有方只披露"学历"字段给验证方 print(json.dumps(Verifier().verify(vc, university, disclosed=["学历"]), ensure_ascii=False, indent=1))
注意输出的"本次所见字段"——验证方确信学历为真,却没拿到专业与毕业年份。凭证的可裁剪性是第一层隐私;第二层是选择性披露证明:连属性值都不给,只给"满足条件"的证明:
def zk_disclose(credential, predicate, witness): """示意:不暴露出生日期,只证明 满足谓词""" # predicate 例:出生年份 <= 某门槛 -> 已成年 # 真实实现走零知识证明电路(第 5.4 节的流水线) statement = f"存在出生日期使谓词成立且与凭证签名一致" proof = hashlib.sha256((predicate + witness).encode()).hexdigest()[:12] return {"陈述": statement, "证明": proof, "泄露": "无"} result = zk_disclose(vc, predicate="出生年份 <= 成年门槛", witness="1996-03") print(result) print("验证方得到:已成年 == True;得不到:具体生日、其他字段、其他凭证")
真实实现里,这个"谓词证明"由零知识电路完成:持有方在本地生成证明(我确实持有一张该大学签发的、未吊销的、出生年份满足条件的凭证),验证方毫秒级核验。第 5 章的重器在身份场景的落地,就是把第 5.1 节的"粒度浪费"问题反过来解。
落地难点排前三的全不是密码学。密钥管理:DID 的私钥就是身份本身,丢失即断链——规范的解法是 DID 文档里的验证密钥轮换与授权密钥机制(老密钥签新密钥完成交接),以及社交恢复(预置多名担保人联合可助恢复);这与第 2.5 节的钱包密钥管理一脉相承,只是轮换被写进了身份协议本体。锚定与发现:验证方要确认"这所大学的公钥是真的",方法名与注册表(链、域名、数据库皆可为注册表)承担公钥目录角色;锚在链上的好处是不可篡改与全球可验,代价是每条链一个命名空间。生态互认:验证方认不认发行方是商业与法律问题——技术能保证签名真伪,不能命令酒店认可某大学。这解释了落地节奏:封闭生态(企业内、政务链)先行,开放互认靠标准缓慢铺路。
⚠️ 三条现场纪律:撤销检查不可省略(被开除的学历、被吊销的执照,验证时必须查撤销注册表——离线缓存的撤销状态要带过期时间);凭证聚合要防关联(同一 DID 出示多张凭证会被串成画像,成对的成对一次性 DID 或zk 匿名凭证是对策);发行方私钥保护等级要配得上它签发的所有凭证(大学签发密钥失守,全校学历作废重发)。
💡 判词:DID 把身份从"机构的数据库"搬回"用户的钱包",VC 把证明从"整份出示"裁成"恰好够用";链只当公证人,不当档案馆。
老病换了马甲继续出诊:钓鱼升级为"恶意验证方索取过度字段"(用户教育与钱包侧的字段级授权是对策);数据泄露升级为"凭证钱包失窃"(生物识别加本地加密缓解);中心化滥用升级为"发行方滥发与选择性拒发"(多发行方竞争与声誉市场部分对冲)。新疾则来自体系本身:去中心化恢复机制被社会工程攻破(担保人被冒充)、锚定注册表的治理被劫持(方法名控制器作恶)、zk 凭证的电路漏洞(谓词写错会放行不该放行的人)。身份是攻击者最勤快的赛道,因为它兑付的是权限本身。
身份之外,还有一片悬在整个工厂头顶的云:量子计算。下一章直面它——哪些材料会失效、何时失效、以及后量子迁移这份长线工程单到底怎么排期。