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):

OptionVoice credits spent
A1
A9
B25
C64

The outcome is as such:

OptionTallied votes
A3.16
B5
C8

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 namezk-SNARK input typeDescriptionSet by
fullStateRootPublicThe final Merkle root of the state treeContract
fullStateTreeDepthHardcodedThe depth of the state treeContract
intermediateStateTreeDepthHardcodedThe depth of the intermediate state treeContract
intermediateStateRootPublicThe intermediate Merkle root generated by the given state leavesContract
intermediatePathElements[k]PrivateThe Merkle path elements from intermediateStateRoot to stateRoot.Coordinator
intermediatePathIndexPublicThe Merkle path index from intermediateStateRoot to stateRoot.Contract
currentResults[n]PrivateThe vote tally of all prior batches of state leavesCoordinator
currentResultsSaltPrivateA random value to hash with the vote tally for state leaves up to the current batchCoordinator
currentResultsCommitmentPublicThe salted commitment of the values in currentResultsContract
newResultsCommitmentPublicThe salted commitment of the vote tally for this batch of leaves plus the vote tally from currentResultsContract
saltPrivateA random value to hash with the culmulate vote tally for this batch of state leavesCoordinator
stateLeaves[m][p]PrivateThe batch of leaves of the state tree to tally.Coordinator
voteLeaves[m][n]PrivateThe 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 - intermediateStateTreeDepthp 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

ConditionOutcome
Invalid state leaves and/or intermediate state rootNo such proof can be generated
Invalid vote option leavesNo such proof can be generated
Invalid Merkle path to the full state root from the intermediate state root for the batch of votesNo such proof can be generated

Last Updated: