当身份不再由单一机构背书------用 Protocol 契约设计可演进的 DID 基础设施
本文是 GEID 开源项目技术博客第一篇。项目仓库:fumiguo/geid-monorepo
一个绕不开的尴尬
你有没有遇到过这种事:用微信登录某个 App,授权了一堆信息,过后却不知道怎么收回;或者公司账号离职即失效,你在那个平台积累的所有数据一夜归零。
这不是某个产品做得差,是整个身份体系的底层设计问题------你的身份不是你的,是发给你身份的那个机构的。机构说你有,你就有;机构说停,你就停。
AI 时代这个问题会更尖锐。当 Agent 代表你去做事、去交易、去跨系统协作,它需要一个能跨域、可审计、你不高兴就能收回的身份凭证。传统机构背书的模式撑不住这三个需求。
W3C DID 标准很好,但落地各有各的"耦合病"
W3C 在 2022 年发布了 DID 标准规范,方向是对的:去中心化、自主权、可验证。但落地实现时,大家各干各的:
- 有的绑死在某条链上(换链等于重写)
- 有的把身份逻辑和业务逻辑揉在一起(改一个动全身)
- 有的把"发证"和"验证"写死在同一个服务里(没法独立扩展)
问题不在标准,在于实现时没有把契约 和实现分开。标准告诉你"身份应该长什么样",但没告诉你"代码层面怎么解耦才不会烂"。
我们的方案:两个 Protocol,构造函数注入,实现可热替换
在 GEID 开源项目里,我们用 Python 的 Protocol(PEP 544)做了这件事------把身份系统的契约和实现彻底分开,用构造函数注入把它们连起来。
第一步:定义契约,不是定义类
python
# geid-core/geid_core/interfaces.py
from typing import Protocol, runtime_checkable
@runtime_checkable
class IBlockchain(Protocol):
"""链上操作。可替换:mock / 真实链 / RPC 客户端。"""
def generate_wallet(self) -> str: ...
def mint_tokens(self, to_address: str, amount: int) -> bool: ...
def burn_tokens(self, from_address: str, amount: int) -> bool: ...
def freeze_account(self, address: str) -> bool: ...
def apply_sanction(self, address: str, sanctioned: bool = True) -> bool: ...
@runtime_checkable
class IIdentity(Protocol):
"""身份注册、查询、验证。"""
async def register_entity(self, data: EntityCreate) -> dict: ...
def query_entity(self, geid: str) -> dict: ...
async def trigger_verification(self, geid: str) -> dict: ...
注意三个细节:
Protocol是结构性子类型------任何实现了这些方法的对象都算"符合契约",不需要继承@runtime_checkable让你能用isinstance(obj, IBlockchain)做运行时检查- 契约里只有方法签名,没有任何实现,也没有任何业务依赖
第二步:实现可以换,调用方不关心
python
# geid-identity/geid_identity/services/blockchain.py
class BlockchainService:
"""实现 IBlockchain。换成真实链客户端,调用方一行不改。"""
def generate_wallet(self) -> str:
return Account.create().address
def mint_tokens(self, to_address: str, amount: int) -> bool:
logger.info("mint %s -> %s", amount, to_address)
return True
def burn_tokens(self, from_address: str, amount: int) -> bool:
logger.info("burn %s <- %s", amount, from_address)
return True
def freeze_account(self, address: str) -> bool:
logger.info("freeze %s", address)
return True
def apply_sanction(self, address: str, sanctioned: bool = True) -> bool:
logger.info("sanction %s = %s", address, sanctioned)
return True
现在是 mock(用 eth_account 生成钱包地址,mint 只记日志)。明天要接真实链,写一个 ChainRPCClient 实现同样的方法,业务代码一行都不用改。
第三步:构造函数注入,不是 import
这是最关键的一步。看注册服务怎么用链:
python
# geid-identity/geid_identity/services/registration.py
class RegistrationService:
"""实现 IIdentity.register_entity / query_entity。"""
def __init__(self, chain=None):
self._chain = chain or BlockchainService() # 注入,不是 import
async def register_entity(self, data: EntityCreate) -> dict:
if not await self._verify_kyc(data.kyc_token):
raise HTTPException(403, "KYC verification failed")
geid = generate_geid(data.region, data.sub_class,
data.generation, data.sequence)
wallet = self._chain.generate_wallet() # 调接口,不调实现
tokens = calculate_initial_tokens(data.entity_type.value,
data.tax_amount, data.population)
self._chain.mint_tokens(wallet, tokens)
db = get_db()
db.execute(
"INSERT INTO entities (geid, entity_type, name, wallet_address, "
"token_balance, kyc_verified) VALUES (?,?,?,?,?,1)",
(geid, data.entity_type.value, data.name, wallet, tokens),
)
db.commit()
return {"geid": geid, "wallet": wallet, "tokens": tokens}
关键在 def __init__(self, chain=None):
- 不写
from blockchain import BlockchainService然后在方法里BlockchainService().generate_wallet() - 而是 把 chain 作为参数传进来,默认给一个 mock
这一步决定了整个系统的生死。因为 chain 是注入的:
| 场景 | 注入什么 | 业务代码改不改 |
|---|---|---|
| 单体运行 | 本地 BlockchainService |
不改 |
| 微服务化 | ChainHTTPClient(实现 IBlockchain,内部走 HTTP) |
不改 |
| 测试 | mock 对象,不碰任何真实链 | 不改 |
三种部署模式自由切换,业务代码一行不动。 这不是"未来支持",是现在就已经这么写的。
附带:一个带校验位的全局编号
身份得有个唯一编号。我们设计了一个带校验位的方案:
python
# geid-identity/geid_identity/utils.py
def generate_geid(region, sub_class, generation, sequence) -> str:
base = region + sub_class + generation + sequence
return f"{base}-{generate_check_digit(base)}"
def generate_check_digit(base_id: str) -> str:
digits = [_char_to_digit(c) for c in base_id]
weights = list(range(1, len(digits) + 1))
total = sum(d * w for d, w in zip(digits, weights))
r = total % 11
return "X" if r == 10 else str(r)
例如 generate_geid("CN", "A", "01", "0001") → CNA010001-4。末尾的 4 是校验位,用加权模 11 算出来的,输入错一位校验位就对不上,防止手滑录错。这个思路跟身份证号最后一位、ISBN 书号校验位是同一类设计。
这东西能用在哪
- 身份中间件:嵌进既有业务系统,提供注册/验证 API,底层链可替换
- 多链身份网关:同一套身份逻辑,按租户切不同的链
- Agent 身份层:给自主决策的 Agent 发可验证凭证,决策行为可审计、可追溯
- 政务/企业数字身份:合规要求高的场景,链实现可替换成联盟链或政务云
核心卖点只有一个:今天用 mock 跑通,明天接真实链不用改业务代码。 这不是画饼,是已经写好的代码。
为什么用 Protocol 而不是 ABC
有人会问:用抽象基类(ABC)也能解耦,为什么选 Protocol?
- ABC 是** nominal subtyping**(名义子类型)------必须显式继承,改一个基类牵一发动全身
- Protocol 是 structural subtyping(结构子类型)------鸭子类型带类型检查,第三方库的类不用改源码也能符合契约
在身份基础设施这种"实现可能来自不同团队、不同链"的场景里,结构子类型意味着我不强迫你继承我的类,你只要方法签名对得上,就算符合我的契约。这对开源项目尤其重要------别人集成你的时候,门槛低很多。
开源了,欢迎来看
完整代码在 GitHub:fumiguo/geid-monorepo
geid-core/geid_core/interfaces.py--- 五个 Protocol 契约(IBlockchain / IIdentity / IBalance / IGovBridge / IAgent)geid-identity/--- DID 身份服务实现geid-balance/--- 可持续性评估引擎geid-protocol/--- Agent 治理协议
MIT 协议,欢迎提 Issue、提 PR。如果你也在做身份基础设施或者 Agent 治理,想聊聊怎么把契约设计做得更好------Issue 区见。
作者:fumiguo · 项目持续更新中,欢迎 Star 关注
当身份不再由单一机构背书------用 Protocol 契约设计可演进的 DID 基础设施
本文是 GEID 开源项目技术博客第一篇。项目仓库:fumiguo/geid-monorepo
一个绕不开的尴尬
你有没有遇到过这种事:用微信登录某个 App,授权了一堆信息,过后却不知道怎么收回;或者公司账号离职即失效,你在那个平台积累的所有数据一夜归零。
这不是某个产品做得差,是整个身份体系的底层设计问题------你的身份不是你的,是发给你身份的那个机构的。机构说你有,你就有;机构说停,你就停。
AI 时代这个问题会更尖锐。当 Agent 代表你去做事、去交易、去跨系统协作,它需要一个能跨域、可审计、你不高兴就能收回的身份凭证。传统机构背书的模式撑不住这三个需求。
W3C DID 标准很好,但落地各有各的"耦合病"
W3C 在 2022 年发布了 DID 标准规范,方向是对的:去中心化、自主权、可验证。但落地实现时,大家各干各的:
- 有的绑死在某条链上(换链等于重写)
- 有的把身份逻辑和业务逻辑揉在一起(改一个动全身)
- 有的把"发证"和"验证"写死在同一个服务里(没法独立扩展)
问题不在标准,在于实现时没有把契约 和实现分开。标准告诉你"身份应该长什么样",但没告诉你"代码层面怎么解耦才不会烂"。
我们的方案:两个 Protocol,构造函数注入,实现可热替换
在 GEID 开源项目里,我们用 Python 的 Protocol(PEP 544)做了这件事------把身份系统的契约和实现彻底分开,用构造函数注入把它们连起来。
第一步:定义契约,不是定义类
python
# geid-core/geid_core/interfaces.py
from typing import Protocol, runtime_checkable
@runtime_checkable
class IBlockchain(Protocol):
"""链上操作。可替换:mock / 真实链 / RPC 客户端。"""
def generate_wallet(self) -> str: ...
def mint_tokens(self, to_address: str, amount: int) -> bool: ...
def burn_tokens(self, from_address: str, amount: int) -> bool: ...
def freeze_account(self, address: str) -> bool: ...
def apply_sanction(self, address: str, sanctioned: bool = True) -> bool: ...
@runtime_checkable
class IIdentity(Protocol):
"""身份注册、查询、验证。"""
async def register_entity(self, data: EntityCreate) -> dict: ...
def query_entity(self, geid: str) -> dict: ...
async def trigger_verification(self, geid: str) -> dict: ...
注意三个细节:
Protocol是结构性子类型------任何实现了这些方法的对象都算"符合契约",不需要继承@runtime_checkable让你能用isinstance(obj, IBlockchain)做运行时检查- 契约里只有方法签名,没有任何实现,也没有任何业务依赖
第二步:实现可以换,调用方不关心
python
# geid-identity/geid_identity/services/blockchain.py
class BlockchainService:
"""实现 IBlockchain。换成真实链客户端,调用方一行不改。"""
def generate_wallet(self) -> str:
return Account.create().address
def mint_tokens(self, to_address: str, amount: int) -> bool:
logger.info("mint %s -> %s", amount, to_address)
return True
def burn_tokens(self, from_address: str, amount: int) -> bool:
logger.info("burn %s <- %s", amount, from_address)
return True
def freeze_account(self, address: str) -> bool:
logger.info("freeze %s", address)
return True
def apply_sanction(self, address: str, sanctioned: bool = True) -> bool:
logger.info("sanction %s = %s", address, sanctioned)
return True
现在是 mock(用 eth_account 生成钱包地址,mint 只记日志)。明天要接真实链,写一个 ChainRPCClient 实现同样的方法,业务代码一行都不用改。
第三步:构造函数注入,不是 import
这是最关键的一步。看注册服务怎么用链:
python
# geid-identity/geid_identity/services/registration.py
class RegistrationService:
"""实现 IIdentity.register_entity / query_entity。"""
def __init__(self, chain=None):
self._chain = chain or BlockchainService() # 注入,不是 import
async def register_entity(self, data: EntityCreate) -> dict:
if not await self._verify_kyc(data.kyc_token):
raise HTTPException(403, "KYC verification failed")
geid = generate_geid(data.region, data.sub_class,
data.generation, data.sequence)
wallet = self._chain.generate_wallet() # 调接口,不调实现
tokens = calculate_initial_tokens(data.entity_type.value,
data.tax_amount, data.population)
self._chain.mint_tokens(wallet, tokens)
db = get_db()
db.execute(
"INSERT INTO entities (geid, entity_type, name, wallet_address, "
"token_balance, kyc_verified) VALUES (?,?,?,?,?,1)",
(geid, data.entity_type.value, data.name, wallet, tokens),
)
db.commit()
return {"geid": geid, "wallet": wallet, "tokens": tokens}
关键在 def __init__(self, chain=None):
- 不写
from blockchain import BlockchainService然后在方法里BlockchainService().generate_wallet() - 而是 把 chain 作为参数传进来,默认给一个 mock
这一步决定了整个系统的生死。因为 chain 是注入的:
| 场景 | 注入什么 | 业务代码改不改 |
|---|---|---|
| 单体运行 | 本地 BlockchainService |
不改 |
| 微服务化 | ChainHTTPClient(实现 IBlockchain,内部走 HTTP) |
不改 |
| 测试 | mock 对象,不碰任何真实链 | 不改 |
三种部署模式自由切换,业务代码一行不动。 这不是"未来支持",是现在就已经这么写的。
附带:一个带校验位的全局编号
身份得有个唯一编号。我们设计了一个带校验位的方案:
python
# geid-identity/geid_identity/utils.py
def generate_geid(region, sub_class, generation, sequence) -> str:
base = region + sub_class + generation + sequence
return f"{base}-{generate_check_digit(base)}"
def generate_check_digit(base_id: str) -> str:
digits = [_char_to_digit(c) for c in base_id]
weights = list(range(1, len(digits) + 1))
total = sum(d * w for d, w in zip(digits, weights))
r = total % 11
return "X" if r == 10 else str(r)
例如 generate_geid("CN", "A", "01", "0001") → CNA010001-4。末尾的 4 是校验位,用加权模 11 算出来的,输入错一位校验位就对不上,防止手滑录错。这个思路跟身份证号最后一位、ISBN 书号校验位是同一类设计。
这东西能用在哪
- 身份中间件:嵌进既有业务系统,提供注册/验证 API,底层链可替换
- 多链身份网关:同一套身份逻辑,按租户切不同的链
- Agent 身份层:给自主决策的 Agent 发可验证凭证,决策行为可审计、可追溯
- 政务/企业数字身份:合规要求高的场景,链实现可替换成联盟链或政务云
核心卖点只有一个:今天用 mock 跑通,明天接真实链不用改业务代码。 这不是画饼,是已经写好的代码。
为什么用 Protocol 而不是 ABC
有人会问:用抽象基类(ABC)也能解耦,为什么选 Protocol?
- ABC 是** nominal subtyping**(名义子类型)------必须显式继承,改一个基类牵一发动全身
- Protocol 是 structural subtyping(结构子类型)------鸭子类型带类型检查,第三方库的类不用改源码也能符合契约
在身份基础设施这种"实现可能来自不同团队、不同链"的场景里,结构子类型意味着我不强迫你继承我的类,你只要方法签名对得上,就算符合我的契约。这对开源项目尤其重要------别人集成你的时候,门槛低很多。
开源了,欢迎来看
完整代码在 GitHub:fumiguo/geid-monorepo
geid-core/geid_core/interfaces.py--- 五个 Protocol 契约(IBlockchain / IIdentity / IBalance / IGovBridge / IAgent)geid-identity/--- DID 身份服务实现geid-balance/--- 可持续性评估引擎geid-protocol/--- Agent 治理协议
MIT 协议,欢迎提 Issue、提 PR。如果你也在做身份基础设施或者 Agent 治理,想聊聊怎么把契约设计做得更好------Issue 区见。
作者:fumiguo · 项目持续更新中,欢迎 Star 关注