ONCHAINID源码分析(二)
上一章节我们对ONCHAINID做了一个总览、对各个组成部分做了概要性质的讲解,然后着重对整套合约的部署框架和代理合约设计做了代码分析。这使得我们对整体有了一定的理解,本章我们继续学习ONCHAINID的业务逻辑部分。
业务逻辑主要集中在Verifier.sol
、Identity.sol
、ClaimIssuer.sol
这三个文件里。
四、身份验证逻辑讲解
前文我们知道,通过部署框架我们得到了IdentityProxy的地址,以后调用这个代理合约就相当于调用Identity合约的逻辑,我们可以想象得到,在实际业务场景中一定是外部的系统或者合约需要在RWA业务逻辑中去使用我们的ONCHAINID去校验身份、也就是校验一个钱包地址对应的Identity合约(的IdentityProxy代理合约)是否具备必要的Claim,以及这些Claim是不是合规指定的Issuer签发的。这个逻辑就是写在Verifier.sol
的,它是外部业务调用ONCHAINID的入口。另外这里也可以看出来身份Identity与身份验证Verifier解耦的设计思想,每个项目要求的用户身份可能会不同,我们可以针对一个特定的项目创建一个Verifier,而用户的身份合约则可以复用、不必每个项目都创建一次。
与之前一样,我摘选Verifier.sol
相关的重要部分进行讲解,先看下几个状态变量:
uint256[] public requiredClaimTopics; //必须的Claims
IClaimIssuer[] public trustedIssuers; //指定的可信Issuers
mapping(address => uint256[]) public trustedIssuerClaimTopics; //可信Issuer与其可颁发的Claims的映射
mapping(uint256 => IClaimIssuer[]) public claimTopicsToTrustedIssuers;//所有可颁发某个Claim的Issuer
接下来我们看下主要方法verify(address identity)
:
function verify(address identity) public view returns(bool isVerified) {if (requiredClaimTopics.length == 0) {return true;}uint256 foundClaimTopic;uint256 scheme;address issuer;bytes memory sig;bytes memory data;uint256 claimTopic;//针对必须的ClaimTopic逐个进行判断:这个ClaimTopic叠加合约允许的签发者,得到ClaimIds,//然后去看Identity里有没有这个ClaimIds中的至少1个,有的话还要去Issuer那验证是否有效for (claimTopic = 0; claimTopic < requiredClaimTopics.length; claimTopic++) {IClaimIssuer[] memory trustedIssuersForClaimTopic =this.getTrustedIssuersForClaimTopic(requiredClaimTopics[claimTopic]);if (trustedIssuersForClaimTopic.length == 0) {return false; //某个必须的claim对应的可信签发者一个都没有}//requiredClaimTopics[claimTopic]这个Claim允许哪些Issuer可以颁发,//为了理解更直观,假设有5个Issuer允许颁发、那么对应的Claim应该也是5个,那这5个Claim的ClaimId什么bytes32[] memory claimIds = new bytes32[](trustedIssuersForClaimTopic.length);for (uint256 i = 0; i < trustedIssuersForClaimTopic.length; i++) {claimIds[i] = keccak256(abi.encode(trustedIssuersForClaimTopic[i], requiredClaimTopics[claimTopic]));}//接下来看,入参的这个identity合约有没有这5个ClaimId中的至少1个for (uint256 j = 0; j < claimIds.length; j++) {(foundClaimTopic, scheme, issuer, sig, data, ) =IIdentity(identity).getClaim(claimIds[j]);if (foundClaimTopic == requiredClaimTopics[claimTopic]) { //这个Identity有该ClaimIdtry IClaimIssuer(issuer).isClaimValid(IIdentity(identity), requiredClaimTopics[claimTopic], sig,data) returns (bool _validity) {if (_validity) { //验证通过,结束循环,去判断下一个Claimj = claimIds.length;}if (!_validity && j == (claimIds.length - 1)) { //验证不通过,且5个都验证过了,falsereturn false;}} catch {if (j == (claimIds.length - 1)) {return false;}}} else if (j == (claimIds.length - 1)) { //这个Identity没有该ClaimId,且5个都没有,返回falsereturn false;}}}return true;
}
代码逻辑比价复杂,我们举例来帮助大家直观的理解:假设一个项目规定参与的用户必须取得topic为1、2的Claim,并且规定只认可A B C D E这5个Issuer颁发的这些Claim。那么我们就要去Identity里看看ClaimId(A,1), ClaimId(B,1) ... ClaimId(E,1)至少要有1个、且对应的Claim到其颁发者处可以验证通过,同理ClaimId(A,2), ClaimId(B,2) ... ClaimId(E,2)也是至少要有1个且可验证有效。这就是verify(address identity)
方法的主要逻辑。
五、用户身份合约Identity的管理
回顾前面IdFactory创建IdentityProxy,我们可以看到是将用户的钱包地址与IdentityProxy合约进行了关联的,且作为了管理Key:
//IdentityProxy的构造方法
//nitialManagementKey就是IdFactory用create2调用创建IdentityProxy时候传入的钱包地址
constructor(address _implementationAuthority, address initialManagementKey) {//通过delegatecall调用实现合约Identity的initialize(address)方法逻辑,完成对代理合约的创建
}
这似乎意味着用户可以自己管理自己的身份合约。实际上确实是这样,作为Web3.0的数字身份系统,用户必然是有权利管理自己的身份的(也只有用户自己有最高权限,平台创建完IdentityProxy之后没有保留控制权、不能直接操作用户这个身份合约)。我们来分析一下“原型”Identity
合约、因为用户身份管理业务逻辑都是在这里边的,这也是ONCHAINID这套合约的核心部分之一、也比较复杂。照例我选取主要的代码进行讲解:
先看看对于这个合约而言很重要的modifier部分:
//只能通过代理合约 fallback() delegatecall调用,逻辑合约里_canInteract == false
modifier delegatedOnly() {require(_canInteract == true, "Interacting with the library contract is forbidden.");_;
}//合约的MANAGEMENT key才能调用,代理合约创建的时候传入的那个用户钱包地址就是MANAGEMENT
//msg.sender == address(this)内部调用也可以
modifier onlyManager() {require(msg.sender == address(this) || keyHasPurpose(keccak256(abi.encode(msg.sender)), 1), "Permissions: Sender does not have management key");_;
}//合约CLAIM key才能调用, Issuer持有
modifier onlyClaimKey() {require(msg.sender == address(this) || keyHasPurpose(keccak256(abi.encode(msg.sender)), 3), "Permissions: Sender does not have claim signer key");_;
}
可以看到对于Identity合约的方法可以通过delegatedOnly指定只能通过代理合约进行调用、onlyManager来指定方法只有这个身份管理员才可以调用(一般是用户钱包地址)、通过onlyClaimKey指定该方法只有Issuer可以调用(谁是本身份合约的Issuer Key也是需要用户MANAGEMENT key指定)。
我们接下来对用于管理身份的几个重点方法进行讲解:
function addKey(bytes32 _key, uint256 _purpose, uint256 _type)publicdelegatedOnlyonlyManageroverridereturns (bool success)function removeKey(bytes32 _key, uint256 _purpose)publicdelegatedOnlyonlyManageroverridereturns (bool success)function addClaim(uint256 _topic,uint256 _scheme,address _issuer,bytes memory _signature,bytes memory _data,string memory _uri)publicdelegatedOnlyonlyClaimKeyoverridereturns (bytes32 claimRequestId)function removeClaim(bytes32 _claimId)publicdelegatedOnlyonlyClaimKeyoverridereturns(bool success)
关于Key管理的两个方法addKey
和removeKey
,可以看到都是需要有管理权限的地址(一般就是钱包地址)通过代理合约才能调用的。这两个方法是为这个身份合约添加或移除具有不同功能权限(Purpose)的地址。MANAGEMENT、ACTION是身份合约对应的主体内部来分权限来做分工; CLAIM意味着这个地址可以给本合约添加和移除Claim;addClaim
、removeClaim
一般是由身份颁发机构来调用,用户钱包地址作为管理员将Issuer的地址addKey添加为3 CLAIM,然后Issuer就可以为本合约添加和移除Claim了。
注,key Purpose 分以下4种:
1: MANAGEMENT keys, which can manage the identity 管理Identity的key
2: ACTION keys, which perform actions in this identity name
(signing, logins, transactions, etc.) 具体行为 签名、登录、交易
- 3: CLAIM signer keys, used to sign claims on other identities
which need to be revokable. 给其他identity签发claims
- 4: ENCRYPTION keys, used to encrypt data e.g. hold in claims.用来给claim里的data加密
Identity合约里还有两个重要的方法execute
和approve
,它们是身份合约与外部业务合约交互的接口,比如说与RWA合约。
先来看看execute
方法:
function execute(address _to, //要交互的合约地址uint256 _value, //发送ETH金额bytes memory _data) //函数选择器+入参ABIexternaldelegatedOnlyoverridepayablereturns (uint256 executionId)
{uint256 _executionId = _executionNonce; //在Storage.sol里定义_executions[_executionId].to = _to; //插入待执行队列_executions[_executionId].value = _value;_executions[_executionId].data = _data;_executionNonce++;emit ExecutionRequested(_executionId, _to, _value, _data);//是身份合约管理员发起的调用,直接许可执行if (keyHasPurpose(keccak256(abi.encode(msg.sender)), 1)) {approve(_executionId, true);}//调用者具有ACTION权限,并且_to不是本合约,直接许可执行else if (_to != address(this) && keyHasPurpose(keccak256(abi.encode(msg.sender)), 2)){approve(_executionId, true);}//其他情况,等待approve方法进行许可执行return _executionId;
}
execute入参里的_data
这里说明一下,这是目标函数选择器+入参的ABI编码,比如我们要调用的函数是RWA合约的subscribe(uint256 amount)方法,那么会有如下的过程使用execute进行执行:
// 1. 用abi.encodeWithSelector组装_data
bytes memory _data = abi.encodeWithSelector(IRWA.subscribe.selector, // 目标函数选择器amount // 参数);// 2. 调用IdentityProxy.execute
identity.execute(rwaAddress,0, // msg.value,如果需要ETH就写金额_data // 组装好的调用数据);
调用者调用当前身份合约的execute方法去与指定合约_to去做交互。如果是调用者是管理员MANAGEMENT权限,则直接许可,如果是ACTION权限且交互的是其他合约则许可,其他情况需要等待通过approve另行许可。
简单来说,如果_to是本合约,那么说明是要对当前身份合约做管理动作,需要是管理员,可以直接执行。
如果_to是其他合约,那么说明是以当前身份合约之名对外做具体行为,比如签名、登录、交易,那么具有ACTION权限或者管理员都可以直接执行。
其他情况,比如非管理员执行本合约管理,那么进入待许可执行队列、等待approve; 再比如与外合约交互、调用者又没有ACTION权限,也同样进入待许可执行队列、等待approve;
再来看看approve
方法:
function approve(uint256 _id, bool _approve)publicdelegatedOnlyoverridereturns (bool success){require(_id < _executionNonce, "Cannot approve a non-existing execution");require(!_executions[_id].executed, "Request already executed");//如果调用对象是本合约,需要是MANAGEMENT来许可(或拒绝)if(_executions[_id].to == address(this)) {require(keyHasPurpose(keccak256(abi.encode(msg.sender)), 1), "Sender does not have management key");}else { //如果是与外合约交互,需要ACTION来许可(或拒绝)require(keyHasPurpose(keccak256(abi.encode(msg.sender)), 2), "Sender does not have action key");}emit Approved(_id, _approve);if (_approve == true) { //许可_executions[_id].approved = true;// solhint-disable-next-line avoid-low-level-calls(success,) = _executions[_id].to.call{value:(_executions[_id].value)}(_executions[_id].data);if (success) { //如果执行成功_executions[_id].executed = true;emit Executed(_id,_executions[_id].to,_executions[_id].value,_executions[_id].data);return true;} else { //执行失败emit ExecutionFailed(_id,_executions[_id].to,_executions[_id].value,_executions[_id].data);return false;}} else { //拒绝_executions[_id].approved = false;}return false;
}
approve方法里在对当前许可调用者的权限做了判断之后,做了主要操作(success,) = _executions[_id].to.call{value:(_executions[_id].value)}(_executions[_id].data)
,_executions[_id].to
是需要交互的合约地址,call方法是在当前身份合约的approve方法里发起的,所以相当于是本身份合约调用了这个需要交互合约,转_executions[_id].value
这么多的ETH,然后执行的方法和入参是_executions[_id].data
。
好了,最后我们结合RWA的场景,总结一下身份合约的管理,以及通过execute方法作为接口、如何以身份合约去与外部合约进行交互:
- 基础身份结构
-
IdentityProxy 合约:代表投资者公司(法人身份)。
-
Key 管理:
-
MANAGEMENT Key
→ 类似董事会/法人代表的权力,能增删 key,决定身份合约本身的治理。 -
ACTION Key
→ 类似公司财务、投资经理的权力,可以对外部协议执行交易。 -
CLAIM Key
→ 给发行方(Issuer)添加/移除合规声明用。
-
- execute/approve 的逻辑
-
execute
→ 发起一笔执行请求。可能是投资操作(比如买入代币、参与流动性池、质押资产)。 -
approve
→ 对这笔请求进行许可(Approve=true 或 Revoke=false),然后实际执行交易。
关键点:
-
如果调用者是 MANAGEMENT Key → 可以直接 approve 并执行,无需额外许可。
-
如果调用者是 ACTION Key 且目标是外部合约 → 也能直接 approve 并执行。
-
如果目标是 本合约(IdentityProxy 自己) → 必须要 MANAGEMENT Key 来 approve。
- RWA 场景下的业务使用
假设 身份合约代表某公司去投资 RWA 协议(如链上基金、债券代币、地产 SPV 代币等):
例子 A:公司财务(ACTION Key)操作
-
财务或投资经理通过
execute
调用 RWA 协议的subscribe()
方法,投入 USDC 购买债券代币。 -
因为
_to
是外部合约(RWA 协议),只要财务持有 ACTION Key,execute
会自动触发approve(..., true)
并立即执行 → 资金立刻划出。 👉 所以,财务可以直接完成投资操作,不需要多签确认。
例子 B:公司内部治理调整
-
假设需要修改 IdentityProxy 的 key(比如撤销某财务的 ACTION Key,或者增加一个新的董事 KEY)。
-
这种情况属于对合约自身的调用(
_to == address(this)
)。 -
只能由 MANAGEMENT Key 来 approve 并最终执行。 👉 财务不能修改权限,必须董事会/法人代表来操作。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/912031.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!