Private decision infrastructure for organizations on Stellar.
Run secure, anonymous votes where participation is confidential,
results are verifiable, and records are tamper-proof on the blockchain.
AnonVote solves a fundamental problem with digital voting: most tools expose voter identity, store results on a server only you control, and provide no independent way to verify outcomes.
AnonVote is different. Identity is cryptographically separated from the ballot at every layer. Every vote is anchored to the Stellar blockchain, so results can be verified by anyone — without trusting AnonVote's servers.
| Property | How it's enforced |
|---|---|
| One person, one vote | Cryptographic token system, not policy |
| Voter anonymity | Structural unlinkability — no database join between identity and token tables |
| Result integrity | AES-256-GCM encrypted votes anchored to Stellar |
| Auditability | Public Stellar transaction links; anyone can verify |
- Features
- Who It's For
- How It Works
- Tech Stack
- Getting Started
- Project Structure
- API Reference
- Privacy Design
- Stellar Integration
- Running Tests
- Roadmap
- License
- Anonymous token issuance — voters receive a one-time 32-byte CSPRNG token; no identity is stored alongside it
- Encrypted vote submission — vote payloads are AES-256-GCM encrypted; tallying decrypts only the option selection
- Blockchain audit trail — every vote and audit event is written to Stellar as a
manageDataoperation - Public verification — anyone can confirm results independently via Stellar transaction IDs
- Weighted & delegated voting — flexible vote-weight configuration and delegation support
- Ranked-choice / multi-round voting — supports complex election formats
- Blind vote verification — voters can self-verify their ballot was counted without exposing identity
- Configurable rate limiting — admin-controlled presets to prevent abuse
- Real-time notifications — ballot created, vote cast, results published
- Email notifications — powered by Resend (ballot creation + results)
- Token reissue flow — lost token recovery without enabling double-voting
- WCAG accessibility — aria labels, roles, and live regions across all components
- Mobile responsive — fully functional on iOS and Android browsers
| Sector | Use Cases |
|---|---|
| Education | Student elections, faculty votes, course feedback |
| Corporate | Policy votes, leadership surveys, board approvals |
| Communities | Governance decisions, membership votes, program approvals |
Eligible Voter List
│
▼
Identity Manager ──► Anonymous Token (one per voter)
│
▼
Vote Submission ──► Encrypted Vote Record
│
▼
Stellar Blockchain ──► Immutable Audit Trail
│
▼
Result Engine ──► Public Verified Results
Administrator flow
- Register your organization at
/registerand log in - Create a ballot — topic, options, deadline, and eligible voter list (CSV upload)
- After the voting period ends, results are automatically published and anchored to Stellar
Voter flow
- Receive a voting link from your organization admin
- Enter your voter identifier (email, employee ID, etc.) to receive a one-time anonymous token
- Use the token to cast your encrypted vote
- After the deadline, verify the result at
/results/:ballotId
Public verification
Anyone can visit /results/:ballotId and independently confirm the outcome via the Stellar transaction link — no trust required.
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite, TailwindCSS, React Router v6 |
| Backend | Node.js 20, Express, TypeScript |
| Database | PostgreSQL 15 + Prisma ORM |
| Blockchain | Stellar SDK (Testnet / Mainnet) |
| Auth | JWT via HTTP-only cookies, bcrypt |
| Crypto | AES-256-GCM vote encryption, SHA-256 identity hashing |
| Resend | |
| Testing | Vitest, React Testing Library |
- Node.js 20+
- Docker (for PostgreSQL)
- A Stellar account (create a Testnet account)
git clone https://github.com/Just-Bamford/AnonVote.git
cd AnonVotecp .env.example .envOpen .env and fill in your values:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/anonvote
JWT_SECRET=your-secret-here
STELLAR_SECRET_KEY=your-stellar-secret-key
BALLOT_ENCRYPTION_KEY=your-32-byte-hex-key
NODE_ENV=developmentTip: Generate a secure encryption key with
openssl rand -hex 32
docker-compose up -dcd backend && npm install && npx prisma migrate dev
cd ../frontend && npm install# In separate terminals:
npm run dev:backend # → http://localhost:3001
npm run dev:frontend # → http://localhost:5173AnonVote/
├── backend/
│ ├── src/
│ │ ├── routes/ # API route handlers
│ │ ├── services/ # Business logic (identity, ballot, privacy, result engines)
│ │ ├── middleware/ # Auth, rate limiting, error handling
│ │ ├── utils/ # Crypto helpers, deadline scheduler
│ │ └── tests/ # Unit, integration, and E2E tests
│ └── prisma/ # Database schema and migrations
├── frontend/
│ └── src/
│ ├── pages/ # All UI pages
│ ├── components/ # Reusable UI components
│ ├── hooks/ # useAuth, useBallot
│ └── api/ # Axios API client
├── shared/ # Shared TypeScript types
├── docker-compose.yml # PostgreSQL local setup
└── .env.example # Environment variable template
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/organizations |
— | Register organization |
POST |
/api/organizations/login |
— | Admin login |
POST |
/api/organizations/logout |
Session | Admin logout |
GET |
/api/organizations/me |
Session | Get current org |
PATCH |
/api/organizations/me |
Session | Update org name / email |
PATCH |
/api/organizations/password |
Session | Change password |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/ballots |
Session | List org ballots |
POST |
/api/ballots |
Session | Create ballot |
GET |
/api/ballots/:id |
— | Get ballot (public) |
PATCH |
/api/ballots/:id |
Session | Edit ballot |
DELETE |
/api/ballots/:id |
Session | Delete ballot |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/eligibility |
Session | Upload voter list |
POST |
/api/tokens |
— | Request voter token |
POST |
/api/tokens/reissue |
— | Reissue lost token (if not yet voted) |
POST |
/api/votes |
— | Submit vote |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/results/:ballotId |
— | Get published result |
POST |
/api/results/:ballotId/tally |
Session | Manually close and tally ballot |
GET |
/api/audit/:ballotId |
— | Get audit event counts |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/admin/rate-limit |
Session | Get rate limit settings |
PATCH |
/api/admin/rate-limit |
Session | Update rate limit preset |
GET |
/api/admin/tokens-issued |
Session | Total tokens issued across ballots |
AnonVote's privacy model is structural, not policy-based. The key properties:
- Voter identifiers are SHA-256 hashed before storage — originals are never recoverable from the database
- Voter tokens are 32-byte CSPRNG values — only their hash is stored server-side
- No database join exists between the eligibility table and the token table — unlinkability is enforced at the schema level
- Vote payloads are AES-256-GCM encrypted — the tally process decrypts only the option selection, nothing else
- Audit logs record event counts only — no identity, no token values, ever
This means that even with full database access, it is computationally infeasible to link a vote back to an individual voter.
All votes and audit events are written to Stellar as manageData operations on a dedicated AnonVote account. Each record's Stellar transaction ID is stored in the database and surfaced on the public verification page.
This means anyone — not just AnonVote — can independently confirm that a result is legitimate by checking the Stellar ledger directly.
Testnet vs Mainnet
Stellar Testnet is used by default for development. To switch to Mainnet, update your .env:
STELLAR_SECRET_KEY=your-mainnet-secret-key
STELLAR_NETWORK=mainnetTests require a running PostgreSQL instance.
# Backend (unit + integration + E2E)
npm run test:backend
# Frontend (Vitest + React Testing Library, 28 tests)
npm run test:frontendCoverage includes: crypto utilities, organization registration and login, token issuance, vote submission, audit counts, and a full end-to-end voting flow.
- Organization registration and admin auth
- Ballot creation with eligibility list upload
- Anonymous token issuance
- Encrypted vote submission
- Stellar blockchain recording
- Automatic tally and result publication
- Public verification page
- Weighted voting
- Delegated voting
- Multi-round / ranked-choice voting
- Blind vote verification (self-verification without identity exposure)
- Soroban smart contracts (stub — correct stellar-sdk v12 APIs, ready to wire)
- Frontend test suite (Vitest + React Testing Library, 28 tests)
- WCAG accessibility (aria labels, roles, live regions)
- Performance optimization (lazy loading, code splitting)
- Stellar consensus timestamps in audit log
- Token reissue flow
- Edit ballot (topic, deadline, eligibility list, vote-aware field locking)
- Manual close & tally
- Configurable rate limiting
- Real-time notifications
- Avatar upload with navbar sync
- Mobile responsiveness
- Email notifications via Resend
- Landing page (hero, how it works, FAQ, CTA)
Pull requests are welcome. For significant changes, please open an issue first to discuss what you'd like to change.