tinyvote module

Minimal pure-Python library that demonstrates a basic encrypted voting workflow by leveraging a secure multi-party computation (MPC) protocol.

class tinyvote.tinyvote.node[source]

Bases: object

Data structure for maintaining the information associated with a node and performing node operations.

Suppose that a secure decentralized voting workflow is supported by three parties. The node objects would be instantiated locally by each of these three parties.

>>> nodes = [node(), node(), node()]

The preprocessing workflow that the nodes must execute can be simulated using the preprocess function. The number of voters that the workflow supports must be known, and it is assumed that all permitted choices are integers greater than or equal to 0 and strictly less than a fixed maximum value. The number of voters and the number of distinct choices must be supplied to the preprocess function.

>>> preprocess(nodes, votes=4, choices=2)

Each voter must then submit a request for the opportunity to submit their vote. The voters can create request instances for this purpose. In the example below, each of the four voters creates such a request.

>>> request_zero = request(identifier=0)
>>> request_one = request(identifier=1)
>>> request_two = request(identifier=2)
>>> request_three = request(identifier=3)

Each voter can deliver their request to each node, and each node can then locally use its masks method to generate masks that can be returned to the requesting voter.

>>> masks_zero = [node.masks(request_zero) for node in nodes]
>>> masks_one = [node.masks(request_one) for node in nodes]
>>> masks_two = [node.masks(request_two) for node in nodes]
>>> masks_three = [node.masks(request_three) for node in nodes]

Each voter can then generate locally a vote instance (i.e., a masked vote choice).

>>> vote_zero = vote(masks_zero, 0)
>>> vote_one = vote(masks_one, 1)
>>> vote_two = vote(masks_two, 1)
>>> vote_three = vote(masks_three, 1)

Every voter can broadcast its masked vote choice to all the nodes. Each node can locally assemble these as they arrive. Once a node has received all masked votes, it can determine its shares of the overall tally of the votes using the outcome method.

>>> shares = [
...     node.outcome([vote_zero, vote_one, vote_two, vote_three])
...     for node in nodes
... ]

The overall outcome can be reconstructed from the shares by the voting workflow operator using the reveal function. The outcome is represented as a list in which each entry contains the tally for the choice corresponding to the entry’s index.

>>> reveal(shares)
[1, 3]
masks(request)[source]

Return masks for a given request.

Parameters

request (Iterable[Tuple[int, int]]) – Request from voter.

Return type

List[Dict[Tuple[int, int], modulo]]

outcome(votes)[source]

Perform computation to determine a share of the overall outcome.

Parameters

votes (Sequence[vote]) – Sequence of masked votes.

Return type

List[modulo]

class tinyvote.tinyvote.request(identifier)[source]

Bases: List[Tuple[int, int]]

Data structure for representing a request to submit a vote. A request can be submitted to each node to obtain corresponding masks for a vote.

Parameters

identifier (int) – Integer identifying the requesting voter.

The example below demonstrates how requests can be created.

>>> request(identifier=1),
([(0, 1)],)
>>> request(identifier=3),
([(0, 3)],)
class tinyvote.tinyvote.vote(masks, choice)[source]

Bases: List[Dict[Tuple[int, int], modulo]]

Data structure for representing a vote that can be broadcast to nodes.

Parameters
  • masks (List[List[Dict[Tuple[int, int], modulo]]]) – Collection of masks to be applied to the vote choice.

  • choice (int) – Non-negative integer representing the vote choice.

Suppose masks have already been obtained from the nodes via the steps below.

>>> nodes = [node(), node(), node()]
>>> preprocess(nodes, votes=4, choices=3)
>>> identifier = 2
>>> choice = 2
>>> masks = [node.masks(request(identifier)) for node in nodes]

This method can be used to mask the vote choice (in preparation for broadcasting it to the nodes).

>>> isinstance(vote(masks, choice), vote)
True
tinyvote.tinyvote.preprocess(nodes, votes, choices)[source]

Simulate a preprocessing workflow among the supplied nodes for a workflow that supports the specified number of votes and distinct choices (where choices are assumed to be integers greater than or equal to 0 and strictly less than the value choices).

Parameters
  • nodes (Sequence[node]) – Collection of nodes involved in the workflow.

  • votes (int) – Number of votes.

  • choices (int) – Number of distinct choices (from 0 to choices - 1).

The example below performs a preprocessing workflow involving three nodes.

>>> nodes = [node(), node(), node()]
>>> preprocess(nodes, votes=4, choices=3)
tinyvote.tinyvote.reveal(shares)[source]

Reconstruct the overall tally of votes from the shares obtained from each node.

Parameters

shares (List[List[modulo]]) – Shares of overall outcome tally (where each share is a list of components, with one component per permitted choice).

Suppose the shares below are returned from the three nodes in a workflow.

>>> from modulo import modulo
>>> p = 4215209819
>>> shares = [
...     [modulo(3, p), modulo(5, p), modulo(4, p)],
...     [modulo(1, p), modulo(2, p), modulo(9, p)],
...     [modulo(8, p), modulo(0, p), modulo(8, p)]
... ]

This method combines such shares into an overall outcome by reconstructing the individual components and returning a list representing a tally of the total number of votes for each choice.

>>> reveal(shares)
[3, 2, 4]
Return type

List[int]