{"id":50758584,"url":"https://github.com/flammafex/prestige","last_synced_at":"2026-06-11T07:33:15.192Z","repository":{"id":346474198,"uuid":"1126972726","full_name":"flammafex/prestige","owner":"flammafex","description":"  Anonymous verifiable voting — secret ballot, public proof. Cryptographic polls where no one can stuff the ballot and no one knows how you voted.","archived":false,"fork":false,"pushed_at":"2026-03-24T02:27:41.000Z","size":348,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-25T02:12:57.980Z","etag":null,"topics":["anonymous-voting","blind-signatures","cryptography","e-voting","nodejs","polls","privacy","secret-ballot","typescript","veribiable","voting"],"latest_commit_sha":null,"homepage":"https://prestige.vote","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flammafex.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-02T23:14:00.000Z","updated_at":"2026-03-24T02:27:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/flammafex/prestige","commit_stats":null,"previous_names":["flammafex/prestige"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/flammafex/prestige","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flammafex%2Fprestige","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flammafex%2Fprestige/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flammafex%2Fprestige/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flammafex%2Fprestige/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flammafex","download_url":"https://codeload.github.com/flammafex/prestige/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flammafex%2Fprestige/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34188272,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anonymous-voting","blind-signatures","cryptography","e-voting","nodejs","polls","privacy","secret-ballot","typescript","veribiable","voting"],"created_at":"2026-06-11T07:33:14.275Z","updated_at":"2026-06-11T07:33:15.158Z","avatar_url":"https://github.com/flammafex.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🗳️ Prestige\n\n## Features\n\n- **Ballot Secrecy**: No one learns how anyone voted (Freebird unlinkability)\n- **Eligibility**: Only authorized voters can vote (caller-signed Freebird token requests)\n- **No Double Voting**: One vote per eligible voter per ballot (nullifiers + one-time token spend tracking)\n- **Verifiability**: Anyone can verify the tally is correct (public reveals + commitments)\n- **Coercion Resistance**: Commit-reveal scheme prevents strategic voting\n- **Configurable Gates**: Pluggable mechanisms for ballot creation and voter eligibility\n- **Multiple Voting Methods**: Single choice, Approval, Ranked Choice (IRV), and Score voting\n- **Progressive Web App**: Install on any device, vote offline, receive notifications\n- **Enhanced Privacy Mode**: Timing attack protection, IP anonymization, Tor-friendly\n- **Audit Exports**: Download ballot data as JSON or CSV for third-party verification\n\n## Voting Methods\n\nPrestige supports multiple voting methods to suit different decision-making needs:\n\n| Method | Description | Best For |\n|--------|-------------|----------|\n| **Single Choice** | Traditional one-person-one-vote | Simple yes/no or binary decisions |\n| **Approval Voting** | Vote for all acceptable choices | Selecting from many similar options |\n| **Ranked Choice (IRV)** | Rank choices in preference order | Eliminating vote-splitting, finding consensus |\n| **Score Voting** | Rate each choice on a scale | Nuanced preference expression |\n\n### Ranked Choice Voting\n\nUses Instant-Runoff Voting (IRV):\n1. Count first-choice votes\n2. If no majority, eliminate lowest candidate\n3. Redistribute eliminated candidate's votes to next preferences\n4. Repeat until majority winner emerges\n\nResults display round-by-round elimination for full transparency.\n\n### Score Voting\n\nVoters assign scores (e.g., 0-5) to each choice:\n- Total scores determine winner\n- Average scores shown for comparison\n- Allows nuanced preference expression\n\n## Gate System\n\n\u003e \"No one owns the mechanism, but someone owns each instance.\"\n\nPrestige uses a two-layer gate system to control access:\n\n### Ballot Gates (Who Creates Ballots)\n\n| Gate | Description | Config |\n|------|-------------|--------|\n| `open` | Anyone can create ballots | (none) |\n| `owner` | Single admin key | `BALLOT_GATE_ADMIN_KEY` |\n| `delegation` | List of authorized keys | `BALLOT_GATE_DELEGATES` |\n| `freebird` | Token-gated creation | `BALLOT_GATE_FREEBIRD_ISSUER` |\n| `petition` | Anyone proposes, activates at threshold | `BALLOT_GATE_PETITION_THRESHOLD` |\n\n### Voter Gates (Who Can Vote - Instance Level)\n\n| Gate | Description | Config |\n|------|-------------|--------|\n| `open` | Anyone can vote | (none) |\n| `freebird` | Sybil-resistant via VOPRF tokens | Uses instance Freebird |\n| `allowlist` | Specific keys only | `VOTER_GATE_ALLOWLIST` |\n\n### Proposal Gates (For Petition Ballot Gate)\n\nWhen using `BALLOT_GATE=petition`, a nested proposal gate controls who can open petitions:\n\n| Gate | Description | Config |\n|------|-------------|--------|\n| `voters` | Anyone who can vote can propose (default) | (none) |\n| `delegation` | Specific keys only | `PETITION_PROPOSAL_DELEGATES` |\n\n### Eligibility Hierarchy\n\n```\nVoter requests token challenge (publicKey)\n        │\n        ▼\n┌───────────────────────┐\n│ Signed Challenge      │ ── Sign token:{ballotId}:{nonce}\n│ (publicKey + signature│    with local Ed25519 identity\n│  + nonce)             │\n└───────────────────────┘\n        │ valid\n        ▼\n┌───────────────────────┐\n│ Instance Voter Gate   │ ── Can this person vote HERE at all?\n│ (configured by owner) │\n└───────────────────────┘\n        │ yes\n        ▼\n┌───────────────────────┐\n│ Ballot Eligibility    │ ── Can this person vote on THIS question?\n│ (set by ballot        │    (can restrict, not expand)\n│  creator)             │\n└───────────────────────┘\n        │ yes\n        ▼\n┌───────────────────────┐\n│ Freebird Token Issue  │ ── Anonymous proof of eligibility\n└───────────────────────┘\n        │\n        ▼\n    Vote cast with\n    unlinkable proof\n```\n\n### Governance Models\n\n| Model | Ballot Gate | Voter Gate | Use Case |\n|-------|-------------|------------|----------|\n| Church | `owner` | `freebird` | Pastor sets agenda, verified members vote |\n| Committee | `delegation` | `allowlist` | Board proposes, authorized members vote |\n| Grassroots | `petition` | `freebird` | Anyone proposes, members activate and vote |\n| Open forum | `open` | `open` | Anyone can create and vote (MVP testing) |\n| Public poll | `owner` | `open` | Operator polls the public |\n\n## How It Works\n\n### Commit-Reveal Scheme\n\n1. **Voting Phase**: Voters submit encrypted commitments `H(choice || salt)` with nullifiers `H(secret || ballotId)`\n2. **Deadline Passes**: No more votes accepted\n3. **Reveal Phase**: Voters prove their commitment by revealing choice + salt\n4. **Finalization**: Tally computed from valid reveals, attested by witnesses\n\n### Privacy Guarantees\n\n- **Freebird VOPRF**: Eligibility tokens are unlinkable - issuer can't connect token to verifier\n- **Nullifiers**: Prevent double-voting without revealing identity\n- **Witness Attestations**: Cryptographic timestamps prove when votes were cast\n- **Timing Obfuscation**: Random delays prevent timing correlation attacks\n- **IP Anonymization**: Headers stripped in privacy mode\n\n## Privacy \u0026 Security\n\n### Enhanced Privacy Mode\n\nEnable privacy mode for high-stakes anonymous voting:\n\n```bash\nPRIVACY_MODE=true\nPRIVACY_MIN_DELAY_MS=100    # Random delay range\nPRIVACY_MAX_DELAY_MS=2000\nDISABLE_LOGGING=false       # Set true for maximum privacy\n```\n\nFeatures:\n- **Timing obfuscation**: Random delays on sensitive endpoints prevent timing attacks\n- **IP anonymization**: Strips `X-Forwarded-For` and similar headers\n- **Security headers**: CSP, HSTS, X-Frame-Options, and more\n- **Privacy-aware rate limiting**: Uses request fingerprints instead of IPs\n\n### Tor Hidden Service Deployment\n\nDeploy Prestige as a Tor hidden service for maximum anonymity:\n\n1. Install Tor: `apt install tor`\n2. Configure `/etc/tor/torrc`:\n   ```\n   HiddenServiceDir /var/lib/tor/prestige/\n   HiddenServicePort 80 127.0.0.1:3000\n   ```\n3. Restart Tor and get your `.onion` address:\n   ```bash\n   systemctl restart tor\n   cat /var/lib/tor/prestige/hostname\n   ```\n4. Set `ONION_LOCATION` to advertise your onion address\n5. Enable `PRIVACY_MODE=true` and `DISABLE_LOGGING=true`\n\n### Privacy Tips for Voters\n\nThe web UI includes privacy guidance:\n- Tor Browser usage recommendations\n- VPN recommendations (Mullvad, ProtonVPN, IVPN)\n- Device privacy best practices\n- Timing attack mitigation tips\n\n## Progressive Web App\n\nPrestige works as a Progressive Web App (PWA):\n\n- **Install on any device**: Add to home screen on mobile or desktop\n- **Offline support**: Queue votes/reveals when offline, sync when connected\n- **Local notifications**: Get reminded when ballots are ending or reveals are due (requires permission)\n- **Fast loading**: Service worker caching for instant access\n\nNote: vote sync requires a pre-issued eligibility proof. If a vote was queued without `proof`, replay is rejected.\n\n### Installing\n\n- **iOS**: Safari → Share → Add to Home Screen (iOS 16.4+ required for notifications)\n- **Android**: Chrome → Menu → Add to Home Screen\n- **Desktop**: Chrome/Edge → Install button in address bar\n\n### Notifications\n\nLocal 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:\n- Voting deadlines (1 hour before)\n- Reveal deadlines (30 minutes before)\n\n## Audit \u0026 Verification\n\n### Witness Attestations\n\nAll votes and results are timestamped by witness nodes:\n- 🙌 Witnessed by {domain} at {time}\n- Cryptographic signatures prove timestamp integrity\n- Multiple witnesses for Byzantine fault tolerance\n\n### Audit Exports\n\nDownload complete ballot data for independent verification:\n\n- **JSON Export**: Full audit data including all votes, reveals, attestations, and cryptographic proofs\n- **CSV Export**: Spreadsheet-friendly format with vote-level data and summary statistics\n\nExports include:\n- All vote commitments and nullifiers\n- All reveals with verification status\n- Witness attestations and signatures\n- Final tally computation data\n\n## Quick Start\n\n### Using Docker Compose\n\n```bash\n# Start all services\ndocker compose up -d\n\n# Create a ballot\ncurl -X POST http://localhost:3000/api/ballot \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"question\": \"Best framework?\", \"choices\": [\"React\", \"Vue\", \"Svelte\"]}'\n\n# Open the web UI\nopen http://localhost:3000\n```\n\n### Local Development\n\n```bash\n# Install dependencies\nnpm install\n\n# Build\nnpm run build\n\n# Start in mock mode (no external services required)\nUSE_MOCKS=true npm run dev\n\n# Or start with real services\nnpm run web\n```\n\n### Mock Mode\n\nFor testing without external services:\n```bash\nUSE_MOCKS=true npm run dev\n```\n\nThis uses mock adapters for Freebird, Witness, and HyperToken.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                         Browser                              │\n│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐          │\n│  │  IndexedDB  │  │   Crypto    │  │   Vote UI   │          │\n│  │  (keypair)  │  │ (commit/    │  │   (PWA)     │          │\n│  │             │  │  nullifier) │  │             │          │\n│  └─────────────┘  └─────────────┘  └─────────────┘          │\n│  ┌─────────────┐  ┌─────────────┐                           │\n│  │   Service   │  │   Offline   │                           │\n│  │   Worker    │  │   Queue     │                           │\n│  └─────────────┘  └─────────────┘                           │\n└─────────────────────────────────────────────────────────────┘\n                              │\n                              ▼\n┌─────────────────────────────────────────────────────────────┐\n│                    Prestige Server                           │\n│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐          │\n│  │   Ballot    │  │    Vote     │  │   Reveal    │          │\n│  │   Manager   │  │   Manager   │  │   Manager   │          │\n│  └─────────────┘  └─────────────┘  └─────────────┘          │\n│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐          │\n│  │   Tally     │  │  Security   │  │   Storage   │          │\n│  │   Manager   │  │ Middleware  │  │   (SQLite)  │          │\n│  └─────────────┘  └─────────────┘  └─────────────┘          │\n└─────────────────────────────────────────────────────────────┘\n        │                   │                   │\n        ▼                   ▼                   ▼\n┌───────────────┐  ┌───────────────┐  ┌───────────────┐\n│   Freebird    │  │    Witness    │  │  HyperToken   │\n│  (Issuer +    │  │   (Gateway +  │  │    Relay      │\n│   Verifier)   │  │    Cluster)   │  │  (optional)   │\n└───────────────┘  └───────────────┘  └───────────────┘\n```\n\nNote: HyperToken Relay is only needed for multi-node federation. Single-node deployments work without it.\n\n## API Reference\n\n### Gates\n\n```\nGET  /api/gates                    # Get gate configuration info\nPOST /api/gates/ballot/check       # Check if key can create ballots\nPOST /api/gates/voter/check        # Check if key can vote on instance\n```\n\n### Ballots\n\n```\nPOST /api/ballot              # Create ballot (requires gate check)\nGET  /api/ballot/:id          # Get ballot details\nGET  /api/ballot/:id/status   # Get status with vote count\nGET  /api/ballots             # List all ballots\n```\n\nFor ballots with `petition` gate:\n```\nPOST /api/ballot/:id/petition  # Sign petition to activate ballot\nGET  /api/ballot/:id/petition  # Get petition status\n```\n\n### Voting\n\n```\nPOST /api/vote                          # Cast vote (commitment + nullifier + proof)\nGET  /api/votes/:ballotId               # Get all commitments\nPOST /api/token/:ballotId/challenge     # Request one-time challenge { publicKey }\nPOST /api/token/:ballotId               # Request eligibility token { publicKey, signature, nonce, sybilProof? }\n```\n\nToken request flow:\n1. Call `/api/token/:ballotId/challenge` with `publicKey`.\n2. Sign `token:{ballotId}:{nonce}` with the same identity key.\n3. Call `/api/token/:ballotId` with `publicKey`, `signature`, and `nonce`.\n\n### Reveals\n\n```\nPOST /api/reveal              # Submit reveal (choice + salt)\nGET  /api/reveals/:ballotId   # Get all reveals\nGET  /api/reveals/:ballotId/stats  # Get reveal statistics\n```\n\n### Results\n\n```\nGET  /api/results/:ballotId              # Final tally with attestation\nGET  /api/results/:ballotId/live         # Live tally during reveal phase\nGET  /api/results/:ballotId/verify       # Verification report\nGET  /api/results/:ballotId/export/json  # Download full audit data (JSON)\nGET  /api/results/:ballotId/export/csv   # Download audit data (CSV)\n```\n\n## CLI Usage\n\n```bash\n# Create a ballot interactively\nprestige create\n\n# Vote on a ballot\nprestige vote \u003cballot-id\u003e\n\n# Reveal your vote after deadline\nprestige reveal \u003cballot-id\u003e\n\n# Check ballot status\nprestige status \u003cballot-id\u003e\n\n# View results\nprestige results \u003cballot-id\u003e\n\n# List recent ballots\nprestige list\n\n# Show gate configuration and your eligibility\nprestige gates\n\n# Sign a petition to activate a ballot (for petition gate)\nprestige petition \u003cballot-id\u003e\n\n# Check your voting eligibility\nprestige eligibility [ballot-id]\n\n# Check service health\nprestige health\n```\n\n## Data Model\n\n```typescript\ninterface Ballot {\n  id: string;\n  question: string;\n  choices: string[];\n  deadline: number;         // Voting ends\n  revealDeadline: number;   // Reveals must be submitted\n  eligibility: EligibilityConfig;\n  voteType: VoteTypeConfig; // single | approval | ranked | score\n  attestation: WitnessAttestation;\n}\n\ninterface Vote {\n  ballotId: string;\n  nullifier: string;    // H(voterSecret || ballotId)\n  commitment: string;   // H(choice || salt)\n  proof: FreebirdToken; // Proves eligibility\n  attestation: WitnessAttestation;\n}\n\ninterface Reveal {\n  ballotId: string;\n  nullifier: string;    // Links to original vote\n  choice: string;       // The actual choice\n  salt: string;         // Proves commitment was honest\n  voteData?: VoteData;  // Extended data for approval/ranked/score\n}\n\ntype VoteData =\n  | { type: 'single'; choice: string }\n  | { type: 'approval'; choices: string[] }\n  | { type: 'ranked'; rankings: string[] }\n  | { type: 'score'; scores: Record\u003cstring, number\u003e };\n```\n\n## Security Properties\n\n| Property | Mechanism |\n|----------|-----------|\n| Ballot Secrecy | Freebird VOPRF unlinkability |\n| Eligibility | Freebird token verification |\n| No Double Voting | Nullifier tracking + one-time token spend tracking |\n| Verifiability | Public commit-reveal scheme |\n| Timestamp Integrity | Witness BFT attestations |\n| Timing Attack Resistance | Random response delays |\n| IP Privacy | Header stripping in privacy mode |\n\n## Configuration\n\nEnvironment variables:\n\n```bash\n# Server\nPORT=3000\nDATA_DIR=/data\nTOKEN_CHALLENGE_TTL_MS=300000            # Token challenge nonce TTL in ms (default: 5 min)\n\n# Freebird (VOPRF Token System)\nFREEBIRD_ISSUER_URL=http://localhost:8081\nFREEBIRD_VERIFIER_URL=http://localhost:8082\n\n# Witness (BFT Timestamping)\nWITNESS_URL=http://localhost:8080\n\n# HyperToken Relay (optional - only needed for multi-node federation)\n# HYPERTOKEN_RELAY_URL=ws://localhost:3001\n\n# Ballot defaults\nDEFAULT_BALLOT_DURATION_MINUTES=1440       # 24 hours\nREVEAL_WINDOW_MINUTES=1440                 # 24 hours\nMIN_DURATION_MINUTES=1                     # Minimum allowed duration\n\n# Ballot Gate (who can create ballots)\nBALLOT_GATE=open                           # open | owner | delegation | freebird | petition\nBALLOT_GATE_ADMIN_KEY=\u003cpublic-key\u003e         # For owner gate (defaults to instance key)\nBALLOT_GATE_DELEGATES=key1,key2,key3       # For delegation gate\nBALLOT_GATE_FREEBIRD_ISSUER=\u003cissuer-id\u003e    # For freebird gate\n# Freebird ballot creation token must include issuerId + epoch\nBALLOT_GATE_PETITION_THRESHOLD=10          # For petition gate (default: 10)\n\n# Voter Gate (who can vote on this instance)\nVOTER_GATE=open                            # open | freebird | allowlist\nVOTER_GATE_ALLOWLIST=key1,key2,key3        # For allowlist gate\n\n# Proposal Gate (when BALLOT_GATE=petition)\nPETITION_PROPOSAL_GATE=voters              # voters | delegation\nPETITION_PROPOSAL_DELEGATES=key1,key2      # For delegation proposal gate\n\n# Enhanced Privacy Mode\nPRIVACY_MODE=false                         # Enable timing obfuscation \u0026 IP anonymization\nPRIVACY_MIN_DELAY_MS=100                   # Minimum random delay (ms)\nPRIVACY_MAX_DELAY_MS=2000                  # Maximum random delay (ms)\nDISABLE_LOGGING=false                      # Disable request logging\nONION_LOCATION=http://your.onion           # Advertise Tor hidden service\n```\n\nToken challenges are stored in-memory and expire automatically. They are cleared when the server restarts.\n\n## Testing\n\n```bash\n# Run all tests\nnpm test\n\n# Run integration tests\nnpm run test:integration\n\n# Run with coverage\nnpm test -- --coverage\n\n# Run in mock mode for manual testing\nUSE_MOCKS=true npm run dev\n```\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflammafex%2Fprestige","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflammafex%2Fprestige","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflammafex%2Fprestige/lists"}