WeIdentity智能合约

介绍

本文结合WeIdentity智能合约文档对其源码进行阅读分析。当前,WeIdentity合约层面的工作目标主要包括两部分:

  • WeIdentity DID智能合约,负责链上ID体系建立,具体包括生成DID(Distributed IDentity)、生成DID Document、DID在链上的读取与更新。
  • WeIdentity Authority智能合约,负责进行联盟链权限管理,具体包括链上DID角色的定义、操作与权限的定义与控制。

WeIdentity DID

概述

从业务视角来看,DID智能合约只需要做一件事,就是如何定义DID Document的存储结构和读写方式。DID Document的结构并不复杂(见规范文档),但在实际的业务中,存在一些挑战:

  • 伴随着接入用户(人与物)的快速增长,DID的总量将会增长迅速,规模庞大。因此,设计一个大而全的映射表是不现实的,这会带来巨大的寻址开销,即使采用传统分库、分表、跨链的思路也难以应付。
  • DID存在更新的需求。因此,每次都存储完整的Document域在更新情况下会产生大量的历史数据。

因此,WeIdentity使用Linked Event:基于事件链的存储方法来解决以上问题。

存储结构

Linked Event的核心实现思路是借助Solidity的事件(Event)机制,采用类似链表的思路对DID Document的更新进行存储和读取。在Solidity里,每个区块都有对应的Event存储区,用于对区块相关的事件进行存储,并最终存入Event log。因此,存储层面上,在不同时间点DID的更新可以存入更新时当前块的Event里,同时将当前块高作为索引记录每次更新事件。读取层面上,如果要读取完整DID Document,只需按索引反向遍历对应的块的Event里即可。基于这一思路,进行以下设计:

  • 设计一个映射记录,使用DID的地址作为索引,用来存储每个DID最近的一次更新事件所对应的块高;
  • 设计一个更新事件,用来记录每次DID更新的相关属性及前一个块高;
  • 设计一个查询函数,用来读取映射记录找到某个DID的最近的块高,以便反向解析具体的更新事件。

以上数据和逻辑会被合并到一个整体合约里。具体流程为:

  • 每当触发一次DID Document的属性更新,就记入一次更新事件,同时记录更新事件所对应的当前块高,存入整体合约的记录映射部分;
  • 记录映射部分存入整体合约的存储区,更新事件最终会存入区块链的Event;
  • 当读取DID Document时,只需通过记录映射读取块高,反向遍历对应的块的Event,解析并找到Document更新相关的事件内容,然后合并即可。

这一流程图可见于:

性能评估

使用Linked Event进行存储的优势有以下几点:

  • 非常适合更新的场景。由于Solidity Event的特性,本方案的写性能和存储开销会远远优于完整存储DID Document内容进入合约的解决方案。
  • 更方便的记录历史版本。通过记录每个事件的块高,可以快速的定位到每个事件,在溯源场景下有着广泛的应用;同时,又不需对那些未更新的属性项进行存储。
  • 读性能对更新事件是O(N)的时间增长。因此,在Document更新不频繁的场景下,读性能非常好。由于WeIdentity的DID本身更多地用来存储公钥等信息,更新频率大部分情况下并不高,因此非常适合WeIdentity的使用场景。

WeIdentity Authority

WeIdentity Authority智能合约,负责进行联盟链权限管理,具体包括链上DID角色的定义、操作与权限的定义与控制。

  • 不同的DID实体拥有不同的权限。

例如,存在Authority Issuer这一角色用来描述现实世界中的「权威凭证发行者」,它们能够发行低段位授权CPT,权限高于一般的DID;更进一步地,在Authority Issuer之上存在着委员会(Committee),它们的权限更高,包括了对Authority Issuer的治理等内容。因此,WeIdentity需要设计合理的「角色—操作」二元权限控制。

  • 权限管理的业务逻辑会随着业务迭代而不断更新。

在真实业务场景中,随着业务变化,权限管理逻辑也可能随之改变;同时,不同的业务方可能会有定制化权限管理的需求。因此,WeIdentity需要进行合理的分层设计,将数据和行为逻辑分离,在升级的情况下就只需对行为逻辑部分进行升级,数据存储保持不变,尽可能地降低更新成本。

当前,业内已经有了一些对权限进行操作和维护的开源解决方案,如ds-auth和OpenZepplin的Role智能合约;但它们的权限管理逻辑可扩展性较差且不支持合约分层更新。下文将介绍WeIdentity的Authority智能合约实现。

架构

角色与权限

当前的WeIdentity角色设计了四种角色:

  • 一般DID。一般的实体(人或物),由WeIdentity的分布式多中心的ID注册机制生成,没有特定权限。
  • Authority Issuer。授权机构,具有发行低段位授权CPT的权限。
  • Committee Member。机构委员会成员。具有管理Authority Issuer成员资格的权限。
  • Administrator。系统管理员。具有管理Committee Member及Authority Issuer成员资格的权限,未来还包括修改合约地址的权限。

每个角色具体的权限表如下:

操作一般DIDAuthority IssuerCommittee MemberAdministrator
增删改AdministratorNNNY
增删改Committee MemberNNNY
增删改Authority IssuerNNYY
发行授权CPTNYYY

合约分层

WeIdentity采用分层设计模式,即将合约分为逻辑合约、数据合约、及权限合约。

  • 逻辑合约:它专注于数据的逻辑处理和对外提供接口,通过访问数据合约获得数据,对数据做逻辑处理,写回数据合约。一般情况下,控制器合约不需要存储任何数据,它完全依赖外部的输入来决定对数据合约的访问。
  • 数据合约:它专注于数据结构的定义、数据内容的存储和数据读写的直接接口。
  • 权限合约:它专注于判断访问者的角色,并基于判断结果确定不同操作的权限。

上述架构图如下:

权限与安全管理

当前的WeIdentity权限管理的挑战是:

  • 合约在链上部署之后,攻击者可能会绕过SDK直接以DApp的形式访问合约。因此合约层面必须要有自完善的权限处理逻辑,不能依赖SDK。
  • 数据合约是公开的,因此数据合约的操作也需要进行权限管理。

WeIdentity的权限管理依赖于一个独立的RoleManager权限管理器合约,它承担了合约所有的权限检查逻辑。WeIdentity的权限粒度是基于角色和操作的二元组,这也是当前大多数智能合约权限控制的通用做法。它的设计要点包括:

  • 将角色和操作权限分别存储。
  • 设计一个权限检查函数checkPermission()供外部调用,输入参数为「地址,操作」的二元组。
  • 对角色和权限分别设计增删改函数供外部调用。
  • 所有WeIdentity的数据合约里需要进行权限检查的操作,都通过外部合约函数调用的方式,调用checkPermission()。
  • 所有WeIdentity依赖权限管理器的合约,需要有更新权限管理器地址的能力。

WeIdentity的权限管理有以下特性:

  • 优秀的可扩展性。WeIdentity的权限控制合约使用外部调用而非继承(如ds-auth和OpenZepplin的Role智能合约实现角色管理方式)方式实现。在权限控制合约升级的场景中,外部调用方案只需简单地将权限管理器合约地址更新即可,极大地提升了灵活度。
  • 使用tx.origin而非msg.sender进行调用源追踪。这是因为用户的权限和自己的DID地址唯一绑定。因此所有权限的验证必须要以最原始用户地址作为判断标准,不能单纯地依赖msg.sender。此外,WeIdentity的权限控制合约需要支持更大的可扩展性,以支持更多公众联盟链的参与成员自行实现不同的Controller。因此,需要通过tx.origin追踪到调用者的WeIdentity DID,并根据DID确定权限。

WeIdentity CPT智能合约

WeIdentity的CPT(Claim Protocol Type)合约,用于在链上存储凭证的Claim模板。CPT合约使用标准的数据-逻辑分离架构。一个数据CPT合约里,最重要的是其jsonSchema部分,它存储了以jsonSchema格式记载的Claim格式内容。区分不同CPT是通过其ID来进行的。

根据CPT使用目的、内容的不同,ID可以被划分成以下三个范围:11000(系统CPT),10002000000(授权CPT),2000000以上(普通CPT)。

系统CPT表

系统CPT的ID落在1~1000里,它们是在WeIdentity智能合约部署之初就创建好的内置CPT,用来完成所有WeIdentity实例的统一功能,它们在部署WeIdentity智能合约时,在初始化过程中部署在链上。系统CPT不支持任何角色创建。

当前,系统CPT表包括以下内容:

ID标题内容
101授权凭证某个WeID授权另一个WeID使用数据
102挑战凭证某个WeID对另一个WeID身份证明的挑战
103身份验证凭证某个WeID针对CPT102的挑战的回复
104Claim Policy某个选择性披露的Claim Policy定义
105API EndpointEndpoint端点服务的端点定义
106嵌套凭证嵌套的Credential,用来进行多签
107嵌套凭证嵌套的CredentialPojo,用来进行多签
108整合可信时间戳为某个嵌套凭证生成的可信时间戳,包含凭证原文
109可分离可信时间戳为某个嵌套凭证生成的可信时间戳,不包含凭证原文

关于每个系统CPT的详细字段要求,可以查阅代码中的 对应文件,此处不再详细展开。

授权CPT

授权CPT的ID落在1000~2000000里,如Authority合约中所述,授权CPT仅支持由Authority Issuer创建,一般是和具体的联盟链业务相关。

一般CPT

一般CPT的ID从2000000开始自增。任何WeID均可以创建此类CPT。

WeIdentity智能合约依赖关系

Evidence相关合约使用Hash实现了存证功能,可视为一个较独立部分,这里先不加以考虑。

RoleController

背景

权限

每个角色具体的权限表如下:

操作一般DIDAuthority IssuerCommittee MemberAdministrator
增删改AdministratorNNNY
增删改Committee MemberNNNY
增删改Authority IssuerNNYY
发行授权CPTNYYY

通过这里我们可知Authority Issuer的权限允许其发行授权CPT

代码分析

功能概述

核心权限判断控制合约

整体结构

//首先定义了通用的错误提示码uint constant public RETURN_CODE_FAILURE_NO_PERMISSION = 500000; //定义角色相关代号uint constant public ROLE_AUTHORITY_ISSUER = 100;uint constant public ROLE_COMMITTEE = 101;uint constant public ROLE_ADMIN = 102; //定义操作相关常数uint constant public MODIFY_AUTHORITY_ISSUER = 200;uint constant public MODIFY_COMMITTEE = 201;uint constant public MODIFY_ADMIN = 202;uint constant public MODIFY_KEY_CPT = 203;//建立角色映射mapping (address => bool) private authorityIssuerRoleBearer;mapping (address => bool) private committeeMemberRoleBearer;mapping (address => bool) private adminRoleBearer;
//构造器并赋予合约部署者相关权限function RoleController() public {authorityIssuerRoleBearer[msg.sender] = true;adminRoleBearer[msg.sender] = true;committeeMemberRoleBearer[msg.sender] = true;}

具体函数说明

// 查询某地址是否有某操作的权限function checkPermission(address addr,uint operation) public constant returns (bool) {if (operation == MODIFY_AUTHORITY_ISSUER) {if (adminRoleBearer[addr] || committeeMemberRoleBearer[addr]) {return true;}}if (operation == MODIFY_COMMITTEE) {if (adminRoleBearer[addr]) {return true;}}if (operation == MODIFY_ADMIN) {if (adminRoleBearer[addr]) {return true;}}if (operation == MODIFY_KEY_CPT) {if (authorityIssuerRoleBearer[addr]) {return true;}}return false;}
// 给某地址添加权限function addRole(address addr,uint role) public {if (role == ROLE_AUTHORITY_ISSUER) {//检查合约调用者是否有相应修改的权限if (checkPermission(tx.origin, MODIFY_AUTHORITY_ISSUER)) {authorityIssuerRoleBearer[addr] = true;}}if (role == ROLE_COMMITTEE) {if (checkPermission(tx.origin, MODIFY_COMMITTEE)) {committeeMemberRoleBearer[addr] = true;}}if (role == ROLE_ADMIN) {if (checkPermission(tx.origin, MODIFY_ADMIN)) {adminRoleBearer[addr] = true;}}}
// 删除某地址权限function removeRole(address addr,uint role) public {if (role == ROLE_AUTHORITY_ISSUER) {if (checkPermission(tx.origin, MODIFY_AUTHORITY_ISSUER)) {authorityIssuerRoleBearer[addr] = false;}}if (role == ROLE_COMMITTEE) {if (checkPermission(tx.origin, MODIFY_COMMITTEE)) {committeeMemberRoleBearer[addr] = false;}}if (role == ROLE_ADMIN) {if (checkPermission(tx.origin, MODIFY_ADMIN)) {adminRoleBearer[addr] = false;}}}
// 检查某地址是否属于某类别function checkRole(address addr,uint role) public constant returns (bool) {if (role == ROLE_AUTHORITY_ISSUER) {return authorityIssuerRoleBearer[addr];}if (role == ROLE_COMMITTEE) {return committeeMemberRoleBearer[addr];}if (role == ROLE_ADMIN) {return adminRoleBearer[addr];}}

WeIdContract

背景

角色与权限

当前的WeIdentity角色设计了四种角色:

  • 一般DID。一般的实体(人或物),由WeIdentity的分布式多中心的ID注册机制生成,没有特定权限。
  • Authority Issuer。授权机构,具有发行低段位授权CPT的权限。
  • Committee Member。机构委员会成员。具有管理Authority Issuer成员资格的权限。
  • Administrator。系统管理员。具有管理Committee Member及Authority Issuer成员资格的权限,未来还包括修改合约地址的权限。

每个角色具体的权限表如下:

操作一般DIDAuthority IssuerCommittee MemberAdministrator
增删改AdministratorNNNY
增删改Committee MemberNNNY
增删改Authority IssuerNNYY
发行授权CPTNYYY

Event事件

当被调用时,会触发参数存储到交易的日志中(区块链上的特殊数据结构)。这些日志与合约的地址关联,并合并到区块链中。日志和事件在合约内不可直接被访问,即使是创建日志的合约。

可设置indexed,来设置是否被索引。设置为索引后,可以允许通过这个参数来查找日志,甚至可以按特定的值过滤。

event WeIdAttributeChanged(address indexed identity,bytes32 key,bytes value,uint previousBlock,int updated);event WeIdHistoryEvent(address indexed identity,uint previousBlock,int created);

该合约中定义了两个事件

在createWeId函数中创建WeId时会发送两条Event事件,用来记录创建时间、auth(身份信息,包括公钥和address)、上次更新WeId对应的区块高度。保存当前区块高度到changed数组,key为address,value为当前区块高度。
setAttribute发送WeIdAttributeChanged事件用来保存DID Document信息,保存当前区块高度到changed数组。

modifier

//上述代码使用了该modifier,用于限制输入的identity必须是合约调用者本身的地址modifier onlyOwner(address identity, address actor) {require (actor == identity);_;}

测试传参过程中如果报错请查看:

https://blog.csdn.net/m0_52739647/article/details/126649846

代码分析

import

import "./RoleController.sol";

功能概述

创建或者修改DID的相关属性并用Event进行记录,可大体分为两类:创建或修改自身的属性以及有权限的机构创造或修改ID属性

整体结构

参数设置:

// 使用import中的RoleController类RoleController private roleController;//changed[identity]:存储该id最近一次发生改变的区块mapping(address => uint) changed;//记录合约建立时的区块uint firstBlockNum;//最新交易的区块uint lastBlockNum;// 计数器:记录DID的总数uint weIdCount = 0;// blockAfterLink[BlockNum]:与该区块相关联的下一个区块数mapping(uint => uint) blockAfterLink;bytes32 constant private WEID_KEY_CREATED = "created";bytes32 constant private WEID_KEY_AUTHENTICATION = "/weId/auth";

修改器(详细说明见上):

 modifier onlyOwner(address identity, address actor) {require (actor == identity);_;}

构造器:

//需要roleControllerAddress的合约地址function WeIdContract(address roleControllerAddress)public{//将RoleController按照指定地址构造,这样方便后续RoleController合约更新升级roleController = RoleController(roleControllerAddress);//初始化firstBlockNum与lastBlockNumfirstBlockNum = block.number;lastBlockNum = firstBlockNum;}

构造Event事件:

event WeIdAttributeChanged(address indexed identity,bytes32 key,bytes value,uint previousBlock,int updated);event WeIdHistoryEvent(address indexed identity,uint previousBlock,int created);

get函数:

  • getLatestRelatedBlock(address identity):查询与该ID相关的最新区块
  • getFirstBlockNum():查询首区块数
  • getNextBlockNumByBlockNum(uint currentBlockNum):查询与该区块相关联的下一个区块
  • getLatestBlockNum() :查询最新区块数
  • getWeIdCount():查询ID总数
  • isIdentityExist(address identity) :ID是否存在

具体函数说明

//创建该地址自己的IDfunction createWeId(address identity,bytes auth,bytes created,int updated) public//使用modifier,用于限制输入的identity必须是合约调用者本身的地址onlyOwner(identity, msg.sender){//ID属性变动事件WeIdAttributeChanged,created与updated是一些身份信息(包括公钥和address)WeIdAttributeChanged(identity, WEID_KEY_CREATED, created, changed[identity], updated);//基本同上WeIdAttributeChanged(identity, WEID_KEY_AUTHENTICATION, auth, changed[identity], updated);//更新 changed[identity]、blockAfterLink[lastBlockNum]、lastBlockNumchanged[identity] = block.number;if (block.number > lastBlockNum) {blockAfterLink[lastBlockNum] = block.number;}WeIdHistoryEvent(identity, lastBlockNum, updated);if (block.number > lastBlockNum) {lastBlockNum = block.number;}//计数器+1weIdCount++;}
//有权限的机构创造IDfunction delegateCreateWeId(address identity,bytes auth,bytes created,int updated)public{// 检查权限等级,合约交互者是否是授权机构,没有要求identity必须是合约交互者地址// 即在判断其权限之后,可以创造其他地址的IDif (roleController.checkPermission(msg.sender, roleController.MODIFY_AUTHORITY_ISSUER())) {WeIdAttributeChanged(identity, WEID_KEY_CREATED, created, changed[identity], updated);WeIdAttributeChanged(identity, WEID_KEY_AUTHENTICATION, auth, changed[identity], updated);changed[identity] = block.number;if (block.number > lastBlockNum) {blockAfterLink[lastBlockNum] = block.number;}WeIdHistoryEvent(identity, lastBlockNum, updated);if (block.number > lastBlockNum) {lastBlockNum = block.number;}weIdCount++;}}
//更改自身的ID属性function setAttribute(address identity, bytes32 key, bytes value, int updated) public //限制更改自身id的属性onlyOwner(identity, msg.sender){WeIdAttributeChanged(identity, key, value, changed[identity], updated);changed[identity] = block.number;}
//有权限的机构更改ID属性function delegateSetAttribute(address identity,bytes32 key,bytes value,int updated)public{if (roleController.checkPermission(msg.sender, roleController.MODIFY_AUTHORITY_ISSUER())) {WeIdAttributeChanged(identity, key, value, changed[identity], updated);changed[identity] = block.number;}}

AuthorityIssuerData

背景

权限

每个角色具体的权限表如下:

操作一般DIDAuthority IssuerCommittee MemberAdministrator
增删改AdministratorNNNY
增删改Committee MemberNNNY
增删改Authority IssuerNNYY
发行授权CPTNYYY

通过这里我们可知Authority Issuer的权限允许其发行授权CPT

合约分层

其实从项目整体结构分析就不难发现,WeIdentity采用分层设计模式,即将合约分为逻辑合约、数据合约、及权限合约。

  • 逻辑合约:它专注于数据的逻辑处理和对外提供接口,通过访问数据合约获得数据,对数据做逻辑处理,写回数据合约。一般情况下,控制器合约不需要存储任何数据,它完全依赖外部的输入来决定对数据合约的访问。
  • 数据合约:它专注于数据结构的定义、数据内容的存储和数据读写的直接接口。
  • 权限合约:它专注于判断访问者的角色,并基于判断结果确定不同操作的权限。

所以该部分是涉及合约的数据分层部分SpecificIssuerData CommitteeMemberData AuthorityIssuerData CptData与之同理

此类合约专注于数据结构的定义、数据内容的存储和数据读写的直接接口。

代码分析

import

//导入权限管理合约import "./RoleController.sol";

功能概述

实现授权机构的创建删除以及认证

整体结构

// Error codesuint constant private RETURN_CODE_SUCCESS = 0;uint constant private RETURN_CODE_FAILURE_ALREADY_EXISTS = 500201;uint constant private RETURN_CODE_FAILURE_NOT_EXIST = 500202;uint constant private RETURN_CODE_NAME_ALREADY_EXISTS = 500203;uint constant private RETURN_CODE_UNRECOGNIZED = 500204;struct AuthorityIssuer {//AuthorityIssuer的一些属性// [0]: name, [1]: desc, [2-11]: extra stringbytes32[16] attribBytes32;// [0]: create date, [1]: update date, [2-11]: extra int// [15]: flag for recognition status (0: unrecognized, 1: recognized)int[16] attribInt;bytes accValue;}//authorityIssuerMap[addr]:相应地址对应的AuthorityIss结构mapping (address => AuthorityIssuer) private authorityIssuerMap;// 授权机构的地址队列address[] private authorityIssuerArray;//uniqueNameMap[name]:相应名字映射的地址mapping (bytes32 => address) private uniqueNameMap;//计数器:已认证的机构数uint recognizedIssuerCount = 0;RoleController private roleController;

构造器:

//引入RoleController合约功能同上 function AuthorityIssuerData(address addr) public {roleController = RoleController(addr);}

Get函数:

  • getRecognizedIssuerCount():返回已认证的授权机构数
  • getAddressFromName(bytes32 name):根据名字返回地址
  • isNameDuplicate(bytes32 name):判断名字是否重复
  • isAuthorityIssuer(address addr) :是否为授权机构
  • getAuthorityIssuerInfoAccValue(address addr):根据地址获取其AuthorityIssuer的AccValue属性
  • getAuthorityIssuerInfoNonAccValue(address addr):根据地址获取其AuthorityIssuer部分属性
  • getAuthorityIssuerFromIndex(uint index):根据序号获得其授权机构地址
  • getDatasetLength():获得授权机构总数

具体函数说明

//判断是否有此AuthorityIssuer,即其地址是否在authorityIssuerMap中存在function isAuthorityIssuer(address addr) public constant returns (bool) {//结合权限合约进行AuthorityIssuer权限判断if (!roleController.checkRole(addr, roleController.ROLE_AUTHORITY_ISSUER())) {return false;}//检查该地址映射的AuthorityIssuer数据结构的name是否为空,为空则返回falseif (authorityIssuerMap[addr].attribBytes32[0] == bytes32(0)) {return false;}return true;}
//添加新的授权机构function addAuthorityIssuerFromAddress(address addr,bytes32[16] attribBytes32,int[16] attribInt,bytes accValue)publicreturns (uint){//addr对应的AuthorityIssuer结构中attribBytes32属性的首位不为0,则说明其authorityIssuer已经存在//从中也可以发现,该结构中的Name属性十分重要if (authorityIssuerMap[addr].attribBytes32[0] != bytes32(0)) {return RETURN_CODE_FAILURE_ALREADY_EXISTS;}//判断使用的名字有没有重复if (isNameDuplicate(attribBytes32[0])) {return RETURN_CODE_NAME_ALREADY_EXISTS;}// Actual Role must be granted by calling recognizeAuthorityIssuer()// roleController.addRole(addr, roleController.ROLE_AUTHORITY_ISSUER());//生成新的authorityIssuer并加入队列,改变相应的属性AuthorityIssuer memory authorityIssuer = AuthorityIssuer(attribBytes32, attribInt, accValue);authorityIssuerMap[addr] = authorityIssuer;authorityIssuerArray.push(addr);uniqueNameMap[attribBytes32[0]] = addr;return RETURN_CODE_SUCCESS;}
// 给授权机构添加相应权限(即认证) function recognizeAuthorityIssuer(address addr) public returns (uint) {if (!roleController.checkPermission(tx.origin, roleController.MODIFY_AUTHORITY_ISSUER())) {return roleController.RETURN_CODE_FAILURE_NO_PERMISSION();}if (authorityIssuerMap[addr].attribBytes32[0] == bytes32(0)) {return RETURN_CODE_FAILURE_NOT_EXIST;}// Set role and flagroleController.addRole(addr, roleController.ROLE_AUTHORITY_ISSUER());recognizedIssuerCount = recognizedIssuerCount + 1;authorityIssuerMap[addr].attribInt[15] = int(1);return RETURN_CODE_SUCCESS;}
//删除授权机构的相应权限(即去认证)function deRecognizeAuthorityIssuer(address addr) public returns (uint) {if (!roleController.checkPermission(tx.origin, roleController.MODIFY_AUTHORITY_ISSUER())) {return roleController.RETURN_CODE_FAILURE_NO_PERMISSION();}// Remove role and flagroleController.removeRole(addr, roleController.ROLE_AUTHORITY_ISSUER());recognizedIssuerCount = recognizedIssuerCount - 1;authorityIssuerMap[addr].attribInt[15] = int(0);return RETURN_CODE_SUCCESS;}
//删除授权机构function deleteAuthorityIssuerFromAddress(address addr) public returns (uint){if (authorityIssuerMap[addr].attribBytes32[0] == bytes32(0)) {return RETURN_CODE_FAILURE_NOT_EXIST;}if (!roleController.checkPermission(tx.origin, roleController.MODIFY_AUTHORITY_ISSUER())) {return roleController.RETURN_CODE_FAILURE_NO_PERMISSION();}roleController.removeRole(addr, roleController.ROLE_AUTHORITY_ISSUER());if (authorityIssuerMap[addr].attribInt[15] == int(1)) {recognizedIssuerCount = recognizedIssuerCount - 1;}uniqueNameMap[authorityIssuerMap[addr].attribBytes32[0]] = address(0x0);delete authorityIssuerMap[addr];uint datasetLength = authorityIssuerArray.length;for (uint index = 0; index < datasetLength; index++) {if (authorityIssuerArray[index] == addr) { break; }} if (index != datasetLength-1) {authorityIssuerArray[index] = authorityIssuerArray[datasetLength-1];}delete authorityIssuerArray[datasetLength-1];authorityIssuerArray.length--;return RETURN_CODE_SUCCESS;}

CptData

背景

Authority Issuer

通过上述权限设置可知,Authority Issuer的权限允许其发行授权CPT

Claim Protocol Type(CPT)注册机制

Claim Protocol Type:凭证的声明类型

不同的Issuer按业务场景需要,各自定义不同类型数据结构的Claim,所有的Claim结构都需要到CPT合约注册,以保证全网唯一。所有的CPT定义文件(JSON-LD格式)可以从CPT合约下载。

{ "$context" : "http://json-schema.org/draft-04/schema#", "cptType" : "original", "description" : "学历证书说明", "title" : "学历证书", "type" : "object", "properties" : { "id" : { "description" : "学生证id", "type" : "string" }, "name" : { "description" : "名字", "type" : "string" } } }

其中CPT为模板类,定义了Claim包含的数据字段及各字段属性要求。Claim为CPT的实例。Issuer将Claim进行签名,即可生成Credential。

还有一些例子可参考:https://weidentity.readthedocs.io/zh_CN/latest/docs/cpt-templates.html#cpt-templates

代码分析

功能概述

该合约实现了CPT的构建

import

import "./AuthorityIssuerData.sol";

整体结构

// CPT ID has been categorized into 3 zones: 0 - 999 are reserved for system CPTs,//1000-2000000 for Authority Issuer's CPTs, and the rest for common WeIdentiy DIDs.uint constant public AUTHORITY_ISSUER_START_ID = 1000;uint constant public NONE_AUTHORITY_ISSUER_START_ID = 2000000;uint private authority_issuer_current_id = 1000;uint private none_authority_issuer_current_id = 2000000;AuthorityIssuerData private authorityIssuerData;//签名 struct Signature {uint8 v; bytes32 r; bytes32 s;}//CPT结构属性struct Cpt {//store the weid address of cpt publisheraddress publisher;// [0]: cpt version, [1]: created, [2]: updated, [3]: the CPT IDint[8] intArray;// [0]: descbytes32[8] bytes32Array;//store json schemabytes32[128] jsonSchemaArray;//store signatureSignature signature;}mapping (uint => Cpt) private cptMap;//用于存储CPT ID的List,由于不同的CPT ID根据不同的分类创建并不连续,这里的List起汇总所有ID的作用uint[] private cptIdList;

构造器:

function CptData(address authorityIssuerDataAddress) public{authorityIssuerData = AuthorityIssuerData(authorityIssuerDataAddress);}

一些Get函数:

  • 根据CptId获取相应cpt以及相应属性值(与cpt结构一一对应)

    • getCpt(uint cptId):根据cptId获取相应的Cpt
    • getCptPublisher(uint cptId)
    • getCptIntArray(uint cptId)
    • getCptJsonSchemaArray(uint cptId)
    • getCptBytes32Array(uint cptId)
    • getCptSignature(uint cptId)
  • isCptExist(uint cptId) :判断是否存在此CptId

  • getDatasetLength():当前Cpt的总数

  • getCptIdFromIndex(uint index):通过索引值获取CptId

  • getCptId(address publisher)

具体函数说明

//输入所需属性构建相应的CPTfunction putCpt(uint cptId, address cptPublisher, int[8] cptIntArray, bytes32[8] cptBytes32Array,bytes32[128] cptJsonSchemaArray, uint8 cptV, bytes32 cptR, bytes32 cptS) public returns (bool) {//构建签名Signature memory cptSignature = Signature({v: cptV, r: cptR, s: cptS});//构建cptcptMap[cptId] = Cpt({publisher: cptPublisher, intArray: cptIntArray, bytes32Array: cptBytes32Array, jsonSchemaArray:cptJsonSchemaArray, signature: cptSignature});cptIdList.push(cptId);return true;}
// 分配cptid索引function getCptId(address publisher)publicconstant returns (uint cptId){// 判断是否存是授权机构if (authorityIssuerData.isAuthorityIssuer(publisher)) {while (isCptExist(authority_issuer_current_id)) {authority_issuer_current_id++;}cptId = authority_issuer_current_id++;// 获得可用的cptid索引if (cptId >= NONE_AUTHORITY_ISSUER_START_ID) {cptId = 0;}} else {// 不是授权机构则直接从2000000开始分配idwhile (isCptExist(none_authority_issuer_current_id)) {none_authority_issuer_current_id++;}cptId = none_authority_issuer_current_id++;}}

CptController

功能概述

实现 cpt 的注册、查找与更新并进行记录

import

import "./CptData.sol";import "./WeIdContract.sol";import "./RoleController.sol";

整体功能

// Error codesuint constant private CPT_NOT_EXIST = 500301;uint constant private AUTHORITY_ISSUER_CPT_ID_EXCEED_MAX = 500302;uint constant private CPT_PUBLISHER_NOT_EXIST = 500303;uint constant private CPT_ALREADY_EXIST = 500304;uint constant private NO_PERMISSION = 500305;// 默认的 CPT 版本int constant private CPT_DEFAULT_VERSION = 1;WeIdContract private weIdContract;RoleController private roleController;// 为合约持有者预留address private internalRoleControllerAddress;address private owner;// CPT和Policy数据的存储地址address private cptDataStorageAddress;address private policyDataStorageAddress;

构造器:

function CptController(address cptDataAddress,address weIdContractAddress) public{owner = msg.sender;weIdContract = WeIdContract(weIdContractAddress);cptDataStorageAddress = cptDataAddress;}

事件:

event RegisterCptRetLog(uint retCode, uint cptId, int cptVersion);event UpdateCptRetLog(uint retCode, uint cptId, int cptVersion);

一些Get函数:

  • getCptDynamicIntArray
  • getCptDynamicBytes32Array
  • getCptDynamicJsonSchemaArray
  • getPolicyIdList
  • getCptIdList
  • getTotalCptId()
  • getTotalPolicyId()

具体函数说明

//设置policy数据合约地址function setPolicyData(address policyDataAddress)public{//如果合约调用者地址不为合约构建者或者其policyData为空,则直接返回if (msg.sender != owner || policyDataAddress == 0x0) {return;}否则的话将policyDataStorageAddress赋值为policyDataAddresspolicyDataStorageAddress = policyDataAddress;}
//设置权限控制合约地址function setRoleController(address roleControllerAddress)public{if (msg.sender != owner || roleControllerAddress == 0x0) {return;}roleController = RoleController(roleControllerAddress);if (roleController.ROLE_ADMIN() <= 0) {return;}internalRoleControllerAddress = roleControllerAddress;}
//指定CPTID构建CPT并进行登记function registerCptInner(uint cptId,address publisher, int[8] intArray, bytes32[8] bytes32Array,bytes32[128] jsonSchemaArray, uint8 v, bytes32 r, bytes32 s,address dataStorageAddress)privatereturns (bool){//判断是否存在该DIDif (!weIdContract.isIdentityExist(publisher)) {//如果不存在就日志中记录“不存在”RegisterCptRetLog(CPT_PUBLISHER_NOT_EXIST, 0, 0);return false;}//从相应地址获取数据CptData cptData = CptData(dataStorageAddress);if (cptData.isCptExist(cptId)) {//根据id判断是否存在该CPT,并打印日志RegisterCptRetLog(CPT_ALREADY_EXIST, cptId, 0);return false;}// 权限检查,我们在这里使用tx.origin进行相应操作。// 对于SDK调用,publisher和tx.origin通常是相同的,对于DApp调用,tx.origin应该规定uint lowId = cptData.AUTHORITY_ISSUER_START_ID();uint highId = cptData.NONE_AUTHORITY_ISSUER_START_ID();if (cptId < lowId) {// 委员会成员创建// 首先检查初始化if (internalRoleControllerAddress == 0x0) {RegisterCptRetLog(NO_PERMISSION, cptId, 0);return false;}// 检查权限if (!roleController.checkPermission(tx.origin, roleController.MODIFY_AUTHORITY_ISSUER())) {RegisterCptRetLog(NO_PERMISSION, cptId, 0);return false;}} else if (cptId < highId) {// 授权机构创建if (internalRoleControllerAddress == 0x0) {RegisterCptRetLog(NO_PERMISSION, cptId, 0);return false;}// 检查权限if (!roleController.checkPermission(tx.origin, roleController.MODIFY_KEY_CPT())) {RegisterCptRetLog(NO_PERMISSION, cptId, 0);return false;}}intArray[0] = CPT_DEFAULT_VERSION;//构建CptcptData.putCpt(cptId, publisher, intArray, bytes32Array, jsonSchemaArray, v, r, s);//日志记录RegisterCptRetLog(0, cptId, CPT_DEFAULT_VERSION);return true;}
//cpt ID通过系统获取function registerCptInner(address publisher, int[8] intArray, bytes32[8] bytes32Array,bytes32[128] jsonSchemaArray, uint8 v, bytes32 r, bytes32 s,address dataStorageAddress) private returns (bool) {if (!weIdContract.isIdentityExist(publisher)) {RegisterCptRetLog(CPT_PUBLISHER_NOT_EXIST, 0, 0);return false;}CptData cptData = CptData(dataStorageAddress);//使用getCptId获得当前将符合条件的下一个id索引uint cptId = cptData.getCptId(publisher); if (cptId == 0) {RegisterCptRetLog(AUTHORITY_ISSUER_CPT_ID_EXCEED_MAX, 0, 0);return false;}int cptVersion = CPT_DEFAULT_VERSION;intArray[0] = cptVersion;cptData.putCpt(cptId, publisher, intArray, bytes32Array, jsonSchemaArray, v, r, s);RegisterCptRetLog(0, cptId, cptVersion);return true;}
//更新cptfunction updateCptInner(uint cptId, address publisher, int[8] intArray, bytes32[8] bytes32Array,bytes32[128] jsonSchemaArray, uint8 v, bytes32 r, bytes32 s,address dataStorageAddress) private returns (bool) {// 判断是否存在该DIDif (!weIdContract.isIdentityExist(publisher)) {UpdateCptRetLog(CPT_PUBLISHER_NOT_EXIST, 0, 0);return false;}// 获取数据CptData cptData = CptData(dataStorageAddress);// 检查权限if (!roleController.checkPermission(tx.origin, roleController.MODIFY_AUTHORITY_ISSUER())&& publisher != cptData.getCptPublisher(cptId)) {UpdateCptRetLog(NO_PERMISSION, 0, 0);return false;}// 检查该CPT是否存在if (cptData.isCptExist(cptId)) {int[8] memory cptIntArray = cptData.getCptIntArray(cptId);// cpt版本更新int cptVersion = cptIntArray[0] + 1;intArray[0] = cptVersion;int created = cptIntArray[1];intArray[1] = created;// cpt更新并记录cptData.putCpt(cptId, publisher, intArray, bytes32Array, jsonSchemaArray, v, r, s);UpdateCptRetLog(0, cptId, cptVersion);return true;} else {UpdateCptRetLog(CPT_NOT_EXIST, 0, 0);return false;}}
//查找功能function queryCptInner(uint cptId,address dataStorageAddress) private constant returns (address publisher, int[] intArray, bytes32[] bytes32Array,bytes32[] jsonSchemaArray, uint8 v, bytes32 r, bytes32 s){CptData cptData = CptData(dataStorageAddress);publisher = cptData.getCptPublisher(cptId);intArray = getCptDynamicIntArray(cptId, dataStorageAddress);bytes32Array = getCptDynamicBytes32Array(cptId, dataStorageAddress);jsonSchemaArray = getCptDynamicJsonSchemaArray(cptId, dataStorageAddress);(v, r, s) = cptData.getCptSignature(cptId);}

凭证模板存储的相关函数

//credentialTemplateStored[cptId]:cptID创建时的区块数mapping (uint => uint) credentialTemplateStored;// 定义事件event CredentialTemplate(uint cptId,bytes credentialPublicKey,bytes credentialProof);// 添加模板function putCredentialTemplate(uint cptId,bytes credentialPublicKey,bytes credentialProof)public{CredentialTemplate(cptId, credentialPublicKey, credentialProof);credentialTemplateStored[cptId] = block.number;}//根据id 获得模板对应区块数function getCredentialTemplateBlock(uint cptId)publicconstantreturns(uint){return credentialTemplateStored[cptId];}// --------------------------------------------------------// Claim Policy storage belonging to v.s. Presentation, Publisher WeID, and CPT//claimPoliciesFromPresentation[presentationClaimMapId]:其对应的Claim Policy ID listmapping (uint => uint[]) private claimPoliciesFromPresentation;mapping (uint => address) private claimPoliciesWeIdFromPresentation;//claimPoliciesFromCPT[cptId]:其对应的Claim Policy ID Listmapping (uint => uint[]) private claimPoliciesFromCPT;uint private presentationClaimMapId = 1;function putClaimPoliciesIntoPresentationMap(uint[] uintArray) public {claimPoliciesFromPresentation[presentationClaimMapId] = uintArray;claimPoliciesWeIdFromPresentation[presentationClaimMapId] = msg.sender;RegisterCptRetLog(0, presentationClaimMapId, CPT_DEFAULT_VERSION);presentationClaimMapId ++;}// 根据presentationId返回相应映射function getClaimPoliciesFromPresentationMap(uint presentationId) public constant returns (uint[], address) {return (claimPoliciesFromPresentation[presentationId], claimPoliciesWeIdFromPresentation[presentationId]);}function putClaimPoliciesIntoCptMap(uint cptId, uint[] uintArray) public {claimPoliciesFromCPT[cptId] = uintArray;RegisterCptRetLog(0, cptId, CPT_DEFAULT_VERSION);}// 根据cptID获得映射function getClaimPoliciesFromCptMap(uint cptId) public constant returns (uint[]) {return claimPoliciesFromCPT[cptId];}

Evidence相关合约

WeIdentity不仅提供了基于DID的公钥存储 + 数字签名用来防止凭证被篡改,同时也提供了Evidence存证功能,基于区块链不可篡改的特性,为创建出的凭证增信。简单来说,任何使用者,都可以将凭证的内容摘要上传到链上,以便在未来使用时可以根据链上内容比对,以防篡改。内容摘要使用Hash算法,抗逆向反推。其具体涉及以下三个合约,这里就不再展开:

  • Evidence
  • EvidenceContract
  • EvidenceFactory

其他类似合约

由于合约采用分层设计模式,即将合约分为逻辑合约、数据合约、及权限合约。其他合约与之类似这里简要提及。

CommitteeMember

  • CommitteeMemberController:与上述 AuthorityIssuerController 十分类似,实现CommitteeMember相关操作
  • CommitteeMemberData:与上述 AuthorityIssuerData 十分类似,实现CommitteeMember数据的添加、删除操作

SpecificIssuer(Issuer链上类型声明)

WeIdentity支持为每位Authority Issuer在链上声明所属类型,即Specific Issuer。您可以指定某位Authority Issuer的具体类型属性,如学校、政府机构、医院等。当前,此属性与其对应的权限没有直接关系,仅作记录之目的。

  • SpecificIssuerController
  • SpecificIssuerData

参考资料:WeIdentity 智能合约设计与实现