Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: optimize message processor and tally #1700

Merged
merged 1 commit into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ module.exports = {
"import/no-extraneous-dependencies": [
"error",
{
devDependencies: ["**/*.test.ts", "**/__benchmarks__/**"],
devDependencies: ["**/*.test.ts", "**/__benchmarks__/**", "**/tests/**", "**/__tests__/**"],
},
],
"no-debugger": isProduction ? "error" : "off",
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/reusable-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,6 @@ jobs:
run: |
pnpm run build

- name: Run hardhat fork
run: |
cd contracts
pnpm run hardhat &
sleep 5

- name: Download rapidsnark (1c137)
run: |
mkdir -p ~/rapidsnark/build
Expand All @@ -90,6 +84,12 @@ jobs:
run: |
pnpm download-zkeys:test

- name: Run hardhat fork
run: |
pnpm run hardhat &
sleep 5
working-directory: contracts

- name: ${{ matrix.command }}
run: pnpm run ${{ matrix.command }}

Expand Down
2 changes: 2 additions & 0 deletions circuits/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ ptau
input.json
circom/main
circom/test
zkeys/

26 changes: 22 additions & 4 deletions circuits/circom/circuits.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,42 @@
"file": "./core/qv/processMessages",
"template": "ProcessMessages",
"params": [10, 2, 1, 2],
"pubs": ["inputHash"]
"pubs": [
"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": [
"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"]
}
}
61 changes: 11 additions & 50 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 @@ -36,6 +35,7 @@ include "../../trees/incrementalQuinaryTree.circom";
// Default for Binary trees.
var STATE_TREE_ARITY = 2;
var batchSize = MESSAGE_TREE_ARITY ** msgBatchDepth;
var maxVoteOptions = MESSAGE_TREE_ARITY ** voteOptionTreeDepth;
var MSG_LENGTH = 10;
var PACKED_CMD_LENGTH = 4;
var STATE_LEAF_LENGTH = 4;
Expand All @@ -48,17 +48,8 @@ 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;
// Number of options for this poll.
signal maxVoteOptions;
signal input numSignUps;
// Time when the poll ends.
signal input pollEndTimestamp;
// The existing message tree root.
Expand All @@ -79,6 +70,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 +105,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 +118,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 +135,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 +144,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 +157,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 Expand Up @@ -286,7 +248,6 @@ include "../../trees/incrementalQuinaryTree.circom";

(computedNewVoteStateRoot[i], computedNewVoteBallotRoot[i]) = ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth)(
numSignUps,
maxVoteOptions,
pollEndTimestamp,
stateRoots[i + 1],
ballotRoots[i + 1],
Expand Down Expand Up @@ -336,6 +297,7 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) {
var BALLOT_NONCE_IDX = 0;
// Ballot vote option (VO) root index.
var BALLOT_VO_ROOT_IDX = 1;
var maxVoteOptions = MESSAGE_TREE_ARITY ** voteOptionTreeDepth;

// Indices for elements within a state leaf.
// Public key.
Expand All @@ -348,7 +310,6 @@ template ProcessOneNonQv(stateTreeDepth, voteOptionTreeDepth) {

// Inputs representing the message and the current state.
signal input numSignUps;
signal input maxVoteOptions;
signal input pollEndTimestamp;

// The current value of the state tree root.
Expand Down
47 changes: 8 additions & 39 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 Down Expand Up @@ -51,70 +50,40 @@ 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 ballotPathElements[k][BALLOT_TREE_ARITY - 1];
signal input votes[batchSize][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, numSignUps]);
numSignUpsValid === 1;

// Hashes each ballot for subroot generation, and checks the existence of the leaf in the Merkle tree.
Expand All @@ -125,7 +94,7 @@ template TallyVotesNonQv(
}

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

// Verifies each ballot's existence within the ballot tree.
LeafExists(k)(
Expand All @@ -143,7 +112,7 @@ template TallyVotesNonQv(
}

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

// Tally the new results.
Expand Down
Loading
Loading