last update 08/09/2024
Implementation of a mixnet using the ElectionGuard Kotlin Elliptical Curve library, and the Verificatum library. The mixnet uses the Terelius / Wikström (TW) mixnet algorithm, see egk mixnet maths for details. Note that paper's timings use the older integer group; the elliptic curve group is much faster.
This is part of VotingWork's cacvote project. It is not part of the ElectionGuard specification per se, but follows the ElectionGuard 2.0 specification wherever possible.
The implementation for Elliptical Curves (EC) is derived from the Verificatum library, including the option to use the Verificatum C library. See VCR License for the license for this part of the library.
Note that the EC implementation is not stable and may change in the future. However, other than different build instructions, this should not affect the API.
Also see: Workflow Notes
Encrypting a ballot with 12 contests and 4 selections each (total of 60 encryptions) takes about 6 ms per ballot, using pre-computed tables for "fixed base acceleration". This does not appear to be using Montgomery forms for fast mod operation.
Mixing 1000 ballots of width 34 takes ~ 17 secs single threaded with good parallelization. Verification is 30-50% slower than the shuffle and proof, see plot.
We use "point compression" on the elliptic curve ElementModP, so we only serialize the x and "sign of y" coordinates, giving a storage reduction of O(64/33) compared to serializing both coordinates, and O(512/33) compared to the integer group. To estimate the computational cost of storing just x and recomputing y: BallotReader reads 1000 ballots (width 34) in 235 msecs. If one computes y instead of reading it, it takes 1274 msecs. So, cost is ~ 1 sec for 34000 texts everytime you have to read the mixed ballots.
Currently we store the ballots in binary and the proofs in json in base64. For very large mixnets, you might want to store proofs as efficiently as possible, which argues for a protobuf option.
readMixnetBallots from working/public/mix1/Shuffled.bin nrows = 1000 width=34
BallotReader took 2352 msecs = .006917 msecs/text (340000 texts) = 235.2 msecs/trial for 10 trials
BallotReaderAlt took 12744 msecs = .03748 msecs/text (340000 texts) = 1274 msecs/trial for 10 trials
cd <install-dir>
git clone https://github.com/JohnLCaron/egk-ec-mixnet.git
cd egk-mixnet
Prerequisites: Java 21
To build the code:
./gradlew clean assemble
./gradlew uberJar
If the library has changed and you need to update it:
cd ~/dev/github/egk-ec-mixnet:
git fetch origin
git rebase origin/main
Then rebuild the code:
./gradlew clean assemble
./gradlew uberJar
Follow the instructions in Egk-ec Getting Started
This is needed for good performance.
~/dev/github/egk-ec-mixnet:$ ./scripts/completeWorkflow.sh working
Runs a complete test of the workflow and writes the output to whatever you set working to.
After running, examine the log file at egkMixnet.log.
Note that election_initialize.sh uses a manifest from src/test/data/mixnetInput. Change that to the correct manifest if needed.
You might want to first delete the log file at egkMixnet.log.
~/dev/github/egk-ec-mixnet:$ ./scripts/encryptionWorkflow.sh working
Runs a test of the encryption workflow and writes the output to whatever you set working to.
After running, examine the log file at egkMixnet.log.
working/public
encrypted_ballots/
device1/
eballot-id1.json
eballot-id2.json
device2/
eballot-id1.json
eballot-id2.json
...
mix1/
mix_config.json
proof_of_shuffle.json
ShuffledBallots.json
mix2/
mix_config.json
proof_of_shuffle.json
ShuffledBallots.json
mixN/
...
constants.json
election_config.json
election_initialized.json
encrypted_tally.json
manifest.json
tally.json
decrypted_sns.json
pballot-table.json
working/private
input_ballots/
pballot-id1.json
pballot-id2.json
...
trustees/
decrypting_trustee-name1
decrypting_trustee-name2
...
decrypted_ballots/
dballot-sn1.json
dballot-sn2.json
...