The ZKTally demo site: an interactive explainer that runs a real election in the browser
using zktally, showing the actual cryptographic
objects at every step.
ZKTally is unaudited academic software implementing a protocol from a student research paper. It has not been reviewed by professional cryptographers and must not be used for any binding election.
Every number on screen is real. When the site shows a ciphertext, it is the ciphertext
encrypt() just produced; when it shows the homomorphic product, aggregate() computed it. A
visitor can export the board, run zktally audit on it in Python, and get the same answer.
That constraint is what gives the site its value. An explainer animating plausible-looking numbers would teach the shape of the protocol while proving nothing.
pnpm install
pnpm dev
pnpm test # unit + honesty checks
pnpm test:e2e # Playwright, against the built site
pnpm build # typecheck, build, and the bundle-size gatezktally is linked from the sibling zktally-js/ checkout. Build that package first —
the site consumes its dist/.
src/
├── lib/zk.ts The single entry point into the crypto library, and the worker pool
├── lib/db.ts IndexedDB persistence
├── lib/format.ts Display helpers for values that are far too big to show whole
├── stores/ Pinia: the live election
├── components/ Shared UI, including the adversary panel
└── views/ One per route
Routing uses hash history because GitHub Pages has no rewrite rules — a deep link under history mode would 404 on reload.
test/honesty.test.ts asserts the constraints that matter:
- The standing disclaimer is rendered by the app shell, so it appears on every page, and prominently on the landing hero.
- No page claims "secure e-voting" unqualified, and nothing affirms receipt-freeness.
- The walkthrough covers what ZKTally does not protect against, and demonstrates the
receipt — the visitor reconstructs their own ballot from
(vote, randomness)— rather than describing it. - Single-authority elections carry a persistent warning that the key holder can decrypt any individual ballot, and the tally view reports such a total as asserted rather than proved.
- Verdicts never rely on colour alone: icon and word, always.
These are tests rather than intentions because this project exists partly to correct an earlier version whose documentation claimed four security properties it did not have.
The election sandbox lets a visitor attack the election and watch it fail. Each attack is one that worked against the research prototype: double voting, a forged key image, a self-made ring, a poisoned ciphertext, vote copying, and an out-of-range vote. The ballots are genuinely malicious and the rejections come from the real verifier.
- No
gen:paramsscript. That step exists because threshold keygen needs safe primes and cannot run in a browser, so the walkthrough would need bundled parameters. This walkthrough runs in single-authority mode at 2048 bits, which is viable live — and generating it in front of the visitor supports the "nothing is pre-computed" claim better than shipping a fixture. The UI states plainly that the mode is weaker than the protocol allows and that the resulting total is unproved. Threshold mode is exercised in the Python package's test suite. - No GitHub Actions workflow. Removed at the maintainer's request.
pnpm checkruns the same gates locally.
No backend, no analytics, no external fonts, no CDN, no network requests of any kind after
load. Elections are stored in IndexedDB and nothing is written until a visitor starts one;
/about has a control that clears everything.
MIT. See LICENSE.