本文阅读需要有一点Solidity的基础,但不需要是高手,跟着本文讲解去看就可以看懂和理解。
RWA不同于DiFi、 Dex那种资产交易都整个的在链上全闭环的生态,现实资产通证化,参与的人当然也需要做现实世界的认证,不能是全匿名的。比如说做实名认证、合格投资者认证、反洗钱等等。是需要链下认证跟链上身份进行结合的。所以需要把链下身份跟链上的钱包地址能关联起来,这样跟RWA合约交互的时候就可以满足合规要求。
一、概要
ONCHAINID是一套链上数字身份的Solidity智能合约框架,面向合规应用场景,由 Tokeny 开发,用来把“链下身份(护照、KYC认证等等)”和“链上地址”关联起来。可以通过将Identity合约关联到链上钱包地址、并让权威Issuer颁发Claim给到Identity合约的方式,实现对链上身份的认证,用于KYC\\AML等场景。在RWA广泛应用的ERC-3643的T-REX实现,就是使用的ONCHAINID来做身份验证的。
二、目录结构
ONCHAINID这套合约的目录和合约源代码文件如下:
.
├── ClaimIssuer.sol
├── Identity.sol
├── Test.sol
├── _testContracts
│ └── VerifierUser.sol
├── factory
│ ├── IIdFactory.sol
│ └── IdFactory.sol
├── gateway
│ └── Gateway.sol
├── interface
│ ├── IClaimIssuer.sol
│ ├── IERC734.sol
│ ├── IERC735.sol
│ ├── IIdentity.sol
│ └── IImplementationAuthority.sol
├── proxy
│ ├── IdentityProxy.sol
│ └── ImplementationAuthority.sol
├── storage
│ ├── Storage.sol
│ └── Structs.sol
├── verifiers
│ └── Verifier.sol
└── version
└── Version.sol
我们先总揽一下,这套合约主要分以下这么几个部分:
IERC734.sol ,IERC735.sol是基础协议接口,定义oid合约具有“Key Holder”和“Claim Holder”的功能。Claim Holder比较好理解,身份合约肯定需要一些身份认证标签,这就需要认证机构颁发Claim给到这个oid合约;oid合约有时候不仅仅是关联到一个钱包地址就完了,还需要设置一些角色权限,比如这个钱包地址代表的是一个公司或者项目,那么就需要分权限来多个钱包一起管理,Key Holder是在oid合约里定义并存放这些公钥Key的功能。
IIdentity.sol, Identity.sol是oid的主体部分,一般说oid合约就是指的这个合约。
Storage.sol, Structs.sol是Identity合约的存储,业务逻辑与状态变量的存储分离是设计可升级可约的常用技巧,可以在存储合约预留的gap变量存储里直接追加而不会打乱合约的StorageLayout,实现合约字段扩充。而字段扩充和合约逻辑的改写,实际上是重新生成了一个新合约,地址会变的,这时候就用到代理合约模式,后面会提到。
IClaimIssuer.sol, ClaimIssuer.sol是特殊的Identity,指的是颁发认证的机构。这个合约内部存储撤销的Claim,以及实现了撤销方法、还有验证Claim是否可信的方法。
Verifier.sol代表一组规则逻辑,里边定义了合规必须的Claim以及对应的可信Claim,然后用verify(address identity)去校验Identity是否满足身份验证的标准。属于是一个业务规则逻辑模块。比如“合格投资者”,那这个肯定是要对应这个投资者Identity需要取得哪些issuer颁发的哪些claim,那么给定的Identity,我们需要判断每个requiredClaimTopics这个Identity是否都有、然后这个claim topic是不是规定的issuer发的。这个校验逻辑对应的主要方法就是verify(address identity)。
ImplementationAuthority.sol,IImplementationAuthority.sol,IdentityProxy.sol这些就是上面提到过的代理合约模式的实现,ImplementationAuthority就是个存放业务逻辑实现合约Identity的地址的容器,IdentityProxy.sol是代理合约,构造函数初始化的时候会传入ImplementationAuthority从而可以获得实现合约,通过调用代理合约来路由到实现合约对应的方法。这里用的是fallback()模式,调用代理合约时、方法名字用的是实现合约的方法,但代理合约里没有实现这些方法,所以统一会走到fallback()方法里,在fallback()方法里用assembly做delegatecall路由调用对应实现合约的方法逻辑。
Gateway.sol、IdFactory.sol、IIdFactory.sol是整套合约的部署框架,Gateway是外部部署调用的入口,网关通过调用IdFactory的方法实现对整套合约的部署调用和集成。
接下来,我准备从部署时候的初始化开始,从整体到细节带大家熟悉一下这套合约。
三、部署初始化逻辑讲解
我们先看下Gateway.sol里边的代码,我把我讲解需要的相关的代码列一下:
//谁可以调用Gateway来进行部署
mapping(address => bool) public approvedSigners;
//Gateway创建的时候,指定关联的IdFactory,以及指定有权限部署的地址名单
constructor(address idFactoryAddress, address[] memory signersToApprove) Ownable() {…}
//为identityOwner创建一个IdentityProxy合约
function deployIdentityWithSalt(
address identityOwner, //Identity是给谁的
string memory salt, //自定义盐,用于Create2
uint256 signatureExpiry, //签名失效时间
bytes calldata signature //部署所需的签名,必须是approvedSigners
) external returns (address) {
if (identityOwner == address(0)) {
revert ZeroAddress();
}
if (signatureExpiry != 0 && signatureExpiry < block.timestamp) {
revert ExpiredSignature(signature);
}
//用签名和签名内容hash、来恢复signer
address signer = ECDSA.recover(
keccak256(
abi.encode(
\”Authorize ONCHAINID deployment\”,
identityOwner,
salt,
signatureExpiry
)
).toEthSignedMessageHash(),
signature
);
if (!approvedSigners[signer]) { //signer是否被许可
revert UnapprovedSigner(signer);
}
if (revokedSignatures[signature]) { // 到撤销签名数组里检查一下签名是不是已撤销的
revert RevokedSignature(signature);
}
return idFactory.createIdentity(identityOwner, salt); //调用工厂合约进行部署
}
可以看到,主要是维护部署管理员白名单,然后调用IdFactory进行部署、为制定的identityOwner创建IdentityProxy合约。
我们接着看IdFactory里的逻辑。
constructor (address implementationAuthority) {
require(implementationAuthority != address(0), \”invalid argument – zero address\”);
_implementationAuthority = implementationAuthority;
}
function createIdentity(address _wallet, string memory _salt) external onlyOwner override returns (address) {
require(_wallet != address(0), \”invalid argument – zero address\”);
require(keccak256(abi.encode(_salt)) != keccak256(abi.encode(\”\”)), \”invalid argument – empty string\”);
string memory oidSalt = string.concat(\”OID\”,_salt);
require (!_saltTaken[oidSalt], \”salt already taken\”); //salt已经用过了,身份合约不能重复创建
require (_userIdentity[_wallet] == address(0), \”wallet already linked to an identity\”); //钱包地址已经关联过IdentityProxy合约了
ad


