基于VNTchain实现的学历认证系统

VNTchain安装和测试

gvnt安装说明
官方示例代码
一个成功案例

环境

Window11的Ubuntu子系统

问题及解决办法

vnt相关的库的安装问题


问题描述
// \A23\deploys\deploy-czqq.js
var fs = require('fs');
var Vnt = require("vnt")
var vntkit = require("vnt-kit")
var Tx = require("ethereumjs-tx")

需要安装vnt和vnt-kit
提示:直接安装vnt.j和vnt-kit.js会很慢,建议下载这两个仓库的压缩包之后解压,再用npm安装:
运行deploy-czqq.js时报错如下:

internal/modules/cjs/loader.js:1188
  return process.dlopen(module, path.toNamespacedPath(filename));
                 ^

Error: /root/A23/vnt-kit.js-master/node_modules/scrypt/build/Release/scrypt.node: invalid ELF header
    at Object.Module._extensions..node (internal/modules/cjs/loader.js:1188:18)
    at Module.load (internal/modules/cjs/loader.js:986:32)
    at Function.Module._load (internal/modules/cjs/loader.js:879:14)
    at Module.require (internal/modules/cjs/loader.js:1026:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at Object.<anonymous> (/root/A23/vnt-kit.js-master/node_modules/scrypt/index.js:3:20)
    at Module._compile (internal/modules/cjs/loader.js:1138:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
    at Module.load (internal/modules/cjs/loader.js:986:32)
    at Function.Module._load (internal/modules/cjs/loader.js:879:14)
原因分析

根据github上的一个issue:

If you are using scrypt.hash, it is now available natively in the Node.js crypto module.
If you are using scrypt.kdf / scrypt.verifyKdf, scrypt-kdf is a close-to-direct replacement.

scrypt已经不再维护,可以用node自带的crypto加密模块替代。

解决方案

将加密所用的库scrypt替换成crypto,具体在\vnt-kit.js\node_modules\scrypt.js\node.js文件,替换为以下内容,crypto为原生node自带因此不用安装:

// var scrypt = require('scrypt')
const crypto = require('crypto')

function hash(key, salt, n, r, p, dklen, progressCb) {
  // return scrypt.hashSync(key, { N: n, r: r, p: p }, dklen, salt)
  try{
    return crypto.scryptSync(key,salt,dklen,{ N: n, r: r, p: p ,maxmem:300000000})
  }
  catch(e){
    console.log('e: ', e);    
  }  
}

module.exports = hash

部署合约时余额不足问题


问题描述

运行deploy-czqq.js时报错如下:

Error: insufficient funds for gas * price + value
    at Object.InvalidResponse (/root/A23/vnt.js-master/lib/vnt/errors.js:38:16)
    at /root/A23/vnt.js-master/lib/vnt/requestmanager.js:86:36
    at XMLHttpRequest.request.onreadystatechange (/root/A23/vnt.js-master/lib/vnt/httpprovider.js:127:7)
    at XMLHttpRequest.dispatchEvent (/root/A23/node_modules/xhr2/lib/xhr2.js:76:20)
    at XMLHttpRequest._setReadyState (/root/A23/node_modules/xhr2/lib/xhr2.js:422:14)
    at XMLHttpRequest._onHttpResponseEnd (/root/A23/node_modules/xhr2/lib/xhr2.js:616:14)
    at IncomingMessage.<anonymous> (/root/A23/node_modules/xhr2/lib/xhr2.js:568:23)
    at IncomingMessage.emit (events.js:327:22)
    at endReadableNT (_stream_readable.js:1221:12)
    at processTicksAndRejections (internal/process/task_queues.js:84:21)
原因分析&解决方案

余额不足,这是由于初始化的时候没有给node分配余额。
按照官方文档中关于搭建本地hubble网络的说明:

在此基础上修改以上数据,就能替换成我们私有网络的配置,周期和见证人数量不变,只修改初始见证人p2p地址和账号即可,然后把修改后的文件保存到testnet目录

实际上除了将witnessesUrlwitnesses这两个属性替换以外,还需要替换alloc中对应的节点地址,可见为每个节点分配的余额是0x200000000000000000000000000000000000000000000000000000000000000

无法收到部署合约交易的收据问题


问题描述

运行deploy-czqq.js时,已经生成transaction hash,但程序一直卡在getTransactionReceipt,无法获得交易的收据,进而无法得到合约地址

原因分析&解决方案

仅仅解锁了4个节点的账户,没有开启区块打包,进而无法将合约上链。
按照官方文档中关于打包区块的说明:

在每个初始见证人节点上解锁账户、开启共识,可以看到区块不断产生

对每个节点执行:

personal.unlockAccount(core.coinbase, "账户密码", 3153600000) // 3153600000指解锁的秒数,这段时间之后,账户会自动锁住
bp.start()

示例代码运行成功

deploy-czqq.js:

transaction hash:  0x50737e24768f61a46f3b0ced25c9ea4d488aa9d75f08a540c735bea26045c66e
tx receipt:  {
  blockHash: '0xced0f79698829153dd98308e12f55a58094391f05c17a4af50b8605db769d176',
  blockNumber: 20,
  contractAddress: '0x2827619f23f57c03179a038bbd05166f02049a6a',
  cumulativeGasUsed: 710721,
  from: '0x7dff077a604de5c60776216af0214617d7be6748',
  gasUsed: 710721,
  logs: [],
  logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
  status: '0x1',
  to: null,
  transactionHash: '0x50737e24768f61a46f3b0ced25c9ea4d488aa9d75f08a540c735bea26045c66e',
  transactionIndex: 0
}
tx status:  0x1
contract address:  0x2827619f23f57c03179a038bbd05166f02049a6a

test-czqq.js:

result: zgc|1.wav|1234567890|A05AE118ACA9D80252C209B2A0A4CD0058825CBDE867A756C8733CB21E8D3566|QmcHxt9scMKmD4xyCAmA7frg7LcAyZgF4kFnUm6DBmNZvN|all|999999999999999-
result: 0x7Dff077a604De5c60776216AF0214617d7BE6748|
transaction hash:  0xacf31f82b1048ff9bf85af32d8e33978db8419b0c819378bb27c74746a990707

系统实现

合约的编写、部署和测试

合约中变量的类型只能是vntlib.h中定义的,参见,用KEY标识变量可使其持久存在于链上,在本合约中有:

持久变量说明
schemas证书模板结构体构成的列表
universities大学结构体构成的列表
certificates证书结构体构成的列表
certificate_num当前证书数数量
university_num当前大学数量
schema_num当前证书模板数量

定义以下函数用于查询和更新数据,用MUTABLE标识表示该函数不改变链的状态,UNMUTABLE表示会改变状态:

合约函数功能是否改变链的状态
issueCertificate签发证书
registerUniversity注册大学
queryUniversityProfileByName通过名称查询大学信息
queryCertificateSchema查询证书模板(指明了需要hash加密的属性)
queryCertificateByUUID通过UUID查询证书
getAllCertificateByStudent查询学生的所有证书
getAllCertificateByUniversity查询大学的所有证书

vnt.js与合约交互时需要注意:

  1. 当不涉及链状态改变,仅查询时,用下面方式交互:
function queryAll(universityPK){
    var contract = vnt.core.contract(abi).at(contractAddress);
    //queryAll是c语言合约中的一个函数名
    var r = contract.queryAll.call(universityPK,{from: account.address});
    console.log('result:', r.toString());
    return r.toString();    
}
  1. 当涉及多个参数需要传递,要提前打包:
var contract = vnt.core.contract(abi);
//registerUniversity是函数名,后面的列表是函数需要的参数
var data = contract.packFunctionData("registerUniversity", [name, publicKey, location, description]);

  1. 当涉及链状态改变,用下面方式交互。原本在合约中为此类有状态改变的函数也设置了返回值,但是没有找到获取返回值的方法,只能通过在函数调用完毕以后,执行回调函数,在回调函数中执行一次查询,获得数据更新结果:
function sendRawTransaction(account, to, data, value ,cb) {
    var nonce = vnt.core.getTransactionCount(account.address);
    var options = {
        nonce: nonce,
        to: to,
        gasPrice: vnt.toHex(vnt.toWei(18, 'Gwei')),
        gasLimit: vnt.toHex(4000000),
        data: data,
        value: value,
        chainId: chainid
    };
    var tx = new TX(options);
    tx.sign(Buffer.from(
        account.privateKey.substring(
            2,
        ),
        'hex'));
    var serializedTx = tx.serialize();
    vnt.core.sendRawTransaction(
        '0x' + serializedTx.toString('hex'), function (err, txHash) {
            if (err) {
                console.log('err happened: ', err);
                console.log('transaction hash: ', txHash);
            } else {
                cb()
                console.log('transaction hash: ', txHash);
            }
        });
}

// 注册新的大学
function registerUniversity(name, publicKey, location, description,callback=(r)=>{
    console.log('r: ', r);
}){
    var contract = vnt.core.contract(abi);
    var data = contract.packFunctionData("registerUniversity", [name, publicKey, location, description]);
    sendRawTransaction(account, contractAddress, data, vnt.toHex(0),()=>{
        //交易完成以后执行查询函数,获得执行结果
        var r = queryUniversityProfileByName(name);
        //该回调函数处理查询结果
        callback(r.toString());
    }) 
}

变量类型嵌套编译问题


问题描述

vntchain/bottle编译C语言合约时,出现以下报错:

panic: runtime error: index out of range

goroutine 1 [running]:
github.com/vntchain/bottle/core.createStructList(0x6, 0x7f06c00bf540, 0x1, 0x7f06c000b260, 0x2, 0x7f06c00bf0f0, 0x1, 0x7f06c000b260)
        /root/go/src/github.com/vntchain/bottle/core/clang.go:184 +0x2c65
github.com/vntchain/bottle/core.cmd.func1(0x6, 0x7f06c00bf540, 0x1, 0x7f06c000b260, 0x2, 0x7f06c00bf0f0, 0x1, 0x7f06c000b260, 0xc400000001)
        /root/go/src/github.com/vntchain/bottle/core/clang.go:70 +0xcd
github.com/vntchain/bottle/vendor/github.com/go-clang/bootstrap/clang.GoClangCursorVisitor(0x6, 0x7f06c00bf540, 0x1, 0x7f06c000b260, 0x2, 0x7f06c00bf0f0, 0x1, 0x7f06c000b260, 0xc420274768, 0x50)
//···省略
原因分析&解决方案

结合报错信息core.createStructList,说明是结构体位置出错,经过排查,原代码:

typedef struct{
    string certificateType; // 证书类型
    string id;     // 版本
    array(string) ordering;      // 属性及顺序,参与merkle tree验证
    string dataType;   // 数据类型(schema)  
} schema;
KEY array(schema) schemas;//存储不同版本的待hash属性模板

改为:

KEY array(struct{
            string certificateType; // 证书类型
            string id;     // 版本
            array(string) ordering;      // 属性及顺序,参与merkle tree验证
            string dataType;   // 数据类型(schema)
        }) schemas;//存储不同版本的待hash属性模板

这样能够通过编译。结论:虽然上面两种形式功能一致,虽然官方文档也说明可以复杂嵌套,但是或许这样的嵌套不适用于typedef中。

编码问题


问题描述

报错如下:

panic: Unsupport keyword MUTABLE

goroutine 1 [running]:
github.com/vntchain/bottle/core.isConstant(0xc42017a2fe, 0x8, 0xc420c3f040)
        /root/go/src/github.com/vntchain/bottle/core/compile.go:532 +0x147
github.com/vntchain/bottle/core.(*abiGen).parseMethod(0xc4207eb9d0)
        /root/go/src/github.com/vntchain/bottle/core/compile.go:279 +0x193
github.com/vntchain/bottle/core.compile(0xc420292f20, 0x0, 0xc420292f20)
        /root/go/src/github.com/vntchain/bottle/core/cmd.go:219 +0x33e
//省略...
原因分析&解决方案

代码中能够正常导入库文件,但是无法识别MUTABLE,将整个文件内容替换成已经正常编译的代码之后仍然无法识别,说明是编码的问题。原始代码用vs code编写,重新创建文件,将内容用Visual Studio打开,写入同样内容后,可以正常编译,说明两个编辑器编码格式存在差异,造成无法编译。

WEB开发

参考
该项目为express框架开发,使用hyperledger-fabric(超级账本)。拟用vue.js基于vnt-chain改写,一方面基于vnt-chain做调整,另一方面进一步熟悉系统原理。

系统概述

在这里插入图片描述

差异

hyperledger-fabric:(通过文件路径)获得网络的证书授权服务器(CA),在本地客户端(WEB应用)创建一个管理员用户,作用是注册用户,管理员用户向CA发出请求后,获得由CA颁发的证书和公私钥对,存入本地钱包。然后再创建应用程序用户:需要管理员用户的签名并向CA发出请求,成功以后获得证书和公私钥对,存入钱包,后面就可以用这些应用程序用户调用合约。

vnt-chain:认证过程没有上面复杂(或者说还没有找到完善的认证方式)。本应用中仅通过节点的地址和密码解锁节点,在连接区块链网络以后即可调用合约。

merkle tree验证过程

本系统中验证证书是否被篡改的过程用到了merkle tree。主要目的是为了快速比对两个大规模数据是否一致:
在这里插入图片描述

全体数据被分成若干块,经过层层hash得到最顶层的根hash。在本系统中将['大学', 'GPA', '学院', '专业']这四个属性值作为叶子节点(可以在合约中的schema中设置这些可选属性),用户可以选择其中的一个或多个属性进行验证。假设要验证K节点,只需计算下图中的红色节点的hash值,减少了计算量:
在这里插入图片描述
在代码中,用区块链上的证书数据生成merkle tree1,再通过下面的方式获得叶子节点的证明,即上述红色节点(除K以外)的hash值,以数组的形式返回。

let multiProof = mTree.getHexMultiProof(paramsToShareIndex);

另外还需要该merkle tree对应的原始数据以及该证书的UUID作为下一步验证所需的参数。为了生成一个便于管理的加密字符串,将三者用AES再次加密:

let result = {
    proof: mTreeProof,
    disclosedData: disclosedData,
    certUUID: req.query.certUUID
};
// Encrypt
let ciphertext = CryptoJS.AES.encrypt(JSON.stringify(result), config.vnt.secretPassword).toString()

需要验证时,解密,获得merkle tree证明、原始数据和UUID。用UUID在数据库中查询该证书的数据,生成merkle tree2,用原始数据的hash和merkle tree证明(必要节点的hash)计算merkle tree1根节点hash,再和merkle tree1根节点的hash比较即可验证是否被篡改。

置换交易定价过低问题


问题描述

多次交易后出现:

Error: replacement transaction underpriced
    at Object.InvalidResponse (/root/A23/vnt.js-master/lib/vnt/errors.js:38:16)
    at /root/A23/vnt.js-master/lib/vnt/requestmanager.js:86:36
    at request.onreadystatechange (/root/A23/vnt.js-master/lib/vnt/httpprovider.js:127:7)
    at XMLHttpRequest.dispatchEvent (/root/A23/node_modules/xhr2/lib/xhr2.js:76:20)
    at XMLHttpRequest._setReadyState (/root/A23/node_modules/xhr2/lib/xhr2.js:422:14)
    at XMLHttpRequest._onHttpResponseEnd (/root/A23/node_modules/xhr2/lib/xhr2.js:616:14)
    at IncomingMessage.<anonymous> (/root/A23/node_modules/xhr2/lib/xhr2.js:568:23)
    at IncomingMessage.emit (node:events:525:35)
    at endReadableNT (node:internal/streams/readable:1359:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

原因分析&解决方案

以太坊实战-再谈nonce使用陷阱

本系统中发送交易的函数如下:

function sendRawTransaction(account, to, data, value ,cb) {
    var nonce = vnt.core.getTransactionCount(account.address);
    var options = {
        nonce: nonce,
        to: to,
        gasPrice: vnt.toHex(vnt.toWei(18, 'Gwei')),
        gasLimit: vnt.toHex(4000000),
        data: data,
        value: value,
        chainId: chainid
    };
    var tx = new TX(options);
    tx.sign(Buffer.from(
        account.privateKey.substring(
            2,
        ),
        'hex'));
    var serializedTx = tx.serialize();
    vnt.core.sendRawTransaction(
        '0x' + serializedTx.toString('hex'),function (err, txHash) {
            cb(err, txHash)
        }); 
}

可见每次发送交易的nonce是根据当前已经完成的交易数来取值的,但是由于某些异常,有些交易并没有完成,而是处于搁置状态,并未计入当前已完成交易总数,这时如果再以相同nonce发送一个新的交易,只有覆盖相同nonce的交易才能交易成功,但这需要更高的gasPrice,而在代码中gasPrice是不变的,因此会报错置换交易定价过低

这个问题主要出现在调试过程中,因此重新搭建一个本地链之后问题得到解决,没有进一步深究更科学的nouce管理方式。

效果展示

大学、学生注册:
在这里插入图片描述
在这里插入图片描述
颁发证书:
在这里插入图片描述
查看证书:
在这里插入图片描述
分享证书:
在这里插入图片描述

生成证明:
在这里插入图片描述
验证:
在这里插入图片描述
验证通过:
在这里插入图片描述
通过数据库修改绩点:
在这里插入图片描述
再次验证,验证失败:
在这里插入图片描述

最后

参考:

  1. gvnt安装说明
  2. 官方示例代码
  3. VNTChain入门指南
  4. Academic Certificates on the Blockchain (Hyperledger Fabric)

项目地址:VNTchain-Certificate-Managemer

复现过程中出现问题可以通过github issue或者通过邮箱rongdongyang@mail.nwpu.edu.cn

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值