An open API service indexing awesome lists of open source software.

https://github.com/flammafex/prestige

Anonymous verifiable voting β€” secret ballot, public proof. Cryptographic polls where no one can stuff the ballot and no one knows how you voted.
https://github.com/flammafex/prestige

anonymous-voting blind-signatures cryptography e-voting nodejs polls privacy secret-ballot typescript veribiable voting

Last synced: 1 day ago
JSON representation

Anonymous verifiable voting β€” secret ballot, public proof. Cryptographic polls where no one can stuff the ballot and no one knows how you voted.

Awesome Lists containing this project

README

          

# πŸ—³οΈ Prestige

## Features

- **Ballot Secrecy**: No one learns how anyone voted (Freebird unlinkability)
- **Eligibility**: Only authorized voters can vote (caller-signed Freebird token requests)
- **No Double Voting**: One vote per eligible voter per ballot (nullifiers + one-time token spend tracking)
- **Verifiability**: Anyone can verify the tally is correct (public reveals + commitments)
- **Coercion Resistance**: Commit-reveal scheme prevents strategic voting
- **Configurable Gates**: Pluggable mechanisms for ballot creation and voter eligibility
- **Multiple Voting Methods**: Single choice, Approval, Ranked Choice (IRV), and Score voting
- **Progressive Web App**: Install on any device, vote offline, receive notifications
- **Enhanced Privacy Mode**: Timing attack protection, IP anonymization, Tor-friendly
- **Audit Exports**: Download ballot data as JSON or CSV for third-party verification

## Voting Methods

Prestige supports multiple voting methods to suit different decision-making needs:

| Method | Description | Best For |
|--------|-------------|----------|
| **Single Choice** | Traditional one-person-one-vote | Simple yes/no or binary decisions |
| **Approval Voting** | Vote for all acceptable choices | Selecting from many similar options |
| **Ranked Choice (IRV)** | Rank choices in preference order | Eliminating vote-splitting, finding consensus |
| **Score Voting** | Rate each choice on a scale | Nuanced preference expression |

### Ranked Choice Voting

Uses Instant-Runoff Voting (IRV):
1. Count first-choice votes
2. If no majority, eliminate lowest candidate
3. Redistribute eliminated candidate's votes to next preferences
4. Repeat until majority winner emerges

Results display round-by-round elimination for full transparency.

### Score Voting

Voters assign scores (e.g., 0-5) to each choice:
- Total scores determine winner
- Average scores shown for comparison
- Allows nuanced preference expression

## Gate System

> "No one owns the mechanism, but someone owns each instance."

Prestige uses a two-layer gate system to control access:

### Ballot Gates (Who Creates Ballots)

| Gate | Description | Config |
|------|-------------|--------|
| `open` | Anyone can create ballots | (none) |
| `owner` | Single admin key | `BALLOT_GATE_ADMIN_KEY` |
| `delegation` | List of authorized keys | `BALLOT_GATE_DELEGATES` |
| `freebird` | Token-gated creation | `BALLOT_GATE_FREEBIRD_ISSUER` |
| `petition` | Anyone proposes, activates at threshold | `BALLOT_GATE_PETITION_THRESHOLD` |

### Voter Gates (Who Can Vote - Instance Level)

| Gate | Description | Config |
|------|-------------|--------|
| `open` | Anyone can vote | (none) |
| `freebird` | Sybil-resistant via VOPRF tokens | Uses instance Freebird |
| `allowlist` | Specific keys only | `VOTER_GATE_ALLOWLIST` |

### Proposal Gates (For Petition Ballot Gate)

When using `BALLOT_GATE=petition`, a nested proposal gate controls who can open petitions:

| Gate | Description | Config |
|------|-------------|--------|
| `voters` | Anyone who can vote can propose (default) | (none) |
| `delegation` | Specific keys only | `PETITION_PROPOSAL_DELEGATES` |

### Eligibility Hierarchy

```
Voter requests token challenge (publicKey)
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Signed Challenge β”‚ ── Sign token:{ballotId}:{nonce}
β”‚ (publicKey + signatureβ”‚ with local Ed25519 identity
β”‚ + nonce) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ valid
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Instance Voter Gate β”‚ ── Can this person vote HERE at all?
β”‚ (configured by owner) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ yes
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Ballot Eligibility β”‚ ── Can this person vote on THIS question?
β”‚ (set by ballot β”‚ (can restrict, not expand)
β”‚ creator) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ yes
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Freebird Token Issue β”‚ ── Anonymous proof of eligibility
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
Vote cast with
unlinkable proof
```

### Governance Models

| Model | Ballot Gate | Voter Gate | Use Case |
|-------|-------------|------------|----------|
| Church | `owner` | `freebird` | Pastor sets agenda, verified members vote |
| Committee | `delegation` | `allowlist` | Board proposes, authorized members vote |
| Grassroots | `petition` | `freebird` | Anyone proposes, members activate and vote |
| Open forum | `open` | `open` | Anyone can create and vote (MVP testing) |
| Public poll | `owner` | `open` | Operator polls the public |

## How It Works

### Commit-Reveal Scheme

1. **Voting Phase**: Voters submit encrypted commitments `H(choice || salt)` with nullifiers `H(secret || ballotId)`
2. **Deadline Passes**: No more votes accepted
3. **Reveal Phase**: Voters prove their commitment by revealing choice + salt
4. **Finalization**: Tally computed from valid reveals, attested by witnesses

### Privacy Guarantees

- **Freebird VOPRF**: Eligibility tokens are unlinkable - issuer can't connect token to verifier
- **Nullifiers**: Prevent double-voting without revealing identity
- **Witness Attestations**: Cryptographic timestamps prove when votes were cast
- **Timing Obfuscation**: Random delays prevent timing correlation attacks
- **IP Anonymization**: Headers stripped in privacy mode

## Privacy & Security

### Enhanced Privacy Mode

Enable privacy mode for high-stakes anonymous voting:

```bash
PRIVACY_MODE=true
PRIVACY_MIN_DELAY_MS=100 # Random delay range
PRIVACY_MAX_DELAY_MS=2000
DISABLE_LOGGING=false # Set true for maximum privacy
```

Features:
- **Timing obfuscation**: Random delays on sensitive endpoints prevent timing attacks
- **IP anonymization**: Strips `X-Forwarded-For` and similar headers
- **Security headers**: CSP, HSTS, X-Frame-Options, and more
- **Privacy-aware rate limiting**: Uses request fingerprints instead of IPs

### Tor Hidden Service Deployment

Deploy Prestige as a Tor hidden service for maximum anonymity:

1. Install Tor: `apt install tor`
2. Configure `/etc/tor/torrc`:
```
HiddenServiceDir /var/lib/tor/prestige/
HiddenServicePort 80 127.0.0.1:3000
```
3. Restart Tor and get your `.onion` address:
```bash
systemctl restart tor
cat /var/lib/tor/prestige/hostname
```
4. Set `ONION_LOCATION` to advertise your onion address
5. Enable `PRIVACY_MODE=true` and `DISABLE_LOGGING=true`

### Privacy Tips for Voters

The web UI includes privacy guidance:
- Tor Browser usage recommendations
- VPN recommendations (Mullvad, ProtonVPN, IVPN)
- Device privacy best practices
- Timing attack mitigation tips

## Progressive Web App

Prestige works as a Progressive Web App (PWA):

- **Install on any device**: Add to home screen on mobile or desktop
- **Offline support**: Queue votes/reveals when offline, sync when connected
- **Local notifications**: Get reminded when ballots are ending or reveals are due (requires permission)
- **Fast loading**: Service worker caching for instant access

Note: vote sync requires a pre-issued eligibility proof. If a vote was queued without `proof`, replay is rejected.

### Installing

- **iOS**: Safari β†’ Share β†’ Add to Home Screen (iOS 16.4+ required for notifications)
- **Android**: Chrome β†’ Menu β†’ Add to Home Screen
- **Desktop**: Chrome/Edge β†’ Install button in address bar

### Notifications

Local notifications require user permission. On iOS, the PWA must be installed to the home screen before notifications can be enabled. The app schedules reminders for:
- Voting deadlines (1 hour before)
- Reveal deadlines (30 minutes before)

## Audit & Verification

### Witness Attestations

All votes and results are timestamped by witness nodes:
- πŸ™Œ Witnessed by {domain} at {time}
- Cryptographic signatures prove timestamp integrity
- Multiple witnesses for Byzantine fault tolerance

### Audit Exports

Download complete ballot data for independent verification:

- **JSON Export**: Full audit data including all votes, reveals, attestations, and cryptographic proofs
- **CSV Export**: Spreadsheet-friendly format with vote-level data and summary statistics

Exports include:
- All vote commitments and nullifiers
- All reveals with verification status
- Witness attestations and signatures
- Final tally computation data

## Quick Start

### Using Docker Compose

```bash
# Start all services
docker compose up -d

# Create a ballot
curl -X POST http://localhost:3000/api/ballot \
-H "Content-Type: application/json" \
-d '{"question": "Best framework?", "choices": ["React", "Vue", "Svelte"]}'

# Open the web UI
open http://localhost:3000
```

### Local Development

```bash
# Install dependencies
npm install

# Build
npm run build

# Start in mock mode (no external services required)
USE_MOCKS=true npm run dev

# Or start with real services
npm run web
```

### Mock Mode

For testing without external services:
```bash
USE_MOCKS=true npm run dev
```

This uses mock adapters for Freebird, Witness, and HyperToken.

## Architecture

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Browser β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ IndexedDB β”‚ β”‚ Crypto β”‚ β”‚ Vote UI β”‚ β”‚
β”‚ β”‚ (keypair) β”‚ β”‚ (commit/ β”‚ β”‚ (PWA) β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ nullifier) β”‚ β”‚ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Service β”‚ β”‚ Offline β”‚ β”‚
β”‚ β”‚ Worker β”‚ β”‚ Queue β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Prestige Server β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Ballot β”‚ β”‚ Vote β”‚ β”‚ Reveal β”‚ β”‚
β”‚ β”‚ Manager β”‚ β”‚ Manager β”‚ β”‚ Manager β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Tally β”‚ β”‚ Security β”‚ β”‚ Storage β”‚ β”‚
β”‚ β”‚ Manager β”‚ β”‚ Middleware β”‚ β”‚ (SQLite) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚ β”‚
β–Ό β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Freebird β”‚ β”‚ Witness β”‚ β”‚ HyperToken β”‚
β”‚ (Issuer + β”‚ β”‚ (Gateway + β”‚ β”‚ Relay β”‚
β”‚ Verifier) β”‚ β”‚ Cluster) β”‚ β”‚ (optional) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

Note: HyperToken Relay is only needed for multi-node federation. Single-node deployments work without it.

## API Reference

### Gates

```
GET /api/gates # Get gate configuration info
POST /api/gates/ballot/check # Check if key can create ballots
POST /api/gates/voter/check # Check if key can vote on instance
```

### Ballots

```
POST /api/ballot # Create ballot (requires gate check)
GET /api/ballot/:id # Get ballot details
GET /api/ballot/:id/status # Get status with vote count
GET /api/ballots # List all ballots
```

For ballots with `petition` gate:
```
POST /api/ballot/:id/petition # Sign petition to activate ballot
GET /api/ballot/:id/petition # Get petition status
```

### Voting

```
POST /api/vote # Cast vote (commitment + nullifier + proof)
GET /api/votes/:ballotId # Get all commitments
POST /api/token/:ballotId/challenge # Request one-time challenge { publicKey }
POST /api/token/:ballotId # Request eligibility token { publicKey, signature, nonce, sybilProof? }
```

Token request flow:
1. Call `/api/token/:ballotId/challenge` with `publicKey`.
2. Sign `token:{ballotId}:{nonce}` with the same identity key.
3. Call `/api/token/:ballotId` with `publicKey`, `signature`, and `nonce`.

### Reveals

```
POST /api/reveal # Submit reveal (choice + salt)
GET /api/reveals/:ballotId # Get all reveals
GET /api/reveals/:ballotId/stats # Get reveal statistics
```

### Results

```
GET /api/results/:ballotId # Final tally with attestation
GET /api/results/:ballotId/live # Live tally during reveal phase
GET /api/results/:ballotId/verify # Verification report
GET /api/results/:ballotId/export/json # Download full audit data (JSON)
GET /api/results/:ballotId/export/csv # Download audit data (CSV)
```

## CLI Usage

```bash
# Create a ballot interactively
prestige create

# Vote on a ballot
prestige vote

# Reveal your vote after deadline
prestige reveal

# Check ballot status
prestige status

# View results
prestige results

# List recent ballots
prestige list

# Show gate configuration and your eligibility
prestige gates

# Sign a petition to activate a ballot (for petition gate)
prestige petition

# Check your voting eligibility
prestige eligibility [ballot-id]

# Check service health
prestige health
```

## Data Model

```typescript
interface Ballot {
id: string;
question: string;
choices: string[];
deadline: number; // Voting ends
revealDeadline: number; // Reveals must be submitted
eligibility: EligibilityConfig;
voteType: VoteTypeConfig; // single | approval | ranked | score
attestation: WitnessAttestation;
}

interface Vote {
ballotId: string;
nullifier: string; // H(voterSecret || ballotId)
commitment: string; // H(choice || salt)
proof: FreebirdToken; // Proves eligibility
attestation: WitnessAttestation;
}

interface Reveal {
ballotId: string;
nullifier: string; // Links to original vote
choice: string; // The actual choice
salt: string; // Proves commitment was honest
voteData?: VoteData; // Extended data for approval/ranked/score
}

type VoteData =
| { type: 'single'; choice: string }
| { type: 'approval'; choices: string[] }
| { type: 'ranked'; rankings: string[] }
| { type: 'score'; scores: Record };
```

## Security Properties

| Property | Mechanism |
|----------|-----------|
| Ballot Secrecy | Freebird VOPRF unlinkability |
| Eligibility | Freebird token verification |
| No Double Voting | Nullifier tracking + one-time token spend tracking |
| Verifiability | Public commit-reveal scheme |
| Timestamp Integrity | Witness BFT attestations |
| Timing Attack Resistance | Random response delays |
| IP Privacy | Header stripping in privacy mode |

## Configuration

Environment variables:

```bash
# Server
PORT=3000
DATA_DIR=/data
TOKEN_CHALLENGE_TTL_MS=300000 # Token challenge nonce TTL in ms (default: 5 min)

# Freebird (VOPRF Token System)
FREEBIRD_ISSUER_URL=http://localhost:8081
FREEBIRD_VERIFIER_URL=http://localhost:8082

# Witness (BFT Timestamping)
WITNESS_URL=http://localhost:8080

# HyperToken Relay (optional - only needed for multi-node federation)
# HYPERTOKEN_RELAY_URL=ws://localhost:3001

# Ballot defaults
DEFAULT_BALLOT_DURATION_MINUTES=1440 # 24 hours
REVEAL_WINDOW_MINUTES=1440 # 24 hours
MIN_DURATION_MINUTES=1 # Minimum allowed duration

# Ballot Gate (who can create ballots)
BALLOT_GATE=open # open | owner | delegation | freebird | petition
BALLOT_GATE_ADMIN_KEY= # For owner gate (defaults to instance key)
BALLOT_GATE_DELEGATES=key1,key2,key3 # For delegation gate
BALLOT_GATE_FREEBIRD_ISSUER= # For freebird gate
# Freebird ballot creation token must include issuerId + epoch
BALLOT_GATE_PETITION_THRESHOLD=10 # For petition gate (default: 10)

# Voter Gate (who can vote on this instance)
VOTER_GATE=open # open | freebird | allowlist
VOTER_GATE_ALLOWLIST=key1,key2,key3 # For allowlist gate

# Proposal Gate (when BALLOT_GATE=petition)
PETITION_PROPOSAL_GATE=voters # voters | delegation
PETITION_PROPOSAL_DELEGATES=key1,key2 # For delegation proposal gate

# Enhanced Privacy Mode
PRIVACY_MODE=false # Enable timing obfuscation & IP anonymization
PRIVACY_MIN_DELAY_MS=100 # Minimum random delay (ms)
PRIVACY_MAX_DELAY_MS=2000 # Maximum random delay (ms)
DISABLE_LOGGING=false # Disable request logging
ONION_LOCATION=http://your.onion # Advertise Tor hidden service
```

Token challenges are stored in-memory and expire automatically. They are cleared when the server restarts.

## Testing

```bash
# Run all tests
npm test

# Run integration tests
npm run test:integration

# Run with coverage
npm test -- --coverage

# Run in mock mode for manual testing
USE_MOCKS=true npm run dev
```

## License

Apache-2.0