Private ballots. Public certainty. Quiet Quorum provides anonymous, credential-gated DAO voting with publicly verifiable results on Midnight Preview.
Eligible members prove membership without revealing their identity, cast Yes / No / Abstain, and consume a proposal-specific nullifier that prevents double voting. Observers can verify proposal rules, aggregate tallies, and quorum outcomes without learning who voted or how any individual voted.
This repository implements the Level 4 Core MVP:
- Compact contract with membership + nullifier + Yes/No/Abstain + finalize/quorum
- Browser dApp with Lace/1AM connect, deploy/join, admin flows, and private vote proving
- Privacy model documented below and in the UI Privacy panel
- CI workflow that typechecks, tests, and builds on every push
- Workspace layout:
contract/,frontend/,docs/,.github/
Still owned by the operator before submission packaging:
- Demo video link
private-dao-voting-midnight/
├── contract/
│ ├── src/dao-voting.compact
│ ├── managed/{keys,zkir,contract}
│ └── test/
├── frontend/
│ ├── src/{browser-circuit,components,hooks,voting,wallet,providers}
│ └── public/{keys,zkir}
├── docs/
├── .github/workflows/ci.yml
└── README.md
Requirements:
- Node.js 22+
- pnpm 10
- Compact CLI (
pragma language_version >= 0.23) for regenerating ZK artifacts
cp .env.example .env
pnpm install
pnpm test
pnpm typecheck
pnpm devApp: http://localhost:5176
Regenerate Compact bindings and ZK assets after circuit changes:
pnpm build:zkProduction frontend build:
pnpm build- Connect Lace or 1AM on Midnight Preview.
- Deploy voting contract (admin).
- Register demo voter root.
- Create and open proposal.
- Select a demo voter credential and cast Yes / No / Abstain.
- Confirm the public tally updates.
- Attempt a second vote with the same credential → rejected (
NULLIFIER_SPENT). - Optionally close and finalize to publish Passed / Failed / QuorumFail.
- Contract address and administrator commitment
- Accepted voter Merkle root
- Proposal identifiers, content digests, open/close slots, quorum config
- Aggregate Yes / No / Abstain counters
- Proposal-specific spent nullifiers
- Final status: Passed, Failed, QuorumFail, or Cancelled
- Voter secret and leaf salt
- Merkle authentication path and leaf index bits
- Ballot choice before it is applied to a public counter branch
| Data | Audience | Notes |
|---|---|---|
| Tallies, nullifiers, proposal rules | Everyone | Required for public auditability |
| Which counter moved / when | Everyone | Inherent to live public counters |
| Voter identity ↔ ballot mapping | Nobody | Not present in public state |
| Merkle path / voter secret | Prover only | Never logged or submitted in cleartext |
Wallet and network metadata remain outside the ZK privacy boundary.
VITE_NETWORK_ID=preview
# Leave unset for 1AM/ProofStation proving:
# VITE_PROOF_SERVER_URL=
# Local Docker proving with Lace:
# VITE_PROOF_SERVER_URL=http://localhost:6300
# VITE_ZK_CONFIG_BASE_URL=
# VITE_DEFAULT_CONTRACT=| Artifact | Value |
|---|---|
| Preview contract address | 40ef8f3376c5f077867770a40101a75b3f71084cfffd18514538a993ad828d8d |
| CI | .github/workflows/ci.yml |
| Product X profile | @QuietQuorum |
| Demo video | https://drive.google.com/file/d/15hAAR9ZoMevzH8thn772YUoG6Y_1F6jK/view?usp=sharing |
A DAO member proves they are on the approved voter list without revealing who they are, casts a private Yes/No/Abstain, and is blocked from voting twice by a proposal-specific nullifier. Anyone can verify the public tally and quorum outcome on Midnight Preview.