Skip to content

Repository files navigation

Secure Voting — Blockchain-Based Student Election System

A full-stack, auditable voting system built on Ethereum. Designed for student/college elections but extensible to any bounded-electorate vote (corporate governance, union elections, DAO proposals, etc.).

┌─────────────┐   HTTPS    ┌──────────────┐   JSON-RPC   ┌──────────────────┐
│   React     │ ─────────▶ │  Express API │ ───────────▶ │  Ethereum        │
│  Frontend   │            │   + SQLite   │              │  (Hardhat/       │
│ (MetaMask)  │ ◀────────  │  voter DB    │ ◀─────────── │   Sepolia)       │
│             │   signed   │  JWT auth    │              │                  │
│             │     tx     │              │              │  Voting.sol      │
└─────────────┘            └──────────────┘              └──────────────────┘

Security properties

Property Enforced by How
One vote per wallet Smart contract hasVoted mapping checked before every vote
Only approved voters can vote Smart contract isRegistered whitelist (admin-controlled)
Votes only during window Smart contract block.timestamp check against startTime/endTime
Only admin changes candidates/voters Smart contract onlyAdmin modifier (immutable admin address)
No candidate additions after start Smart contract Reverts once block.timestamp >= startTime
Reentrancy safety Smart contract nonReentrant guard on castVote
Auditable vote trail Smart contract Every vote emits a VoteCast event anyone can query
Identity ↔ wallet binding Backend Bcrypt-hashed password, wallet address pinned at registration
Brute-force resistance Backend Rate limiter on auth endpoints (20 req / 15 min)
Session security Backend Short-lived JWT (12h), HTTPS headers via Helmet
Secrets never exposed Frontend Users sign votes in their own wallet; never share keys

Tech stack

  • Smart contracts: Solidity ^0.8.24, Hardhat
  • Blockchain: Local Hardhat node (dev) → Sepolia (production)
  • Backend: Node.js, Express, SQLite (better-sqlite3), JWT, bcrypt, ethers.js v6
  • Frontend: React 18, Vite, ethers.js v6, Tailwind CSS v3, Framer Motion, Recharts, PapaParse, MetaMask via window.ethereum

UI/UX Enhancements

  • Civic Editorial Aesthetic: A clean, modern interface using Fraunces and JetBrains Mono with warm paper tones and an integrated Dark Mode toggle.
  • Rich Interactions: Powered by Framer Motion, featuring staggered entrances, interactive hover states, pulse animations, and animated confirmation modals.
  • Live Data Visualizations: Real-time updating bar charts using Recharts for live election results.
  • Admin Tools: Bulk voter approval via CSV upload (PapaParse) to streamline large-scale elections.
  • Skeleton Loaders: Polished initial loading states for an uninterrupted user experience.

Project layout

secure-voting/
├── contracts/Voting.sol         # The election smart contract
├── scripts/deploy.js            # Deploys + copies ABI to backend/frontend
├── test/Voting.test.js          # Full contract test suite
├── hardhat.config.js
├── backend/
│   └── src/
│       ├── server.js            # Express API
│       └── db.js                # SQLite voter registry
├── frontend/
│   └── src/
│       ├── App.jsx              # Main React app
│       ├── components/          # AuthBox, Ballot, AdminPanel, AuditLog
│       ├── hooks/useWallet.js   # MetaMask connection
│       └── utils/api.js         # Backend client
└── deployments/                 # Auto-generated contract addresses per network

Running it locally (first-time setup)

1. Install dependencies

git clone <this-repo> secure-voting
cd secure-voting
npm run install:all

This installs Hardhat (root), the backend, and the frontend in one go.

2. Start a local Ethereum node

In a dedicated terminal:

npx hardhat node

Hardhat prints ~20 test accounts with 10,000 ETH each. Copy account #0's private key — that will be your admin wallet. Also import a few of the other accounts into MetaMask for voter testing.

3. Deploy the contract

In a second terminal:

npm run deploy:local

You should see:

Voting deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Wrote contract.json to backend and frontend.

This script writes backend/src/contract.json and frontend/src/contract.json containing the deployed address + ABI — both apps pick that up automatically.

4. Configure the backend

cd backend
cp .env.example .env

Edit .env and set:

  • ADMIN_PRIVATE_KEY — the private key of Hardhat account #0 (from step 2)
  • JWT_SECRET — any long random string
  • BOOTSTRAP_ADMIN_PASSWORD_HASH — generate with:
    node -e "console.log(require('bcryptjs').hashSync('admin123', 12))"
  • BOOTSTRAP_ADMIN_WALLET — Hardhat account #0's public address

Then start the backend:

npm start

Expected output:

Bootstrapped admin user: admin
Loaded contract at: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Admin wallet configured: 0xf39F...
Backend listening on http://localhost:4000

5. Configure MetaMask

In MetaMask → Add a custom network:

  • Network name: Hardhat Local
  • RPC URL: http://127.0.0.1:8545
  • Chain ID: 31337
  • Currency: ETH

Import a few Hardhat-provided private keys as accounts.

6. Start the frontend

In a third terminal:

cd frontend
cp .env.example .env       # Default values are fine for local
npm run dev

Visit http://localhost:5173.


Demo walkthrough

As a voter:

  1. Click Connect wallet → pick a voter MetaMask account
  2. Click Register tab, pick a username + password (≥8 chars)
  3. Click Login
  4. Wait for an admin to approve you (see below)
  5. Once approved, select a candidate and click Cast ballot
  6. MetaMask prompts you to sign the transaction — confirm
  7. Your vote is recorded on-chain. The audit ledger updates live.

As an admin:

  1. Log in as admin (password = whatever you set during backend setup)
  2. Connect MetaMask with the admin wallet (Hardhat account #0)
  3. Add candidates — must be done before the election startTime
  4. Approve voters — each approval submits an on-chain registerVoter() tx. You can also use the Bulk Approve via CSV tool to upload a list of usernames.
  5. After endTime has passed, click Finalize election to lock results

Deploying to Sepolia (public testnet)

  1. Get free Sepolia ETH from https://sepoliafaucet.com
  2. Get an RPC URL from Alchemy or Infura
  3. Edit the root .env:
    cp .env.example .env
    # fill in SEPOLIA_RPC_URL and PRIVATE_KEY
  4. Deploy:
    npm run deploy:sepolia
  5. Update backend/.env with the new Sepolia RPC_URL and ADMIN_PRIVATE_KEY
  6. Update frontend/.env with VITE_CHAIN_ID=11155111
  7. Restart backend + frontend

Running the tests

npm test

Tests cover:

  • Constructor validation
  • Admin access control
  • Candidate management (before/after election start)
  • Voter registration (single + batch)
  • Voting (success, double-vote, unregistered, invalid candidate, outside window)
  • Winner computation
  • Finalization

Threat model & known limitations

What this protects against:

  • Ballot stuffing (one vote per wallet, enforced on-chain)
  • Admin tampering (admin can't modify votes after they're cast; events are immutable)
  • Off-hours voting (time-bounded)
  • Vote count forgery (counts come from on-chain state, not the backend DB)

What this does NOT protect against (by design, for a student election scope):

  • Vote secrecy — all votes are public. For secret ballots, upgrade to a commit-reveal scheme or use zk-SNARKs (e.g., Semaphore protocol).
  • Coercion / vote buying — if votes are public, buyers can verify purchases. Same mitigations as above.
  • Sybil attacks via the off-chain registry — depends on how rigorously the admin vets identity before approving a wallet. For real student elections, bind to SSO (e.g., university SAML) instead of username/password.
  • Compromised admin key — admin can approve fake voters. Mitigate by using a multisig (Gnosis Safe) as the contract admin and decentralizing approval.
  • RPC provider trust — the frontend trusts the RPC endpoint for reads. For critical elections, users should verify against multiple RPC providers or run their own node.

Extending to production

For a real, high-stakes election you'd want to add:

  1. Secret ballots via commit-reveal
    • Voter submits keccak256(candidateId, salt) during the voting phase
    • Reveals (candidateId, salt) during a separate reveal phase
  2. zk-voting (Semaphore, MACI) for anonymous but verifiable votes
  3. Multisig admin — replace the single admin with a Gnosis Safe
  4. Merkle-tree voter whitelist — scales cheaply to millions of voters
  5. SSO identity binding — OAuth/SAML against the institution's IDP
  6. Formal verification — run Certora or Slither over the contract
  7. Professional audit before ever handling a real election

License

MIT — use it, fork it, run elections with it.

About

Blockchain voting system using Ethereum, Solidity, React + MetaMask

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages