Skip to content

Prachet-Dev-Singh/zk-ranked-voting

Repository files navigation

ZK-Ranked Voting System

A complete Zero-Knowledge Proof based ranked voting system that ensures vote privacy, verifiability, and secure aggregation. This system allows voters to rank candidates privately while proving vote validity and calculating results without revealing individual choices. The demo video is attached in the github repo itself, and here is the link for the same: https://drive.google.com/file/d/17ldeaRUjjqomlNvkIGU0ia72M-XmkW_T/view?usp=sharing

🎯 Project Overview

This project implements a fully zero-knowledge ranked voting system with:

  • Private Voting: Individual vote rankings remain completely private
  • Zero-Knowledge Tallying: Aggregated results calculated without revealing individual votes
  • Circuit Security Verification: Automated verification using Picus to ensure circuits are properly constrained
  • Tie-Breaking: Deterministic tie-breaking mechanism for equal scores
  • Modern Web Interface: Clean, responsive UI built with Next.js and Tailwind CSS

✨ Key Features

πŸ” Privacy & Security

  • Vote Privacy: Rankings are hashed using Poseidon commitments - never revealed
  • ZK Proofs: Each vote generates a zero-knowledge proof of validity
  • Double-Voting Prevention: Cryptographic nullifiers prevent duplicate votes
  • Circuit Verification: Picus-verified circuits ensure no information leakage

πŸ“Š Voting & Tallying

  • Ranked Voting: Voters rank candidates (1-4, no duplicates)
  • Borda Count: Aggregated scoring using Borda count method
  • ZK Tally Proof: Zero-knowledge proof of correct aggregation
  • Tie-Breaking: Alphabetical ordering for deterministic tie resolution

πŸ›‘οΈ Security Verification

  • Picus Integration: Automated circuit verification for underconstrained signals
  • Formal Verification: Circuits verified as "properly constrained" (safe)
  • Transparent Process: All proofs are verifiable by anyone

πŸ—οΈ Technology Stack

  • Circom 2.0: Zero-knowledge circuit language
  • SnarkJS: ZK-SNARK proof generation and verification (Groth16)
  • Picus: Formal verification tool for circuit security
  • Next.js 14: React framework for web interface
  • Tailwind CSS: Modern styling
  • Poseidon Hash: Cryptographic hash function for commitments
  • TypeScript: Type-safe development

πŸ“‹ Prerequisites

Before you begin, ensure you have:

  • Node.js (v18 or higher) and npm
  • Circom 2.0 compiler (Installation Guide)
  • SnarkJS (installed via npm)
  • Git (for version control)

Optional (for Circuit Verification)

  • Picus: For automated circuit security verification
  • Racket: Required by Picus
  • cvc5: SMT solver for Picus

πŸš€ Quick Start

1. Clone and Install

git clone <your-repo-url>
cd zkp
npm install

2. Compile Circuits

npm run compile

This compiles both:

  • rankedVoting.circom - Individual vote verification circuit
  • voteTally.circom - Zero-knowledge vote aggregation circuit

3. Generate Trusted Setup

For Voting Circuit:

cd circuits/artifacts

# Generate Powers of Tau
snarkjs powersoftau new bn128 14 pot14_0000.ptau -v
snarkjs powersoftau contribute pot14_0000.ptau pot14_0001.ptau --name="First contribution" -v
snarkjs powersoftau prepare phase2 pot14_0001.ptau pot14_final.ptau -v

# Generate zkey for rankedVoting
snarkjs groth16 setup rankedVoting.r1cs pot14_final.ptau rankedVoting_0000.zkey
snarkjs zkey contribute rankedVoting_0000.zkey rankedVoting_0001.zkey --name="1st Contributor" -v
snarkjs zkey export verificationkey rankedVoting_0001.zkey verification_key.json

For Tally Circuit:

# Generate zkey for voteTally
npm run setup:tally

Or manually:

cd circuits/artifacts
snarkjs groth16 setup voteTally.r1cs pot14_final.ptau voteTally_0000.zkey
snarkjs zkey contribute voteTally_0000.zkey voteTally_0001.zkey --name="1st Contributor" -v
snarkjs zkey export verificationkey voteTally_0001.zkey voteTally_verification_key.json

4. Copy Files to Public Directory

# Copy voting circuit files
copy circuits\artifacts\rankedVoting_js\rankedVoting.wasm public\circuits\
copy circuits\artifacts\rankedVoting_0001.zkey public\circuits\
copy circuits\artifacts\verification_key.json public\circuits\

# Copy tally circuit files
copy circuits\artifacts\voteTally_js\voteTally.wasm public\circuits\
copy circuits\artifacts\voteTally_0001.zkey public\circuits\
copy circuits\artifacts\voteTally_verification_key.json public\circuits\voteTally_verification_key.json

5. Run the Application

npm run dev

Open http://localhost:3000 in your browser.

πŸ” Circuit Verification with Picus

Before running locally, verify your circuits are secure:

# Using local Picus installation (Windows CMD)
npm run verify:local:cmd

# Using local Picus installation (Git Bash)
npm run verify:local:bash

Expected Result: Both circuits should show "The circuit is properly constrained" (safe).

For detailed Picus setup, see the Picus documentation.

πŸ§ͺ Testing the System

Test Zero-Knowledge Properties

  1. Start the application: npm run dev
  2. Admin Setup: Set number of voters (1-10 for current circuit)
  3. Submit Votes:
    • Enter voter IDs
    • Rank candidates (1-4, no duplicates)
    • Verify vote hash is generated (not rankings)
  4. Check Privacy:
    • Open browser DevTools β†’ Application β†’ Local Storage
    • Verify rankings are NOT stored in plain text
  5. Generate Tally:
    • Wait for all votes to be collected
    • Click "Generate Zero-Knowledge Tally"
    • Verify aggregated scores are shown (not individual votes)

See TEST_ZERO_KNOWLEDGE.md for comprehensive testing guide.

πŸ“ Project Structure

zkp/
β”œβ”€β”€ circuits/
β”‚   β”œβ”€β”€ rankedVoting.circom      # Vote verification circuit
β”‚   β”œβ”€β”€ voteTally.circom          # Zero-knowledge tally circuit
β”‚   └── artifacts/                # Compiled circuit files (gitignored)
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ AdminSetup.tsx       # Election configuration
β”‚   β”‚   β”œβ”€β”€ VotingInterface.tsx  # Voting UI
β”‚   β”‚   └── ResultsDisplay.tsx  # Results with ZK tally
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ zkp.js               # ZKP utilities (voting)
β”‚   β”‚   β”œβ”€β”€ tallyZKP.js          # ZKP utilities (tallying)
β”‚   β”‚   β”œβ”€β”€ voteStorage.ts       # Local storage management
β”‚   β”‚   └── resultsCalculator.ts # Results calculation
β”‚   β”œβ”€β”€ pages/
β”‚   β”‚   β”œβ”€β”€ index.tsx            # Main page
β”‚   β”‚   └── _app.tsx             # Next.js app wrapper
β”‚   └── styles/
β”‚       └── globals.css           # Global styles
β”œβ”€β”€ scripts/
β”‚   β”œβ”€β”€ compile.js               # Circuit compilation
β”‚   β”œβ”€β”€ setup-tally.js           # Tally trusted setup
β”‚   β”œβ”€β”€ verify-picus-local.bat   # Picus verification (Windows)
β”‚   └── verify-picus-local.sh    # Picus verification (Linux/Mac)
β”œβ”€β”€ public/
β”‚   └── circuits/                 # Circuit files for local execution
β”œβ”€β”€ package.json
β”œβ”€β”€ next.config.js
β”œβ”€β”€ tsconfig.json
└── README.md

πŸ” How It Works

1. Vote Submission

  1. Voter ranks candidates (1-4, no duplicates)
  2. System generates:
    • Random secret
    • Vote hash: Poseidon(rankings[0], rankings[1], secret)
    • Nullifier: Poseidon(voterId, secret)
  3. Circuit verifies (without revealing rankings):
    • All rankings in valid range [1, 4]
    • No duplicate rankings
    • Hash commitment matches vote
  4. ZK Proof generated: Proves validity without revealing data

2. Vote Tallying

  1. All votes collected: System waits for admin-set number of voters
  2. ZK Tally Proof generated:
    • Verifies all vote commitments are valid
    • Calculates aggregated Borda scores
    • Proves aggregation correctness
    • Individual rankings remain private
  3. Results displayed: Only aggregated scores shown

3. Tie-Breaking

When candidates have equal scores:

  • Primary: Sort by Borda count score (descending)
  • Tie-Breaker: Alphabetical order (deterministic)
  • Visual Indicators: Tied candidates marked with 🀝 icon

See TIE_BREAKING.md for details.

βš™οΈ Configuration

Circuit Vote Limit

The voteTally.circom circuit is currently compiled for 10 votes maximum. To support more votes:

  1. Edit circuits/voteTally.circom (last line):
    component main = VoteTally(4, 20); // Change 10 to desired max
  2. Recompile: npm run compile
  3. Regenerate trusted setup: npm run setup:tally
  4. Copy files to public/circuits/

See CIRCUIT_VOTE_LIMIT.md for details.

πŸ“š Documentation

πŸ”¬ Security Analysis

Zero-Knowledge Properties Verified

βœ… Vote Privacy: Rankings never revealed in stored data or proofs
βœ… Tally Privacy: Aggregated results calculated without revealing individual votes
βœ… Circuit Security: Picus verified circuits are properly constrained
βœ… Proof Validity: All proofs are cryptographically verifiable

Privacy Considerations

  • Encrypted Rankings: Temporarily stored for tally proof generation (cleared after tally)
  • Base64 Encoding: Used for demo (production should use proper encryption)
  • Local Storage: All data stored client-side (no server)

See ZERO_KNOWLEDGE_ANALYSIS.md for detailed analysis.

πŸ“Š Results Interpretation

Vote Results

  • Vote Hash: Cryptographic commitment (cannot be reversed)
  • ZK Proof: Mathematical proof of validity (doesn't reveal rankings)
  • Nullifier: Prevents double-voting (doesn't reveal identity)

Tally Results

  • Aggregated Scores: Total Borda count points per candidate
  • Tally Proof: ZK proof that aggregation is correct
  • Individual Votes: Never revealed

πŸ“ License

MIT License

Note: This is a proof-of-concept implementation. For production use, additional security measures, proper encryption, and server-side infrastructure would be required.

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors