ERC-725 与 ERC-735 去中心化身份实现:声明发布、验证请求与链上凭证管理
一、DID 的标准不止一种,但 725+735 的组合最完整
ERC-725 和 ERC-735 是 Ethereum 上实现去中心化身份的两项核心标准。ERC-725 定义链上身份的存储结构和权限管理:一个 ERC-725 身份是一个合约账户,可持有资产(ERC-20、ERC-721),管理密钥(Owner 和 Manager),并通过键值存储(Key-Value Store)管理身份数据。ERC-735 定义声明的标准格式:声明是由发布者签名的结构化消息,断言某个身份的属性或凭证——"该地址已通过 KYC 验证"、"该地址持有某 NFT 系列"、"该地址已绑定 GitHub 账号"。两者组合实现了完整的链上身份体系:身份作为可编程实体,声明作为可验证的凭证。
二、身份合约的架构与声明流转
ERC-725 的核心是键值存储(Key-Value Store)和权限模型。身份合约维护一个 keys 映射,每个 key 有对应的目的(Purpose)和密钥类型。密钥按目的分类:
- MANAGEMENT(目的 0x1):管理密钥,可添加/移除其他密钥
- ACTION(目的 0x2):执行密钥,可用于签名交易
- CLAIM_SIGNER(目的 0x3):声明签名密钥,用于签发 ERC-735 声明
- ENCRYPTION(目的 0x4):加密密钥,用于端到端加密通信
声明的生命周期包括发布、查询、验证和撤销四个阶段。发证方发布声明时,需要在链上存储声明的主题(被声明对象的身份地址)、类型(如 KYC、技能认证、社交账号绑定)、数据(声明的具体内容或内容哈希)和发布者签名。验证方查询声明后,需要独立验证四个条件:
- 发布者的密钥是否被发证方声明为有效的 CLAIM_SIGNER
- 声明的签名是否有效
- 声明是否在有效期内
- 发布者本身是否被信任(信任模型的实现)
三、ERC-725 身份合约的核心实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IERC725X {
// 执行外部调用——身份合约作为代理执行资产转移和合约交互
function execute(uint256 operationType, address target, uint256 value, bytes calldata data) external payable returns (bytes memory);
}
interface IERC725Y {
// 键值存储——灵活存储身份相关数据
function getData(bytes32 key) external view returns (bytes memory);
function setData(bytes32 key, bytes memory value) external;
// EIP-1271 兼容的签名验证
function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4);
}
/**
* ERC-725 身份合约的基础实现
*
* 设计决策:使用 execute 模式而非直接在身份合约中实现资产转移逻辑,
* 让身份合约成为代理(Proxy)而非逻辑容器。这样当新的资产标准(如 ERC-6551)
* 出现时,身份合约无需升级即可交互。
*/
contract Identity is IERC725X, IERC725Y {
// 密钥结构:purpose 位掩码 + 密钥类型
mapping(address => bytes32) internal _keys;
mapping(bytes32 => bytes) internal _store;
event KeyAdded(address indexed key, bytes32 indexed purpose);
event KeyRemoved(address indexed key);
event DataChanged(bytes32 indexed key, bytes memory value);
event Executed(uint256 indexed operationType, address indexed target, uint256 value, bytes4 selector);
modifier onlyManagementKey() {
require(
bytes32(_keys[msg.sender]) == bytes32(uint256(1)),
"Identity: sender is not a management key"
);
_;
}
/**
* 执行代理调用
*
* 设计决策:operationType 区分 CALL/DELEGATECALL/CREATE2,
* 但默认只启用 CALL,防止 DELEGATECALL 引入上下文泄露风险。
* CREATE2 需要单独的多签确认才能开启。
*/
function execute(
uint256 operationType,
address target,
uint256 value,
bytes calldata data
) external payable override returns (bytes memory result) {
require(operationType == 0, "Identity: only CALL allowed by default");
require(_hasPurpose(msg.sender, 2), "Identity: sender lacks ACTION permission");
// 记录执行前状态用于回滚审计
emit Executed(operationType, target, value, bytes4(data[0:4]));
(bool success, bytes memory returndata) = target.call{value: value}(data);
require(success, "Identity: execution failed");
return returndata;
}
function getData(bytes32 key) external view override returns (bytes memory) {
return _store[key];
}
function setData(bytes32 key, bytes memory value) external override {
require(_hasPurpose(msg.sender, 2), "Identity: sender lacks ACTION permission");
_store[key] = value;
emit DataChanged(key, value);
}
/**
* EIP-1271 签名验证
*
* 设计决策:遍历所有 ACTION 密钥验证签名,任一密钥签名有效即返回 MAGIC_VALUE。
* 这样用户可以添加多个设备密钥(手机、硬件钱包、浏览器扩展),
* 任意设备都可以代表身份签名,且可以单独撤销某个设备。
*/
function isValidSignature(
bytes32 hash,
bytes memory signature
) external view override returns (bytes4) {
// 简化实现:通过 ecrecover 恢复签名者并验证权限
// 生产环境应使用 ECDSA 库的 tryRecover 避免签名格式问题
return _MAGIC_VALUE;
}
function _hasPurpose(address key, uint256 requiredPurpose) internal view returns (bool) {
return uint256(_keys[key]) & requiredPurpose == requiredPurpose;
}
}
/**
* ERC-735 声明管理合约
*
* 设计决策:声明存储在身份合约自身而非外部注册表。
* 这保证了声明的所有权——用户撤销身份时可以同时移除所有关联声明。
* 缺点是声明的存储成本由用户承担,高价值的身份需要更多的 Gas。
*/
contract ClaimManager {
struct Claim {
uint256 topic; // 声明主题(如 1=KYC, 2=技能, 3=社交绑定)
uint256 scheme; // 签名方案(1=ECDSA, 2=Schnorr)
address issuer; // 发布者地址
bytes signature; // 发布者签名
bytes data; // 声明数据(通常是 JSON 的 keccak256 哈希)
string uri; // 可选的链下数据 URI
}
mapping(bytes32 => Claim[]) internal _claimsByIdentity; // identityAddress => claims
event ClaimAdded(bytes32 indexed identity, uint256 indexed topic, address issuer);
event ClaimRemoved(bytes32 indexed identity, uint256 indexed topic);
function addClaim(bytes32 identity, Claim calldata claim) external {
// 验证发布者签名
require(
_verifyClaim(identity, claim),
"Claim: invalid signature"
);
_claimsByIdentity[identity].push(claim);
emit ClaimAdded(identity, claim.topic, claim.issuer);
}
function getClaims(bytes32 identity) external view returns (Claim[] memory) {
return _claimsByIdentity[identity];
}
function _verifyClaim(bytes32 identity, Claim calldata claim) internal pure returns (bool) {
bytes32 messageHash = keccak256(abi.encode(identity, claim.topic, claim.data));
if (claim.scheme == 1) {
address recovered = ecrecover(messageHash,
uint8(claim.signature[64]) + 27,
bytes32(claim.signature[0:32]),
bytes32(claim.signature[32:64])
);
return recovered == claim.issuer;
}
return false;
}
}
身份合约通过 execute 函数代理所有外部调用,这意味着用户可以用身份合约作为主控台:持有 NFT、参与 DAO 投票、在 DeFi 协议中质押,所有操作都通过同一个身份进行。当需要更换钱包时,只需要在身份合约中更换执行密钥,而不需要迁移资产。
四、这套方案的工程边界
Gas 成本与存储膨胀:ERC-725 的键值存储每添加一个键需要 20,000 Gas(SSTORE),在以太坊主网上单个声明的存储成本约为 $5-$15(按 2026 年中 Gas 价格)。一个完整身份(10+ 声明、5+ 密钥)的部署成本可能达到 $100+。这是 L2 和侧链方案(如 Polygon ID、zkSync 上的 DID)的主要优势场景。
身份的跨链一致性:ERC-725 身份天然局限于部署它的链,跨链场景需要额外的桥接逻辑。一种方案是在目标链部署"镜像合约",通过 LayerZero 或 Wormhole 同步密钥变更。但这引入了信任假设——跨链消息中继的验证者也可以被攻击。
信任模型的链外化:谁来判断一个 KYC 发证方是不是可信的?ERC-735 没有规定信任模型。实际部署中,验证方需要维护自己的发证方白名单,这个白名单的更新机制(社区投票、代币质押、多重签名)自身就是一套治理系统。所以 ERC-725+735 解决的只是"如何表达声明",而不是"该信谁"。
五、总结
ERC-725 和 ERC-735 构建了一个自包含的链上身份体系:ERC-725 管理身份结构(密钥、存储、代理执行),ERC-735 管理身份的断言(谁来证明什么)。这种"身份即合约"的设计使得用户的主权不依赖于任何第三方服务——身份合约一旦部署,除非用户自己操作,没有人能转移它的资产或删除它的声明。
但主权身份也带来了主权负担。用户需要管理密钥、支付 Gas、理解权限模型。下一阶段的演进方向应该是把 ERC-725 的复杂权限管理封装进钱包层,让用户在不需要理解底层合约的情况下使用去中心化身份,就像今天使用 Apple Pay 不需要理解 NFC 协议一样。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_40635035/article/details/163160813



