+ sign and verify votes and prevent unverified updates
🗳️ P2P Verified Polling App
A decentralized, real-time polling application built with Nuxt 3, Yjs, and WebRTC. This app allows users to create and participate in polls where every vote is cryptographically signed and verified peer-to-peer, ensuring data integrity without a central authority "owning" the results.
🌟 Key Features
- Serverless Real-time Sync: Uses Yjs (CRDTs) and WebRTC to sync poll data directly between browsers. No database is required for live updates.
- Persistence with Nitro: While the logic is P2P, the Nuxt/Nitro backend provides a "Snapshot" service to ensure polls persist even after all peers go offline.
- Cryptographic Integrity: Every vote is signed using RSA-PSS (Web Crypto API). Each user has a unique private key (stored locally via
.pemfiles) to ensure votes cannot be forged or tampered with. - Chained Verification: Implements a "History-Signing" logic where each new vote signs the entire preceding state of the poll, creating a verifiable chain of trust.
- Privacy First: Users identify via UUIDs and Public/Private key pairs rather than traditional accounts.
⚙️ How It Works
1. Identity Creation
When a new user is created, the system generates a unique UUID (User ID) and an RSA Key Pair. The user is prompted to save their Private Key as a .pem file, named after their User ID (e.g., 550e8400-e29b.pem). This file acts as their "Passport"—it is never uploaded to the server and must be kept secure by the user.
2. Authentication
Upon returning to the app, users load their local .pem file. The application extracts the Private Key for signing and the UUID for identification. No passwords or central servers are involved in this local-first login process.
3. Joining a Poll
When a user joins a poll, the app fetches the latest binary snapshot from the server to populate a local Y.Doc. This ensures the user sees the current state immediately, even before connecting to other peers.
4. The P2P Mesh
The app establishes connections to other active voters via a WebRTC signaling server. Any changes made to the poll (adding options or voting) are broadcasted instantly to all peers using Conflict-free Replicated Data Types (CRDTs) to prevent sync conflicts.
5. Casting a Signed Vote
To ensure security, the voting process follows a strict cryptographic chain:
- The app captures the current list of votes.
- It appends the new vote data (User ID + Timestamp).
- It signs the entire array (the previous history + the new vote) using the user's RSA private key.
- The signed update is merged into the shared Yjs Map and broadcasted.
6. Distributed Verification
Whenever a peer receives a new update, they fetch the voter's Public Key from the API. They then verify that the signature matches the current state of the poll history. If a signature is invalid or the history has been tampered with, the vote is rejected by the peer's local state.
🛠️ Tech Stack
- Framework: Nuxt 3 (Vue 3 + TypeScript)
- Conflict-Free Replicated Data Types (CRDT): Yjs
- P2P Transport:
y-webrtc - Security: Web Crypto API (SubtleCrypto)
- Backend/Storage: Nitro (Nuxt's server engine) with filesystem storage drivers
AI Disclaimer
This App was developed with the assistance of AI.
Nuxt Minimal Starter
Look at the Nuxt documentation to learn more.
Setup
Make sure to install dependencies:
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
Development Server
Start the development server on http://localhost:3000:
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
Production
Build the application for production:
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
Locally preview production build:
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
Check out the deployment documentation for more information.