Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ce6907a
feat(tally): a proof of correct decryption anyone can check
claude Sep 24, 2026
5738f35
fix(contracts): close the pre-Amoy review findings
claude Sep 24, 2026
c7510fe
fix(backend): server-fixed World ID action, honest credentials, bound…
claude Sep 24, 2026
82fdbc4
fix(frontend): verifiable results, no invented receipt, safer identities
claude Sep 24, 2026
6056c98
feat(tally-cli): prove when publishing, and --verify with no key
claude Sep 24, 2026
d6251b7
chore: CI lint and e2e, Node 24 / npm 11, honest README and known limits
claude Sep 24, 2026
353e3fb
Merge dev into final-revision-before-amoy-deploy
claude Sep 24, 2026
4e75fb8
feat(frontend): recovery phrases from the EFF short wordlist, 124 bits
claude Sep 24, 2026
2892fb5
test(frontend): phrases in the new word list in the last three suites
claude Sep 24, 2026
28e9ae2
feat(circuits): ballot and tally circuits, trusted setup and generate…
claude Sep 24, 2026
9e80094
feat(contracts): hidden re-votes, on-chain aggregate, proved tally
claude Sep 24, 2026
84f24d3
feat: frontend, backend and tally CLI on the hidden-revote ballots
claude Sep 24, 2026
7f1ca92
docs, ci: circuits job, hidden re-votes and honest limits
claude Sep 24, 2026
5c918d7
fix: seed windows sized for proving, CLI exits after --verify, locali…
claude Sep 24, 2026
5661e24
fix(seed): ballots may be absent from a spec
claude Sep 24, 2026
ca486d0
test(circuits): compile before the witness tests, never skip them
claude Sep 24, 2026
5c23b95
ci(circuits): generate phase 1 once and cache it on its own
claude Sep 24, 2026
9e5c022
fix(contracts): refuse a deployment before its first transaction
CodeInIA Sep 27, 2026
8870c21
fix(circuits): compile on Windows with the native circom
CodeInIA Sep 27, 2026
9d6b644
fix(frontend): quote paid ballots at each election's circuit size
CodeInIA Sep 27, 2026
665f8c1
fix(frontend): read failures, the hourly limit and gaps found testing…
CodeInIA Sep 27, 2026
b29f032
fix(frontend): steady local RPC proxy, and no chain reads for demo el…
CodeInIA Sep 27, 2026
6822cd9
fix(frontend): scale an unmeasured circuit size by its circuit, not i…
CodeInIA Sep 28, 2026
d1651b0
feat(frontend): replace a ballot straight away through the previous e…
CodeInIA Sep 28, 2026
453879f
docs(frontend): terms, how it works and privacy say what the contract…
CodeInIA Sep 28, 2026
3feb83a
docs: the current ballot scheme, the Amoy deploy steps and the last-h…
CodeInIA Sep 28, 2026
a408b31
chore(deps): update every package to its latest release
CodeInIA Sep 28, 2026
1cc3e8f
perf(frontend): import Poseidon by arity, not from the package root
CodeInIA Sep 28, 2026
9c0cb73
docs: Mermaid architecture diagrams in every README
CodeInIA Sep 28, 2026
bf37832
docs: two ballots within an hour, not one, now that the app uses both…
CodeInIA Sep 28, 2026
0a157c6
docs: the hand-drawn diagrams in the dev docs become Mermaid
CodeInIA Sep 28, 2026
5f100e9
docs: future work, and the one tally limit the docs did not state
CodeInIA Sep 28, 2026
618c7d2
docs: future work, taking the per-user gas off the platform
CodeInIA Sep 28, 2026
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
100 changes: 96 additions & 4 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ on:
push:
branches: [main]
pull_request:
branches: [main]
# `dev` too: that is where work is reviewed before it reaches `main`.
branches: [main, dev]
workflow_dispatch:

permissions:
Expand All @@ -44,6 +45,11 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- run: npm ci
working-directory: frontend
# oxlint rather than ESLint: typescript-eslint does not support the
# TypeScript 7 compiler this project builds with, and oxlint needs no
# compiler at all. Correctness rules only, including react-hooks.
- run: npm run lint
working-directory: frontend
# `npm run build` is `tsc -b && vite build`, so this covers types as well
# as the bundle. It is also what 4EVERLAND runs, which is the point: if
# this passes and that fails, the difference is configuration, not code.
Expand All @@ -63,6 +69,13 @@ jobs:
# The file that keeps deep links working on IPFS. Cheap to lose in a
# refactor of `public/`, and its absence only shows up as a 404 on a
# reloaded route in production.
# Every screen, rendered in a real browser at three sizes, against the
# demo data with the API answering as a server with no session. Catches
# what unit tests cannot: a render loop, a route that crashes on load.
- run: npx playwright install --with-deps chromium
working-directory: frontend
- run: npm run test:e2e
working-directory: frontend
- name: The SPA rewrite survived the build
working-directory: frontend
run: |
Expand All @@ -82,6 +95,8 @@ jobs:
cache-dependency-path: backend/package-lock.json
- run: npm ci
working-directory: backend
- run: npm run lint
working-directory: backend
- run: npm run build
working-directory: backend
- run: npm test
Expand All @@ -100,8 +115,76 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max

# ── the ballot and tally circuits ──────────────────────────────────────────
#
# The witness tests say the circuits accept what they should and refuse what
# they should; the build produces the verifiers the contracts compile against
# and the proving files the contracts' E2E suite proves with.
#
# CACHED IN TWO LAYERS, because the trusted setup is slow. The phase 1 of
# 2^17 takes nearly forty minutes to generate on a runner and depends on
# nothing in the repository, so it has a cache of its own, SAVED THE MOMENT
# IT EXISTS: a later step failing must not throw that work away. The rest
# (phase 2, verifiers, proving files) is cached on the circuit sources.
# The phase 2 here is the public DEVELOPMENT one, fine for CI and never
# deployable (deploy.ts refuses it off the local chain).
circuits:
name: circuits
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
cache-dependency-path: circuits/package-lock.json
- run: npm ci
working-directory: circuits
- run: npm test
working-directory: circuits
- uses: actions/cache@v4
id: build
with:
path: |
circuits/build
contracts/contracts/verifiers
frontend/public/circuits
key: circuits-${{ hashFiles('circuits/src/**', 'circuits/scripts/build.mjs', 'circuits/package-lock.json') }}
- uses: actions/cache/restore@v4
id: ptau
if: steps.build.outputs.cache-hit != 'true'
with:
path: circuits/build/pot17_dev.ptau
key: ptau-dev-17-v1
- name: Phase 1 (generated once, then cached)
if: steps.build.outputs.cache-hit != 'true' && steps.ptau.outputs.cache-hit != 'true'
run: node scripts/build.mjs --ptau 17
working-directory: circuits
timeout-minutes: 75
- uses: actions/cache/save@v4
if: steps.build.outputs.cache-hit != 'true' && steps.ptau.outputs.cache-hit != 'true'
with:
path: circuits/build/pot17_dev.ptau
key: ptau-dev-17-v1
- if: steps.build.outputs.cache-hit != 'true'
run: npm run build
working-directory: circuits
timeout-minutes: 30
- uses: actions/upload-artifact@v4
with:
name: circuits
# The verifiers and what proving needs; not the phase 1 files.
path: |
circuits/build/*.zkey
circuits/build/*_js/*.wasm
circuits/build/*.vkey.json
circuits/build/manifest.json
contracts/contracts/verifiers
retention-days: 3

contracts:
name: contracts
needs: circuits
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
Expand All @@ -110,14 +193,21 @@ jobs:
node-version: 24
cache: npm
cache-dependency-path: contracts/package-lock.json
- uses: actions/download-artifact@v4
with:
name: circuits
path: .
- run: npm ci
working-directory: contracts
- run: npm run build
working-directory: contracts
# The slowest of the three by a wide margin: it proves Groth16 end to
# end rather than mocking it.
# end rather than mocking it. REQUIRE_CIRCUITS turns a missing build
# into a failure rather than a quietly skipped suite.
- run: npm test
working-directory: contracts
env:
REQUIRE_CIRCUITS: "1"

# THE SCRIPTS ARE NOT CHECKED BY EITHER OF THE TWO ABOVE, which is easy
# to miss: `hardhat compile` builds Solidity and `hardhat test` only
Expand Down Expand Up @@ -156,8 +246,8 @@ jobs:
# Those stay manual, in `deploy-heroku.yml` and `deploy-phala.yml`, because
# deciding what the world talks to should be somebody's decision.
# THE TALLY CLI WAS IN NO JOB AT ALL, which is a strange thing to leave out:
# it is the auditor's path, the one that reads the encrypted ballots, adds
# them up and publishes the result with an IPFS receipt. Nothing here builds
# it is the auditor's path, the one that decrypts and proves the tally,
# publishes it with an IPFS receipt and checks a published one. Nothing here builds
# or runs it, so a dependency bump could break the official count and every
# tick would stay green.
#
Expand All @@ -175,6 +265,8 @@ jobs:
cache-dependency-path: scripts-tally/package-lock.json
- run: npm ci
working-directory: scripts-tally
# Type-checks `ballotCrypto.ts` too, which this CLI shares with the
# frontend: a change there that breaks the auditor's path fails here.
- run: npm run typecheck
working-directory: scripts-tally

Expand Down
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ contracts/coverage/
# Local deploy manifests are throwaway; amoy.json IS committed (frontend reads it)
contracts/deployments/local.json

# ------------- Circuits -------------
# Build outputs of circuits/scripts/build.mjs: the trusted setup, and the
# verifiers and proving files generated from it. Never committed, because they
# belong to ONE ceremony: a deployment generates its own and publishes them.
circuits/build/
contracts/contracts/verifiers/
frontend/public/circuits/

# ------------- React Frontend -------------
frontend/build/
frontend/dist/
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
7 changes: 4 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ Votain is an **end-to-end verifiable, anonymous, coercion-resistant voting dApp*
- `docs/dev/state.md`. Current milestone, pinned versions, technical debt.
- `docs/dev/conventions.md`. Binding development conventions.
- `docs/dev/architecture.md`. On-chain / off-chain / frontend / tally diagram.
- `docs/dev/glossary.md`. Semaphore, nullifier, SD-JWT, ERC-4337, Paillier, TEE, etc.
- `docs/dev/glossary.md`. Semaphore, nullifier, SD-JWT, ElGamal, Groth16, TEE, etc.

## Monorepo structure

```
votain/
├── contracts/ # Solidity 0.8.37 + Hardhat 3 + Semaphore V4
├── contracts/ # Solidity 0.8.37 + Hardhat 3
├── backend/ # Node.js Express SD-JWT issuer + World ID v4
├── frontend/ # React 19 + Vite + Tailwind 4 + ethers v6
├── scripts-tally/ # off-chain tally with paillier-bigint (to create)
├── circuits/ # ballot + tally circuits, trusted setup, generated verifiers
├── scripts-tally/ # off-chain tally, proof and --verify (auditor CLI)
├── docs/
│ ├── PLAN.md # iterative plan (source of truth for progress)
│ ├── dev/ # developer documentation
Expand Down
Loading
Loading