一、概述
1.设计名称:区块链上安全且隐私保护的投票系统
2.实验目的:本实验课程是计算机、网络安全、软件工程等专业学生的一门专业课程,通过实验,帮助学生更好地掌握数据隐私保护与区块链相关概念、技术、原理、应用等;通过实验提高学生编写实验报告、总结实验结果的能力;使学生对区块链模型、智能合约、隐私保护算法等有比较深入的认识。要掌握的知识点如下:
1)掌握区块链中涉及的相关概念、模型、算法;
2)熟悉智能合约原理、开发与测试的流程;
3)熟悉常用的数据隐私保护方法。
3.编程语言:Solidity、JavaScript
二、基本要求
1.实验前,复习《数据隐私保护技术》与《区块链技术》课程中的有关内容。
2.准备好实验数据,编程完成实验内容,收集实验结果。
3.独立完成实验报告。
三、实验内容:
基于区块链智能合约实现多方参与的YES/NO投票,并在参与方本地对数据进行隐私保护。
1 实现智能合约投票协议
编写支持多方参与的智能合约投票协议
(可选)支持参与方注册,由发起方审核注册
(可选)可指定投票开始与结束时间
2 支持参与方对自身数据进行隐私保护
采用随机响应(randomized response)对YES/NO投票进行理论严格的差分隐私保护
3 发起方对隐私保护的投票结果进行无偏解码
利用随机响应的无偏估计方法得到投票结果
4 测试和评估系统
测试准确性和效率
四、实验过程和摘要
1. 原理
电子投票系统
的一个主要问题是如何分配合理的权限给正确的人,并且要防止篡改。首先要为投票设立一个简称创建一个合约,发起者作为主席来给每一个独立的地址分配权限。每一个参与者可以自己投票或者委托给信任的人。程序最后会返回得票数最多的那个提议。
其次需要保证投票统计数据库的查询结果不会受到任何单一用户的隐私数据的影响。采用中心化差分隐私
,ε-差分隐私(ε-DP)
的定义如下:
这里的随机函数 A 是运行在服务器上面的,本地用户不需要运行任何差分隐私算法。ε
可以看做是隐私预算,它用来量化一个用户隐私泄露的风险。ε
的值越大,隐私泄露的风险就越大,反之,ε
越小,隐私泄露的风险就越小。
本实验的扰动方案对输入数据进行扰动,加入噪音,使其满足 ε-差分隐私。对于输入数据扰动的典型方案就是随机响应
, 对于输出数据扰动的典型方案就是拉普拉斯算法
。
拉普拉斯算法在真实投票选项f(D)的基础上加上符合拉普拉斯分布的噪音。噪音 η
的取值公式为:
其中 GS_f 的是函数 f 的 GS(global sensitivity)。GS_f 取值为:
对于拉普拉斯算法,他的 mean
为0,``variance
是 2
∗ (GS_f)2。variance 越小,说明处理之后的数据集的 utility
越高。但是,从 variance
的公式我们可以得知 variance 和 ε 成反比,也就是说 ε
和 utility
成反比。因此,单纯的追求隐私保护或高utility
都会损坏另外一方。在实际需求中,我们需要寻找一个平衡点
。
2. 代码
代码构成
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ └── src //前端界面代码
│ ├── App.js
│ ├── Ballot.js
│ ├── HeadBar.js
│ ├── Home.js
│ ├── contracts
│ │ ├── Election.json
│ │ └── Migrations.json
│ ├── getWeb3.js //合约交互
│ ├── index.js
│ ├── serviceWorker.js
│ └── theme.js
├── contracts
│ ├── Election.sol //合约主体代码
│ └── Migrations.sol
├── migrations //部署合约代码
│ ├── 1_initial_migration.js
│ └── 2_deploy_contracts.js
├── truffle-box.json //Truffle依赖文件
├── truffle-config.js
└── yarn.lock
主体实验代码
// 从所有的提案中找出最大投票数的提案,作为最终的方案。
pragma solidity >=0.4.22;
contract Myballot{
//投票人
struct Voter{
bool voted; //是否投票
uint weight; //权重
uint8 vote_id; //投给哪个提案id,就是数组下标
address delegate; //投票权委托人
}
//提案
struct Proposal{
string name; //提案名字
string content; //提案内容
uint voteCount; //被投票数
bool enable; //启动
}
//主席
address public chairperson;
//可变长度数组,放置所有提案,下标与提案id对应,不用mapping的原因是方便遍历。
Proposal[] public proposals;
//当前的提案数
uint public nowNumProposal;
//Uint8的最大值
uint public UINT8_MAX_VALUE = (2**8)-1;
//投票人映射
mapping(address=>Voter) public voters;
constructor(uint _chairpersonWight) public{
//设置主席的初始参数
chairperson = msg.sender;
voters[chairperson].weight = _chairpersonWight;
//定义提案数组长度
// +1 是因为默认值0的数组位置不给用,从1开始
proposals.length = UINT8_MAX_VALUE + 1;
}
//提案申请
function applyProposal(string _proposalName, string _proposalContent) public{
//检查提案数量是否已满
require(nowNumProposal < UINT8_MAX_VALUE, "提案已满");
//提案数加1
++nowNumProposal;
//录入提案信息
proposals[nowNumProposal].name = _proposalName;
proposals[nowNumProposal].content = _proposalContent;
proposals[nowNumProposal].enable = true;
}
//仅主席可调用
modifier Onlychairperson{
require(msg.sender == chairperson, "仅主席可操作");
_;
}
//检查某人是否还有投票权
modifier haveVoted(address someone){
require(voters[someone].voted == false, "投票权已使用");
_;
}
//授权投票人
function authorizedVote(address _voter, uint _weight) public Onlychairperson haveVoted(_voter){
require(_weight >= 0 && _weight <= 100, "投票权重范围超出");
require(voters[_voter].weight == 0, "只允许赋权一次");
voters[_voter].weight = _weight;
}
//代理投票
function delegateVoting(address _to) public haveVoted(msg.sender){
//重点!!!
//代理会出现多重代理,要使用while找到目的地
require(_to != msg.sender, "不能将代理权给自己"); //设置后所有人都不可能自循环
//循环的目的:找到多重代理的最终者
//继续迭代条件(while条件):
//①代理者的下一个代理人存在 voters[_to].delegate != address(0)
//②不能代理回之前的代理人(造成环)若成环那么环中的人一定会调回到自己,后面的判断就生效 voters[_to].delegate != msg.sender
//③不能自循环(上面已保证)
while(voters[_to].delegate != address(0) && voters[_to].delegate != msg.sender)
_to = voters[_to].delegate; //迭代,将代理人的代理变为下一个代理_to
require(voters[_to].delegate != msg.sender, "不可循环代理");
//表示代理
voters[msg.sender].delegate = _to;
if(voters[_to].voted){
//如果代理人已经投过票了,那么就把权重累加到已投的提案
proposals[voters[_to].vote_id].voteCount += voters[msg.sender].weight;
voters[msg.sender].vote_id = voters[_to].vote_id;
}else{
//没投那么就把权重交给代理人
voters[_to].weight += voters[msg.sender].weight;
}
voters[msg.sender].voted = true;
}
//投票函数
function vote(uint8 _proposalID) public haveVoted(msg.sender){
require(_proposalID >= 0 && _proposalID <= UINT8_MAX_VALUE, "投票提案ID范围超出");
require(proposals[_proposalID].enable, "提案未开启");
proposals[_proposalID].voteCount += voters[msg.sender].weight;
voters[msg.sender].vote_id = _proposalID;
voters[msg.sender].voted = true;
}
//当前获胜提案
function winningProposal() public view returns(string name, uint maxProposalID, uint maxVoteCount){
require(nowNumProposal > 0, "当前无提案");
for(uint i = 1; i <= nowNumProposal; i++){
if(proposals[i].voteCount > maxVoteCount){
maxVoteCount = proposals[i].voteCount;
maxProposalID = i;
name = proposals[i].name;
}
}
require(maxVoteCount > 0, "当前无提案被投票");
}
}
3.实验步骤
-
主体合约代码编写
编写主体合约的代码,包括设置提案,设置投票票数以及提案。例如
编写如下主体代码:
struct Voter{
bool voted; //是否投票
uint weight; //权重
uint8 vote_id; //投给哪个提案id,就是数组下标
address delegate; //投票权委托人
}
代表委托投票信息。 -
将随机响应的机制应用于智能合约
运用随机响应的机制,使用js语言实现智能合约的随机响应(randomized response)对YES/NO投票进行理论严格的差分隐私保护。 -
合约编译部署
使用Truffle编译部署合约,并产生1_initial_migration.js
与2_deploy_contracts.js
文件供后续提供ABI接口,ABI(Application Binary Interface)用于连接合约层与应用层。监听8545端口用于metamask
的本地钱包,与此同时创建出10个以太链上的账户,用于测试执行投票程序。
4. 图形化界面设计并与合约连接
使用react编写投票前台程序,用于构成用户界面进行投票操作。操纵Web3的接口以连接合约层。
5.合约Dapp部署
使用Node.js 建立开启服务器,部署Dapp合约。映射监听端口。
- 投票响应测试
测试投票功能是否成功,是否能够在以太链上产生一笔交易。
五、实验结果分析
程序使用步骤如下:
- 安装需要的依赖
- Npm
想要取得安装信息 , 从 https://nodejs.org/en/ 获取 - Metamask
一个加密的区块链钱包, 从 https://metamask.io 获取 - Yarn(可选)
Yarn是一个Javascript的包管理工具,其修补了许多NPM的缺陷。
-
使用Truffle打包合约并创建测试账户
-
启动前台页面渲染
-
启动metamask并进行投票
-
发起投票方公布投票结果