Hardware-Integrated Blockchain Voting System for department-level college elections.
- Frontend: React (Vite) + Tailwind + Framer Motion + Lucide + Recharts
- Backend: Node.js + Express + Mongoose + JWT + bcrypt + helmet
- Database: MongoDB (six collections: Voters, Votes, Blockchain Blocks, Candidates, Elections, Admins)
- Blockchain: Functional simulated chain — SHA-256 PoW, linked previous-hashes, smart-contract gates, masked candidate identities in blocks
secure-votechain/
├── docker-compose.yml # Local MongoDB
├── server/ # Express API + blockchain engine
│ ├── data/students.csv # Imported on boot
│ └── src/
│ ├── blockchain/ # Chain ops + smart contract
│ ├── models/ # 6 Mongoose collections
│ ├── routes/ # auth, voters, elections, candidates, votes, blockchain, admin
│ ├── utils/ # SHA-256 hashing, CSV importer
│ ├── seed.js # Idempotent: admin + voters + genesis + demo election
│ └── index.js
└── client/ # Vite + React + Tailwind
└── src/
├── pages/ # 10 pages
├── components/
└── context/ # Auth + Theme
cd ~/Downloads/secure-votechain
docker compose up -d(Or install MongoDB locally and ensure it's running on mongodb://localhost:27017.)
cd server
npm install
npm run devOn first boot the server will:
- Create the default admin (
admin/admin123) - Import every row of
data/students.csvinto the Voters collection - Generate a SHA-256 blockchain hash ID for each voter
- Mine the genesis block
- Create a demo election + 3 sample candidates
Backend runs on http://localhost:5000.
cd ../client
npm install
npm run devFrontend runs on http://localhost:5173 and proxies /api/* to the backend.
| Role | Username/Reg No. | Password |
|---|---|---|
| Admin | admin |
admin123 |
| Student | any register number from students.csv |
student123 |
All voter passwords are seeded to the same default. Rotate before any real use.
- Home — animated blockchain background, CTAs
- Student Login / Admin Login — separate, JWT-based
- Student Dashboard — live participation, eligibility, active elections
- Cast Vote — encrypted vote, block-mining receipt, QR transaction code
- Blockchain Explorer — every block, hash chain, integrity status (candidate identity is masked)
- Results — public turnout always; candidate-wise tallies sealed until end time, then auto-revealed; PDF export
- Admin Dashboard — create elections, add candidates, activate/release, voter search, CSV re-import, chain reset
- About, Security, Architecture — institutional documentation
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /api/auth/student/login |
public | Issue student JWT |
| POST | /api/auth/admin/login |
public | Issue admin JWT |
| GET | /api/auth/me |
any token | Current identity |
| GET | /api/elections |
public | All elections + stats |
| GET | /api/elections/:id |
public | One election + candidates |
| GET | /api/elections/:id/results |
public | Sealed before end; revealed after |
| POST | /api/elections |
admin | Create |
| PATCH | /api/elections/:id |
admin | Update / toggle active |
| POST | /api/elections/:id/release |
admin | Force-release results |
| GET | /api/candidates?election=:id |
public | List |
| POST | /api/candidates |
admin | Add candidate |
| POST | /api/votes/cast |
student | Cast vote (runs contract → mines block) |
| GET | /api/votes/me/:electionId |
student | My receipt |
| GET | /api/blockchain/blocks |
public | Explorer feed (masked candidate) |
| GET | /api/blockchain/status |
public | Chain integrity |
| GET | /api/blockchain/tx/:hash |
public | Look up a transaction |
| GET | /api/admin/overview |
admin | Dashboard widgets |
| GET | /api/admin/voters?q= |
admin | Search roster |
| POST | /api/admin/import-csv |
admin | Re-run CSV importer |
| POST | /api/admin/reset-chain |
admin | Dev only: wipe votes + blocks |
- When a vote is cast, the candidate ID is hashed into a
candidateMask = sha256(candidateId | electionId)before being written to the block. The block does not store the raw candidate ID. - The actual
(voterHashId → candidate)mapping lives only in theVotecollection, used for tallying. GET /api/elections/:id/resultscheckselection.areResultsVisible(). Before end time, it returns turnout only. After end time (or after an admin presses Release), it returns the candidate-wise counts.- Anyone hitting the Blockchain Explorer sees blocks, hashes, and transaction IDs but cannot infer the winner because the candidate is masked.
server/src/blockchain/chain.js— genesis, append, full-chain re-validation (validateChain()re-hashes every block and verifies the previous-hash link).server/src/blockchain/smartContract.js— the gates that run before block creation: election liveness, voter not voted, candidate registered, no duplicate vote row, chain still valid.server/src/utils/hash.js— SHA-256 helpers and a light proof-of-work miner (difficulty2=00…prefix; tweak inchain.jsif you want heavier PoW for demos).
# wipe everything:
docker compose down -v && docker compose up -d
# or, from the admin dashboard:
"Reset Chain" → clears votes + blocks + voter flags
"Re-import CSV" → reads server/data/students.csv again- Rotate
JWT_SECRETand all default passwords - Issue per-voter unique passwords instead of the shared default
- Move blockchain mining to a worker (current PoW is intentionally light)
- Pair voter login with hardware terminal attestation
- Add HTTPS termination + WAF