Election Voting on Corda

September 08, 2021

Electronic voting has been making headlines. Would it be possible to one day run secure elections using blockchain and other distributed ledger technologies?

Let’s have a look at how you could implement an Electronic Voting Machine (EVM) system using Corda. Then, we can play with some sample code.

The example architecture consists of a series of district voting systems and an observing node network. The observer node makes the final count and determines the winner.

Diagram

Each of the district nodes will use Corda’s Accounts SDK to create an account for each voter in the district. The voters can then initiate a vote transaction to the Observer node.

Using a distributed ledger technology such as Corda ensures that the vote the district node sees and the vote that the observer node network sees, are exactly the same and cannot be tampered with. There is consensus between the nodes that are privy to the relevant vote.

Each voter must “register to vote” by creating an account on the district node, which is then shared with the observer node.

In this sample, voters are anonymous.  When registering to vote, an account is created on the district node using personally-identifiable information (PII).  However, when the account is shared to the district node, the PII is obfuscated away so that the observer can only determine whether an account is unique or not using a hash rather than the PII of the voter. The votes cast to the observer are masked as private accounts in a similar way.

If you now take a look at the GitHub repo itself, you will see more about how the flows work to cast a vote. The `SendVote` flow is the most critical to the architecture of this CorDapp. It takes as input:

  • `whoAmI` – the name of the account voting
  • `observer` – the name of the observer node the vote is being sent to
  • `opportunity` – the name of the election the voter is taking part in, e.g. “1970”
  • `choice` – the integer representing the candidate the voter chooses to vote for

Every time the flow uses the account name, it calls the `HashAccount` subFlow to convert the account name to a shortened MD5 hash. This obfuscates the account name and provides a level of anonymity. The district node still has a representation of the account name, so you could still look up an account at the district level. However, this is not the case at the observer level.

If you follow the instructions in the README.md file, you will see how the accounts are created using flows and how the votes are finally cast. The flows follow the architecture pictured above, where three district nodes each have three accounts for voters, and each of these accounts casts a vote.

This brings us to the `CountVotes` flow. As you may have guessed, this allows an observer node to count the votes sent to it and show the totals of the votes for each candidate. It does this by sending a `vaultQuery` to its internal vault storage, which retrieves all of the received `VoteStates`. It then iterates through these states to count the votes. During this process, the observer checks that each vote is assigned to the correct voting opportunity. It does this by taking an `opportunity` input to the `CountVotes` flow, then checking each `opportunity` field in the `VoteState`.

The votes that are part of the correct voting opportunity are then added to a HashMap, with the account hash as the key and the integer representing the candidate as the value. This way, each account’s vote is only counted once. This also allows a candidate to update their vote, as the HashMap records the most recent vote to the key. HashMap is a rather useful feature in this way since, by default, it filters out duplicate key-value pairs based on the key, it also expands with each additional key-value pair added. It will therefore accommodate all of the votes cast as they are read from the observer node’s vault.

“`
HashMap<String, Integer> voteMap = new HashMap<>();

for (StateAndRef<VoteState> vote : votes) {

VoteState recordedVote = vote.getState().getData();

if (recordedVote.getOpportunity().equals(opportunity)) {

voteMap.put(recordedVote.getVoter(), recordedVote.getChoice());

}

}

for (Integer i : voteMap.values()) {

voteCounts.set(i, voteCounts.get(i) + 1);

}

return voteCounts;

“`

Finally, the `CountVotes` flow returns a count of each of the vote keys within the HashMap. This lets you see which candidate won the election!

You can now implement a simple EVM using Corda. Have a dig into the code and see if you can extend or make the implementation stronger, or tailor it for how your country handles voting. The best way I find to learn Corda is by doing—give it a shot and feel free to reach out to us on the #cordaledger Slack channel to let us know how you are doing!