a system for not serious votes
An encrypted ranked-choice voting system. Voters get a standalone HTML ballot that works offline — no internet needed. They rank candidates, and the ballot generates an encrypted vote code (a text string) they send via WhatsApp, SMS, or any messaging app. The organizer pastes the codes into a reader script to decrypt and tally results using instant-runoff voting (IRV).
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ ballot.html│ vote │ WhatsApp │ paste │ reader.js │
│ (offline) │──code──> │ / SMS │──code──> │ (organizer) │
│ │ │ / Email │ │ │
│ Voter ranks│ │ │ │ Decrypts & │
│ candidates │ │ │ │ tallies IRV │
└─────────────┘ └──────────────┘ └──────────────┘
Security:
- Vote codes are RSA-2048 encrypted — only the organizer's private key can decrypt them
- Each voter token is single-use (HMAC-verified, no server-side token list needed)
- No personal data is stored — votes are anonymous
- The ballot works fully offline — no network requests
- Node.js v18 or later
No npm dependencies required — everything uses built-in Node.js modules.
Edit config.json:
{
"title": "Your Election Title",
"question": "Rank the candidates in order of your preference (drag to reorder):",
"options": [
{ "id": "Rick", "label": "Rick Astley" },
{ "id": "bob", "label": "Bob Smith" },
{ "id": "carol", "label": "Carol Williams" }
],
"receiverContact": "Send your code to +1-555-0100 on WhatsApp",
"tokenSecret": "pick-a-long-random-secret-here"
}| Field | Description |
|---|---|
title |
Election name shown on the ballot |
question |
Instruction text above the candidate list |
options |
Array of candidates — id is internal, label is displayed |
receiverContact |
Shown to voters after they generate their code — tells them where to send it |
tokenSecret |
Secret key used to sign voter tokens — keep this private |
node generate-keys.jsThis creates:
data/private.pem— keep secret, only the organizer needs thisdata/public.jwk— automatically embedded in the ballot
node generate-tokens.js <number-of-voters>Example:
node generate-tokens.js 30Tokens are printed to stdout. Each voter gets exactly one token. Tokens look like:
xETDAtjfBIG9ZQ.vgEpLnKbsP-AHkU4hqIRqPM47xRhDFVjeXkOhZGAobo
To save them to a file:
node generate-tokens.js 30 > tokens.txtnode build-ballot.jsThis generates ballot.html with your candidates and public key embedded. Distribute this file to voters (email, USB, AirDrop, etc.).
- Open
ballot.htmlin any browser (works offline) - Paste your voter token
- Drag candidates to rank them (#1 = top choice)
- Click Generate Vote Code
- Copy the code and send it to the organizer via WhatsApp/SMS/email
node reader.jsPaste vote codes one at a time. Type results to see the current tally and IRV winner. Type quit to exit.
node reader.js <vote-code>node reader.js --resultsExample output:
=== Community Election 2026 — Results ===
Votes cast: 12 of 30 tokens issued (40%)
First-choice votes:
Alice Johnson ████████████████████ 6
Bob Smith ██████████ 3
Carol Williams ██████████ 3
Instant-Runoff Voting:
Round 1: Alice Johnson: 6, Bob Smith: 3, Carol Williams: 3 -> Carol Williams eliminated
Round 2: Alice Johnson: 6, Bob Smith: 6
Round 3: Alice Johnson: 8, Bob Smith: 4 -> Alice Johnson wins
WINNER: Alice Johnson
To clear all vote data and start fresh (keeps your keys):
./clear.shThen regenerate tokens and rebuild the ballot:
node generate-tokens.js 30 > tokens.txt
node build-ballot.js| File | Purpose |
|---|---|
config.json |
Election title, candidates, token secret |
generate-keys.js |
Generates RSA-2048 key pair (run once) |
generate-tokens.js |
Generates voter tokens |
build-ballot.js |
Builds ballot.html from config + public key |
ballot.html |
The file voters open to vote (distribute this) |
reader.js |
Decrypts vote codes and tallies results |
clear.sh |
Wipes vote data and tokens |
data/private.pem |
RSA private key (NEVER share) |
data/public.jwk |
RSA public key (embedded in ballot automatically) |
data/voter-registry.json |
List of issued voter IDs (created by generate-tokens) |
data/used-voters.json |
Tracks which tokens have been used (created by reader) |
data/votes.json |
Anonymous vote records (created by reader) |
# 1. Edit config.json with your candidates and a secret
# 2. One-time setup
node generate-keys.js
# 3. Generate tokens and build ballot
node generate-tokens.js 30 > tokens.txt
node build-ballot.js
# 4. Send ballot.html + one token to each voter
# 5. When codes come in via WhatsApp:
node reader.js
# paste codes, type "results" to see the winner