Skip to content

Commit

Permalink
chore: optimize message processor and tally
Browse files Browse the repository at this point in the history
- [x] Remove sha256 hashing
- [x] Support multiple params for verifier
  • Loading branch information
0xmad committed Jul 26, 2024
1 parent bd894d8 commit 044e3ea
Show file tree
Hide file tree
Showing 34 changed files with 361 additions and 1,184 deletions.
28 changes: 24 additions & 4 deletions circuits/circom/circuits.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,44 @@
"file": "./core/qv/processMessages",
"template": "ProcessMessages",
"params": [10, 2, 1, 2],
"pubs": ["inputHash"]
"pubs": [
"maxVoteOptions",
"numSignUps",
"index",
"batchEndIndex",
"msgRoot",
"currentSbCommitment",
"newSbCommitment",
"pollEndTimestamp",
"actualStateTreeDepth"
]
},
"ProcessMessagesNonQv_10-2-1-2_test": {
"file": "./core/non-qv/processMessages",
"template": "ProcessMessagesNonQv",
"params": [10, 2, 1, 2],
"pubs": ["inputHash"]
"pubs": [
"maxVoteOptions",
"numSignUps",
"index",
"batchEndIndex",
"msgRoot",
"currentSbCommitment",
"newSbCommitment",
"pollEndTimestamp",
"actualStateTreeDepth"
]
},
"TallyVotes_10-1-2_test": {
"file": "./core/qv/tallyVotes",
"template": "TallyVotes",
"params": [10, 1, 2],
"pubs": ["inputHash"]
"pubs": ["index", "numSignUps", "sbCommitment", "currentTallyCommitment", "newTallyCommitment"]
},
"TallyVotesNonQv_10-1-2_test": {
"file": "./core/non-qv/tallyVotes",
"template": "TallyVotesNonQv",
"params": [10, 1, 2],
"pubs": ["inputHash"]
"pubs": ["index", "numSignUps", "sbCommitment", "currentTallyCommitment", "newTallyCommitment"]
}
}
57 changes: 10 additions & 47 deletions circuits/circom/core/non-qv/processMessages.circom
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ include "./safe-comparators.circom";
include "../../utils/hashers.circom";
include "../../utils/messageToCommand.circom";
include "../../utils/privToPubKey.circom";
include "../../utils/processMessagesInputHasher.circom";
include "../../utils/non-qv/stateLeafAndBallotTransformer.circom";
include "../../trees/incrementalMerkleTree.circom";
include "../../trees/incrementalQuinaryTree.circom";
Expand Down Expand Up @@ -48,17 +47,10 @@ include "../../trees/incrementalQuinaryTree.circom";
var STATE_LEAF_TIMESTAMP_IDX = 3;
var msgTreeZeroValue = 8370432830353022751713833565135785980866757267633941821328460903436894336785;

// nb. The usage of SHA-256 hash is necessary to save some gas costs at verification time
// at the cost of more constraints for the prover.
// Basically, some values from the contract are passed as private inputs and the hash as a public input.

// The SHA-256 hash of values provided by the contract.
signal input inputHash;
signal input packedVals;
// Number of users that have completed the sign up.
signal numSignUps;
signal input numSignUps;
// Number of options for this poll.
signal maxVoteOptions;
signal input maxVoteOptions;
// Time when the poll ends.
signal input pollEndTimestamp;
// The existing message tree root.
Expand All @@ -79,6 +71,10 @@ include "../../trees/incrementalQuinaryTree.circom";
// @note it is a public input to ensure fair processing from
// the coordinator (no censoring)
signal input actualStateTreeDepth;
// The last batch index
signal input batchEndIndex;
// The batch index of current message batch
signal input index;

// The state leaves upon which messages are applied.
// transform(currentStateLeaf[4], message5) => newStateLeaf4
Expand Down Expand Up @@ -110,14 +106,6 @@ include "../../trees/incrementalQuinaryTree.circom";
// Therefore, the index of the first message to process does not match the index of the
// first message (e.g., [msg1, msg2, msg3] => first message to process has index 3).

// The index of the first message leaf in the batch, inclusive.
signal batchStartIndex;

// The index of the last message leaf in the batch to process, exclusive.
// This value may be less than batchStartIndex + batchSize if this batch is
// the last batch and the total number of messages is not a multiple of the batch size.
signal batchEndIndex;

// The history of state and ballot roots and temporary intermediate
// signals (for processing purposes).
signal stateRoots[batchSize + 1];
Expand All @@ -131,31 +119,6 @@ include "../../trees/incrementalQuinaryTree.circom";
var computedCurrentSbCommitment = PoseidonHasher(3)([currentStateRoot, currentBallotRoot, currentSbSalt]);
computedCurrentSbCommitment === currentSbCommitment;

// Verify public inputs and assign unpacked values.
var (
computedMaxVoteOptions,
computedNumSignUps,
computedBatchStartIndex,
computedBatchEndIndex,
computedHash
) = ProcessMessagesInputHasher()(
packedVals,
coordPubKey,
msgRoot,
currentSbCommitment,
newSbCommitment,
pollEndTimestamp,
actualStateTreeDepth
);

// The unpacked values from packedVals.
computedMaxVoteOptions ==> maxVoteOptions;
computedNumSignUps ==> numSignUps;
computedBatchStartIndex ==> batchStartIndex;
computedBatchEndIndex ==> batchEndIndex;
// Matching constraints.
computedHash === inputHash;

// -----------------------------------------------------------------------
// 0. Ensure that the maximum vote options signal is valid and if
// the maximum users signal is valid.
Expand All @@ -173,7 +136,7 @@ include "../../trees/incrementalQuinaryTree.circom";
computedMessageHashers[i] = MessageHasher()(msgs[i], encPubKeys[i]);
}

// If batchEndIndex - batchStartIndex < batchSize, the remaining
// If endIndex - startIndex < batchSize, the remaining
// message hashes should be the zero value.
// e.g. [m, z, z, z, z] if there is only 1 real message in the batch
// This makes possible to have a batch of messages which is only partially full.
Expand All @@ -182,7 +145,7 @@ include "../../trees/incrementalQuinaryTree.circom";
var computedPathIndex[msgTreeDepth - msgBatchDepth];

for (var i = 0; i < batchSize; i++) {
var batchStartIndexValid = SafeLessThan(32)([batchStartIndex + i, batchEndIndex]);
var batchStartIndexValid = SafeLessThan(32)([index + i, batchEndIndex]);
computedLeaves[i] = Mux1()([msgTreeZeroValue, computedMessageHashers[i]], batchStartIndexValid);
}

Expand All @@ -195,8 +158,8 @@ include "../../trees/incrementalQuinaryTree.circom";
// Computing the path_index values. Since msgBatchLeavesExists tests
// the existence of a subroot, the length of the proof correspond to the last
// n elements of a proof from the root to a leaf, where n = msgTreeDepth - msgBatchDepth.
// e.g. if batchStartIndex = 25, msgTreeDepth = 4, msgBatchDepth = 2, then path_index = [1, 0].
var computedMsgBatchPathIndices[msgTreeDepth] = QuinGeneratePathIndices(msgTreeDepth)(batchStartIndex);
// e.g. if startIndex = 25, msgTreeDepth = 4, msgBatchDepth = 2, then path_index = [1, 0].
var computedMsgBatchPathIndices[msgTreeDepth] = QuinGeneratePathIndices(msgTreeDepth)(index);

for (var i = msgBatchDepth; i < msgTreeDepth; i++) {
computedPathIndex[i - msgBatchDepth] = computedMsgBatchPathIndices[i];
Expand Down
77 changes: 23 additions & 54 deletions circuits/circom/core/non-qv/tallyVotes.circom
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ include "../../trees/incrementalMerkleTree.circom";
include "../../trees/incrementalQuinaryTree.circom";
include "../../utils/calculateTotal.circom";
include "../../utils/hashers.circom";
include "../../utils/tallyVotesInputHasher.circom";

/**
* Processes batches of votes and verifies their validity in a Merkle tree structure.
Expand All @@ -32,7 +31,7 @@ template TallyVotesNonQv(
var BALLOT_TREE_ARITY = 2;

// The number of ballots processed at once, determined by the depth of the intermediate state tree.
var batchSize = BALLOT_TREE_ARITY ** intStateTreeDepth;
var ballotsBatchSize = BALLOT_TREE_ARITY ** intStateTreeDepth;
// Number of voting options available, determined by the depth of the vote option tree.
var numVoteOptions = TREE_ARITY ** voteOptionTreeDepth;

Expand All @@ -51,81 +50,51 @@ template TallyVotesNonQv(
signal input ballotRoot;
// Salt used in commitment to secure the ballot data.
signal input sbSalt;

// Inputs combined into a hash to verify the integrity and authenticity of the data.
signal input packedVals;
// Commitment to the state and ballots.
signal input sbCommitment;
// Commitment to the current tally before this batch.
signal input currentTallyCommitment;
// Commitment to the new tally after processing this batch.
signal input newTallyCommitment;

// A tally commitment is the hash of the following salted values:
// - the vote results,
// - the number of voice credits spent per vote option,
// - the total number of spent voice credits.

// Hash of all inputs to ensure they are unchanged and authentic.
signal input inputHash;

// Start index of given batch
signal input index;
// Number of users that signup
signal input numSignUps;
// Ballots and their corresponding path elements for verification in the tree.
signal input ballots[batchSize][BALLOT_LENGTH];
signal input ballots[ballotsBatchSize][BALLOT_LENGTH];
signal input ballotPathElements[k][BALLOT_TREE_ARITY - 1];
signal input votes[batchSize][numVoteOptions];

signal input votes[ballotsBatchSize][numVoteOptions];
// Current results for each vote option.
signal input currentResults[numVoteOptions];
// Salt for the root of the current results.
signal input currentResultsRootSalt;

// Total voice credits spent so far.
signal input currentSpentVoiceCreditSubtotal;
// Salt for the total spent voice credits.
signal input currentSpentVoiceCreditSubtotalSalt;

// Salt for the root of the new results.
signal input newResultsRootSalt;
// Salt for the new total spent voice credits root.
signal input newSpentVoiceCreditSubtotalSalt;
// The number of total registrations, used to validate the batch index.
signal numSignUps;
// Index of the first ballot in this batch.
signal batchStartIndex;

// Verify sbCommitment.
var computedSbCommitment = PoseidonHasher(3)([stateRoot, ballotRoot, sbSalt]);
computedSbCommitment === sbCommitment;

// Verify inputHash.
var (
computedNumSignUps,
computedBatchNum,
computedHash
) = TallyVotesInputHasher()(
sbCommitment,
currentTallyCommitment,
newTallyCommitment,
packedVals
);

inputHash === computedHash;
numSignUps <== computedNumSignUps;
batchStartIndex <== computedBatchNum * batchSize;

// Validates that the batchStartIndex is within the valid range of sign-ups.
var numSignUpsValid = LessEqThan(50)([batchStartIndex, numSignUps]);
// Validates that the index is within the valid range of sign-ups.
var numSignUpsValid = LessEqThan(50)([index * ballotsBatchSize, numSignUps]);
numSignUpsValid === 1;

// Hashes each ballot for subroot generation, and checks the existence of the leaf in the Merkle tree.
var computedBallotHashers[batchSize];
var computedBallotHashers[ballotsBatchSize];

for (var i = 0; i < batchSize; i++) {
for (var i = 0; i < ballotsBatchSize; i++) {
computedBallotHashers[i] = PoseidonHasher(2)([ballots[i][BALLOT_NONCE_IDX], ballots[i][BALLOT_VO_ROOT_IDX]]);
}

var computedBallotSubroot = CheckRoot(intStateTreeDepth)(computedBallotHashers);
var computedBallotPathIndices[k] = MerkleGeneratePathIndices(k)(computedBatchNum);
var computedBallotPathIndices[k] = MerkleGeneratePathIndices(k)(index);

// Verifies each ballot's existence within the ballot tree.
LeafExists(k)(
Expand All @@ -136,38 +105,38 @@ template TallyVotesNonQv(
);

// Processes vote options, verifying each against its declared root.
var computedVoteTree[batchSize];
for (var i = 0; i < batchSize; i++) {
var computedVoteTree[ballotsBatchSize];
for (var i = 0; i < ballotsBatchSize; i++) {
computedVoteTree[i] = QuinCheckRoot(voteOptionTreeDepth)(votes[i]);
computedVoteTree[i] === ballots[i][BALLOT_VO_ROOT_IDX];
}

// Calculates new results and spent voice credits based on the current and incoming votes.
var computedIsFirstBatch = IsZero()(batchStartIndex);
var computedIsFirstBatch = IsZero()(index * ballotsBatchSize);
var computedIsZero = IsZero()(computedIsFirstBatch);

// Tally the new results.
var computedCalculateTotalResult[numVoteOptions];
for (var i = 0; i < numVoteOptions; i++) {
var computedNumsRC[batchSize + 1];
computedNumsRC[batchSize] = currentResults[i] * computedIsZero;
for (var j = 0; j < batchSize; j++) {
var computedNumsRC[ballotsBatchSize + 1];
computedNumsRC[ballotsBatchSize] = currentResults[i] * computedIsZero;
for (var j = 0; j < ballotsBatchSize; j++) {
computedNumsRC[j] = votes[j][i];
}

computedCalculateTotalResult[i] = CalculateTotal(batchSize + 1)(computedNumsRC);
computedCalculateTotalResult[i] = CalculateTotal(ballotsBatchSize + 1)(computedNumsRC);
}

// Tally the new spent voice credit total.
var computedNumsSVC[batchSize * numVoteOptions + 1];
computedNumsSVC[batchSize * numVoteOptions] = currentSpentVoiceCreditSubtotal * computedIsZero;
for (var i = 0; i < batchSize; i++) {
var computedNumsSVC[ballotsBatchSize * numVoteOptions + 1];
computedNumsSVC[ballotsBatchSize * numVoteOptions] = currentSpentVoiceCreditSubtotal * computedIsZero;
for (var i = 0; i < ballotsBatchSize; i++) {
for (var j = 0; j < numVoteOptions; j++) {
computedNumsSVC[i * numVoteOptions + j] = votes[i][j];
}
}

var computedNewSpentVoiceCreditSubtotal = CalculateTotal(batchSize * numVoteOptions + 1)(computedNumsSVC);
var computedNewSpentVoiceCreditSubtotal = CalculateTotal(ballotsBatchSize * numVoteOptions + 1)(computedNumsSVC);

// Verifies the updated results and spent credits, ensuring consistency and correctness of tally updates.
ResultCommitmentVerifierNonQv(voteOptionTreeDepth)(
Expand Down
Loading

0 comments on commit 044e3ea

Please sign in to comment.