WeIdentity智能合约源码分析

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-events.png

性能评估

使用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成员资格的权限,未来还包括修改合约地址的权限。

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

操作 一般DID Authority Issuer Committee Member Administrator
增删改Administrator N N N Y
增删改Committee Member N N N Y
增删改Authority Issuer N N Y Y
发行授权CPT N Y Y Y

合约分层

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

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

上述架构图如下:

authority-contract-arch.png

权限与安全管理

当前的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的挑战的回复
104 Claim Policy 某个选择性披露的Claim Policy定义
105 API Endpoint Endpoint端点服务的端点定义
106 嵌套凭证 嵌套的Credential,用来进行多签
107 嵌套凭证 嵌套的CredentialPojo,用来进行多签
108 整合可信时间戳 为某个嵌套凭证生成的可信时间戳,包含凭证原文
109 可分离可信时间戳 为某个嵌套凭证生成的可信时间戳,不包含凭证原文

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

授权CPT

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

一般CPT

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

WeIdentity智能合约依赖关系

contractdep.jpg

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

RoleController

背景

权限

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

操作 一般DID Authority Issuer Committee Member Administrator
增删改Administrator N N N Y
增删改Committee Member N N N Y
增删改Authority Issuer N N Y Y
发行授权CPT N Y Y Y

通过这里我们可知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成员资格的权限,未来还包括修改合约地址的权限。

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

操作 一般DID Authority Issuer Committee Member Administrator
增删改Administrator N N N Y
增删改Committee Member N N N Y
增删改Authority Issuer N N Y Y
发行授权CPT N Y Y Y

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与lastBlockNum
        firstBlockNum = 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是否存在

具体函数说明

//创建该地址自己的ID	
	function 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]、lastBlockNum
        changed[identity] = block.number;
        if (block.number > lastBlockNum) {
            blockAfterLink[lastBlockNum] = block.number;
        }
        WeIdHistoryEvent(identity, lastBlockNum, updated);
        if (block.number > lastBlockNum) {
            lastBlockNum = block.number;
        }
        //计数器+1
        weIdCount++;
    }

//有权限的机构创造ID
function delegateCreateWeId(address identity,bytes auth,bytes created,int updated)public{
        // 检查权限等级,合约交互者是否是授权机构,没有要求identity必须是合约交互者地址
        // 即在判断其权限之后,可以创造其他地址的ID
        if (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

背景

权限

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

操作 一般DID Authority Issuer Committee Member Administrator
增删改Administrator N N N Y
增删改Committee Member N N N Y
增删改Authority Issuer N N Y Y
发行授权CPT N Y Y Y

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

合约分层

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

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

authority-contract-arch.png

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

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

代码分析

import

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

功能概述

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

整体结构

    // Error codes
    uint 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 string
        bytes32[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是否为空,为空则返回false
        if (authorityIssuerMap[addr].attribBytes32[0] == bytes32(0)) {
            return false;
        }
        return true;
    }
//添加新的授权机构
function addAuthorityIssuerFromAddress(
        address addr,
        bytes32[16] attribBytes32,
        int[16] attribInt,
        bytes accValue
    )
        public
        returns (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 flag
        roleController.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 flag
        roleController.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合约下载。

image-20221006110030563

{
   "$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 publisher
        address publisher;
        // [0]: cpt version, [1]: created, [2]: updated, [3]: the CPT ID
        int[8] intArray;
        // [0]: desc
        bytes32[8] bytes32Array;
        //store json schema
        bytes32[128] jsonSchemaArray;
        //store signature
        Signature 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)

具体函数说明

//输入所需属性构建相应的CPT
function 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});
        //构建cpt
        cptMap[cptId] = Cpt({publisher: cptPublisher, intArray: cptIntArray, bytes32Array: cptBytes32Array, jsonSchemaArray:cptJsonSchemaArray, signature: cptSignature});
        cptIdList.push(cptId);
        return true;
    }
// 分配cptid索引
function getCptId(address publisher)  public  constant 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开始分配id
            while (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 codes
    uint 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赋值为policyDataAddress
        policyDataStorageAddress = 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
    )
        private
        returns (bool)
    {
    	//判断是否存在该DID
        if (!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;
        
        //构建Cpt
        cptData.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;
    }

//更新cpt
function 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) 
    {
    	// 判断是否存在该DID
        if (!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
    )
        public
        constant
        returns(uint)
    {
        return credentialTemplateStored[cptId];
    }

    // --------------------------------------------------------
    // Claim Policy storage belonging to v.s. Presentation, Publisher WeID, and CPT
    
    //claimPoliciesFromPresentation[presentationClaimMapId]:其对应的Claim Policy ID list
    mapping (uint => uint[]) private claimPoliciesFromPresentation;
    mapping (uint => address) private claimPoliciesWeIdFromPresentation;
    //claimPoliciesFromCPT[cptId]:其对应的Claim Policy ID List
    mapping (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 智能合约设计与实现

猜你喜欢

转载自blog.csdn.net/m0_52739647/article/details/127231026