The quadratic vote tallying circuit
Quadratic voting is one of many types of vote tallying mechanisms. We chose it for the first version of MACI due to the high amount of interest that the community has shown for it.
Quadratic voting allows users to express the strength of their preferences when they vote for options. Since users are allocated a limited number of voice credits, and the number of tallied votes per option is the square root of the number of voice credits spent on said option, quadratic voting over-privileges neither concentrated nor diffuse interests.
For instance, if a user has 99 voice credits, they may spend them this way (each row represents a command):
Option | Voice credits spent |
---|---|
A | 1 |
A | 9 |
B | 25 |
C | 64 |
The outcome is as such:
Option | Tallied votes |
---|---|
A | 3.16 |
B | 5 |
C | 8 |
Even though the user has a disproportionate preference for option C (64 voice credits), their impact on the tallied vote (8 votes) is merely the square root of the voice credits they have spent. This prevents them from having an outsized influence on the results simply by virtue of their willingness to spend as many voice credits on that option as they had.
Additionally, we consider that votes are cumulative. This means that the user spent 10 voice credits on option A.
The MACI contract's quadraticVoteTally()
function should verify a proof created using this circuit to compute the results of tallying a set of state leaves. This also proves that these state leaves have an intermediate root A
, as well that A
is part of the tree with final state root R
. This allows the coordinator to prove the final tally in batches. The function keeps track of the index of each intermediate root to ensure that they are processed consecutively.
Inputs
Pseudocode name | zk-SNARK input type | Description | Set by |
---|---|---|---|
fullStateRoot | Public | The final Merkle root of the state tree | Contract |
fullStateTreeDepth | Hardcoded | The depth of the state tree | Contract |
intermediateStateTreeDepth | Hardcoded | The depth of the intermediate state tree | Contract |
intermediateStateRoot | Public | The intermediate Merkle root generated by the given state leaves | Contract |
intermediatePathElements[k] | Private | The Merkle path elements from intermediateStateRoot to stateRoot . | Coordinator |
intermediatePathIndex | Public | The Merkle path index from intermediateStateRoot to stateRoot . | Contract |
currentResults[n] | Private | The vote tally of all prior batches of state leaves | Coordinator |
currentResultsSalt | Private | A random value to hash with the vote tally for state leaves up to the current batch | Coordinator |
currentResultsCommitment | Public | The salted commitment of the values in currentResults | Contract |
newResultsCommitment | Public | The salted commitment of the vote tally for this batch of leaves plus the vote tally from currentResults | Contract |
salt | Private | A random value to hash with the culmulate vote tally for this batch of state leaves | Coordinator |
stateLeaves[m][p] | Private | The batch of leaves of the state tree to tally. | Coordinator |
voteLeaves[m][n] | Private | The vote leaves for each user in this batch of state leaves. | Coordinator |
n
is the number of options in voteOptionTree
. m
is the number of state leaves in this batch. k
is fullStateTreeDepth - intermediateStateTreeDepth
p
is the message length
A result commitment is the hash of a Merkle root of all the vote leaves, and a salt. For instance:
root = genTree(results)
hash(root, salt)
Circuit pseudocode
// Alice votes for party A with 16 credits
// Bob votes for party A with 9 credits
// Party A gets 7 tallied votes. NOT 5 votes.
// Ensure via a constraint that the intermediate root is the
// correct Merkle root of the stateLeaves passed into this
// snark
assert(intermediateStateRoot == genTree(stateLeaves))
// Ensure via a constraint that the intermediate root is part of the full state tree
var x = generateMerkleRoot(
intermediatePathElements,
intermediatePathIndex,
intermediateRoot
)
assert(x == stateRoot)
// This variable stores the sum of the square roots of each
// user's voice credits per option.
var computedResults = currentResults
var start = 1
if intermediatePathIndex > 0:
start = 0
// For each user
for i as start to m: // we ignore leaf 0 on purpose
// Ensure via a constraint that the voteLeaves for this
// user is correct (such that when each vote leaf is
// inserted into an MT, the Merkle root matches
// the `voteOptionTreeRoot` field of the state leaf)
var computedVoteOptionTreeRoot = genTree(voteLeaves[i])
assert(computedVoteOptionTreeRoot == stateLeaves[i].voteOptionTreeRoot)
// Calculate the sum of votes for each option
for j as 0 to n.
// This adds to the subtotal from previous batches
// of state leaves
computedResults[j] += voteLeaves[i][j]
// Ensure via a constraint that the commitment to the current results is
// correct
assert(
hash(genTree(currentResults), currentResultsSalt) ==
currentResultsCommitment
)
// Ensure via a constraint that the final result
// is correct
assert(
hash(genTree(computedResults), salt) ==
newResultsCommitment
)
where genTree
is pseudocode for a circuit which computes a Merkle root from a list of leaves.
Circuit failure modes
Condition | Outcome |
---|---|
Invalid state leaves and/or intermediate state root | No such proof can be generated |
Invalid vote option leaves | No such proof can be generated |
Invalid Merkle path to the full state root from the intermediate state root for the batch of votes | No such proof can be generated |