1. Introduction
Voting is a core mechanism on Kambria. The ability to elect experts, vote on issues, signal preferences and development direction allows the community to drive innovation across various verticals. In this post, we discuss the implementation details of KambriaVote, an on-chain voting mechanism created for Kambria. KambriaVote is an improvement of CarbonVote.
2. Use Case Study
As a first use case of KambriaVote, users can vote for their favorite challenges (candidates) for the hackathons (voting campaigns) that Kambria will organize towards the end of this year.
2.1 Mechanics
- A user (account with KAT test tokens, referred to as KATT) can join any open voting campaign and vote for one candidate inside that campaign.
- To vote, a user casts all the balance of KATT as a ballot.
- Ballots are not equal and are weighted. The weight of a ballot is the balance of KATT. E.g. a user with 10 KATT will have a higher-weighted ballot than a user with 5 KATT. This means the more KATT a user holds, the more impact his/her vote will be on the outcome.
- If the user votes on another candidate in the same campaign, the smart contract will move the ballot of that user over to the other candidate.
- A user can change their vote but only vote for one candidate in a campaign.
- A user can vote across multiple campaigns.
2.2 How votes are counted
The weight of a single ballot will change if the balance of KATT in the account changes during the voting period. The running total of weighted ballots will be dynamically reflected on the current vote count in an open campaign. When voting ends, only the current balance of KATT in an account will be used for the weighted ballot. For example, if a user owns 10 KAT when he/she first voted but spent 2 KAT, at the end of the voting period he/she will only contribute an 8-weighted ballot (remaining balance) to the voted candidate. This ensures no single user can “double-vote”.
3. Technical Implementation
3.1 Class and Sequence UML
3.2 Moderator Smart ContractModerator manages and publishes voting campaigns (ballots). It is deployed on-chain once and manages the lifetime of multiple voting campaigns.
3.3 Ballot Smart Contract
Ballot is a smart contract that represents a single voting campaign. A ballot contract is deployed by the moderator with the following inputs:
- MODERATOR: the moderator address.
- RATE: 10^RATE makes the basic unit of a vote.
- CIDS: a list of candidate IDs.
- START: the block number for the start of the campaign.
- END: the block number for the end of the campaign.
- SCID: the index of a candidate in CIDS.
Vote is an action where you cast your weighted-ballot to one candidate by providing the ballot contract with the candidate’s SCID. As mentioned in Section 2, your weighted-ballot is the current balance of KATT in your account.
To vote, the user computes the amount of WEI from the SCID of the candidate and sends that to the ballot contract address.
- amount to send to contract = SCID X 10^RATE WEI
Example:
To vote for the candidate X with SCID = 3 at ballot contract address 0xef75e34c50c1b109fe65ee696f12225de508b9f2 with RATE = 12. We compute the amount to send is 3 X 10¹² WEI = 0.000003 ETH. Note that this amount of ETH is only used to indicate the candidate and will be refunded immediately. The only thing the user pays is the usual transaction fee.
function() payable mustBeVoting { uint256 value = msg.value; msg.sender.transfer(msg.value);
uint256 vote = value.div(RATE); require(vote >= 0 && vote < CANDIDATE_IDENTITIES.length); votes[msg.sender] = CANDIDATE_IDENTITIES[vote]; emit VoteFor(msg.sender, votes[msg.sender]); }
In the code above, msg.sender.transfer(msg.value)
refunds the ETH. Only the value is used to calculate the SCID and record it into the mapping votes[msg.sender] = CANDIDATE_IDENTITIES[vote]
.
The mapping records the address of a vote, so addresses are unique within a campaign but can be used across multiple campaigns.
To change the vote, a user can send another transaction with the new vote and it will replace the prior vote from the same address.
It is also possible for a user to “un-vote”, revoking his/her vote. The first way is simply transferring all the KAT test tokens to another address that have not yet voted. This means you are basically posting a zero-weighted ballot to a candidate. The second way is to send a vote with SCID = 0. By default, we use CID = 0x in CIDS to indicate NULL candidate, so you can send a transaction along with 0 ETH to un-vote. However, in some special cases, this may change so be sure to check beforehand.
The ‘mustBeVoting’ flag indicates that the campaign is opened or closed, votes/un-votes that come in after a campaign is closed will not be honored.
To end a voting campaign, a user with moderator privilege will set END
or 0
to the current block number from the moderator contract.
4. KambriaVote vs CarbonVote
4.1 Security
In CarbonVote, every candidate in a voting campaign is a contract. So to run a 10 candidate campaign, one needs to deploy 10 contracts on the Ethereum network.
We define {Ballot, Candidate, Real-World-Mapping } (BCR) as the semantic relationship between a ballot and its candidates as well as the mapping of candidates to real-world components, e.g. a candidate maps to a codebase repo.
The lack of an on-chain moderator means CarbonVote cannot maintain BCR on-chain. Grouping of candidates and candidates to real-world component mappings will all be stored off-chain which is susceptible to attacks.
KambriaVote solves this by putting BCR on-chain. Candidates of a voting campaign share a common ballot contract address. Doing so, we preserve the {Ballot, Candidate} part of BCR. We can construct the {Candidate, Real-World-Mapping} relationship by using the hash of a codebase repo’s KDNAas the CID.
If an adversary wants to attack the BCR, it can.
1) Destroy the {Ballot, Candidate} relationship, which means successfully attacking the blockchain or,
2) Destroy the {Candidate, Real-World-Mapping} relationship which means to inverse the CID which we defined above as the cryptographic hash of the candidate’s KDNA, or change the KDNA which will then change CID as well.
Both attacks are very unlikely to happen.
4.2 Gas used
Does increasing security increase the cost of KambriaVote?
Let’s recap: CarbonVote uses one contract for each candidate while KambriaVote uses one ballot contract for one voting campaign. The table below compares the gas used between CarbonVote and KambriaVote in a typical voting campaign.
CarbonVote may be cheaper for campaigns with a small number of candidates but in the long run, KambriaVote will be more economical.
4.3 Voiding a vote
To unvote/void a vote in CarbonVote, one needs to move all the ETH to another address. That means the ballot casted is still there but it is now zero-weighted. Technically this is ok but practically, it may cause some problems. Big organizations usually use multisig or cold wallets. Moving ETH is a multi-step process that involves 2 or more parties and may involve creating another multisig wallet which means deploying another contract. That is why we estimate this operation will cost at least 21.000 or more gas in our cost comparison in section 4.2.
In KambriaVote, we support the CarbonVote way of voiding a vote because sometimes it is useful, e.g. to lower the weight of your ballot. But we also provided a second, more economical way to void a void. Which is to cast your ballot for the zero-indexed void-candidate (0x). This will effectively void your ballot cast. We estimate this operation to cost 30.016 gas.
5. Future work
5.1 Privacy
We understand in some cases voters may not want to be identified by their addresses when they vote. We propose two approaches to solve privacy in KambriaVote.
Approach A uses Differential Privacy. We can update the voting mechanism to be a 2-step process.
- Step 1: Toss a coin, if heads, you would vote correctly (defined as a true-vote). If tails, you choose to either vote correctly or vote incorrectly (defined as a false-vote).
- Step 2: Repeat Step 1.
The probability of heads or tails is 50%. With heads, a true-vote is 100% and false-vote is 0%. With tails, true-vote is 50% and false-vote is 50% which is a Bernoulli(0.5) distribution.
We have:
true-vote = 0.5 * 1 + 0.5 * 0.5 = 0.75
false-vote = 0.5 * 0 + 0.5 * 0.5 = 0.25
Assume that in a 2-candidate voting campaign, candidate C1 got 100 votes and candidate C2 got 160. How do we translate this to the final vote counts for C1 and C2? Let x, y be the final votes of C1, C2 respectively.
According to our distribution, out of the 100 votes for C1, 0.75 are true-votes and 0.25 are false-votes. Similarly, for the 160 votes C2 received.
So:
true-vote-for-C1 = 0.75 * 100
false-vote-for-C1 = 0.25 * 100
true-vote-for-C2 = 0.75 * 160
false-vote-for-C2 = 0.25 * 160
then,
x = 0.75 * 100 + 0.25 * 160 = 115
y = 0.75 * 160 + 0.25 * 100 = 145
Therefore, we can announce that C2 won with 145 final vote counts and C1 received 115 final vote counts. In this scheme, we can ensure that Voter V voted but we don’t know if they tossed heads or tails, so we are unable to deduce who they voted for, thus anonymity is preserved.
Approach B is slightly more complicated. We can implement a zkSNARKs-enabled voting system. Recall that zkSNARKS is a protocol to satisfy the requirement: Prover P can prove Verifier V that P knows the solution S to a riddle R, without revealing S or any knowledge which V can later use to gain an advantage to find the solution S.
In KambriaVote, we allow voters to secretly cast their votes and ensure no one other than the candidate knows who voted for them. We allow the candidate to know exactly how many votes they received and who voted for him/her. In this case, a candidate is P and other candidates are V. P has the incentive to protect their own voters’ identities so they don’t lose them to V. Every candidate will act as P and V in our voting scheme. At the end of the voting and verifying rounds, every V will know the result of the votes (which is cryptographically verified) but not the identity of the voters.
Phan Sơn Tự, Kambria Blockchain Engineer
Tingxi Tan, Kambria CTO and Co-founder
Visit the Kambria platform at app.kambria.io. And learn more about the tech behind the Kambria platform by visiting the Kambria blog.