tinyvote: Secure Voting Workflow Demo


This secure voting workflow demo (source code available on GitHub) simulates a secure multi-party computation (MPC) protocol entirely in your browser by importing and invoking the tinyvote library using PyScript.

Within the implementation of the library, vectors of finite field elements are used to represent both submitted votes and the overall outcome. In the visualizations below, instances of these data structures are rendered using a variant of a heat map in which the brightness value corresponds to the difference between the finite field element and zero. For example, a vector such as 2 mod 115 mod 118 mod 113 mod 1110 mod 11 would be represented by   . Note also that voters and nodes are numbered starting from zero in order to match the source code.

  1. For each voter, click on one of the options and then click Submit. The votes are encoded as vectors, masks are requested from the nodes, and the masked vectors are broadcast to all the nodes.

Voter 0


Voter 1


Voter 2


Voter 3


  1. Each node receives the masked vectors corresponding to the four votes, and then locally computes its secret share of the overall result vector.

Node 0

Masked Votes

Secret Share of Outcome

Node 1

Masked Votes

Secret Share of Outcome

Node 2

Masked Votes

Secret Share of Outcome

  1. The voting workflow operator receives the secret shares of the result vector from each node and reconstructs it to determine the outcome.

Network Output

packages = ["tinyvote~=0.1"]
from pyodide.ffi import create_proxy import js from tinyvote import * def modulo_to_uint8(m): return 64 + (((255 - 64) * int(m)) // m.modulus) def compute_outcome_if_ready(): if all(vote is not None for vote in votes): shares = [node.outcome(votes) for node in nodes] for node_id in range(3): js.nodeShowShare( node_id, [modulo_to_uint8(value) for value in shares[node_id]] ) result = reveal(shares) js.recipientEnable() js.document.getElementById('recipient-output').innerHTML = \ 'Alice: ' + str(result[1]) + '; Bob: ' + str(result[2]) + '.' def make_vote_handler(vote_id): def vote_handler(_): choice = js.clientGetVoteAndDisable(vote_id) request_ = request(identifier=vote_id) masks = [node.masks(request_) for node in nodes] votes[vote_id] = vote(masks, choice) js.diagramInputUpdate() js.nodesEnable() for node_id in range(len(nodes)): js.nodeShowVoteMasked( vote_id, node_id, [modulo_to_uint8(list(d.values())[0]) for d in votes[vote_id]] ) compute_outcome_if_ready() return vote_handler nodes = [node(), node(), node()] votes = [None, None, None, None] preprocess(nodes, votes=len(votes), choices=3) for vote_id in range(len(votes)): js.document.getElementById('vote-' + str(vote_id)).addEventListener( 'click', create_proxy(make_vote_handler(vote_id)) )