ERC-725 与 ERC-735 去中心化身份实现:声明发布、验证请求与链上凭证管理
ERC-725 与 ERC-735 去中心化身份实现声明发布、验证请求与链上凭证管理一、DID 的标准不止一种但 725735 的组合最完整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; // 声明主题如 1KYC, 2技能, 3社交绑定 uint256 scheme; // 签名方案1ECDSA, 2Schnorr 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 GasSSTORE在以太坊主网上单个声明的存储成本约为 $5-$15按 2026 年中 Gas 价格。一个完整身份10 声明、5 密钥的部署成本可能达到 $100。这是 L2 和侧链方案如 Polygon ID、zkSync 上的 DID的主要优势场景。身份的跨链一致性ERC-725 身份天然局限于部署它的链跨链场景需要额外的桥接逻辑。一种方案是在目标链部署镜像合约通过 LayerZero 或 Wormhole 同步密钥变更。但这引入了信任假设——跨链消息中继的验证者也可以被攻击。信任模型的链外化谁来判断一个 KYC 发证方是不是可信的ERC-735 没有规定信任模型。实际部署中验证方需要维护自己的发证方白名单这个白名单的更新机制社区投票、代币质押、多重签名自身就是一套治理系统。所以 ERC-725735 解决的只是如何表达声明而不是该信谁。五、总结ERC-725 和 ERC-735 构建了一个自包含的链上身份体系ERC-725 管理身份结构密钥、存储、代理执行ERC-735 管理身份的断言谁来证明什么。这种身份即合约的设计使得用户的主权不依赖于任何第三方服务——身份合约一旦部署除非用户自己操作没有人能转移它的资产或删除它的声明。但主权身份也带来了主权负担。用户需要管理密钥、支付 Gas、理解权限模型。下一阶段的演进方向应该是把 ERC-725 的复杂权限管理封装进钱包层让用户在不需要理解底层合约的情况下使用去中心化身份就像今天使用 Apple Pay 不需要理解 NFC 协议一样。