From bc5e2eead8b32d4c53dcd8f23fbb352340291e87 Mon Sep 17 00:00:00 2001 From: 1ynx Date: Sat, 4 Apr 2026 22:36:17 +0200 Subject: [PATCH] + create user with public/private key + sign and verify votes and prevent unverified updates --- README.md | 54 +- app/app.vue | 93 +- app/components/Poll.vue | 7 +- app/components/PollList.vue | 4 +- app/composables/usePoll.ts | 62 +- app/composables/user.ts | 2 + app/utils/crypto.ts | 145 + app/utils/types.ts | 13 +- nuxt.config.ts | 4 + package-lock.json | 10118 ++++++++++++++++++++++++++++++++++ server/api/polls/[id].ts | 35 + server/api/users/[id].ts | 41 + server/middleware/uuid.ts | 24 - server/utils/crypto.ts | 86 + server/utils/types.ts | 36 + 15 files changed, 10672 insertions(+), 52 deletions(-) create mode 100644 app/composables/user.ts create mode 100644 package-lock.json create mode 100644 server/api/users/[id].ts delete mode 100644 server/middleware/uuid.ts create mode 100644 server/utils/crypto.ts create mode 100644 server/utils/types.ts diff --git a/README.md b/README.md index e57ef73..4bd3534 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,56 @@ -# P2P Poll App +# πŸ—³οΈ 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 `.pem` files) 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](https://nuxt.com/) (Vue 3 + TypeScript) +* **Conflict-Free Replicated Data Types (CRDT):** [Yjs](https://yjs.dev/) +* **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 diff --git a/app/app.vue b/app/app.vue index 49459ce..53be88f 100644 --- a/app/app.vue +++ b/app/app.vue @@ -25,7 +25,8 @@ input { font-size: 1rem; } -button { +button, +.button { background: #3b82f6; color: white; border: none; @@ -36,7 +37,8 @@ button { transition: background 0.2s; } -button:hover { background: #2563eb; } +button:hover, +.button:hover { background: #2563eb; } .status { font-size: 0.85rem; @@ -61,6 +63,12 @@ button:hover { background: #2563eb; } font-size: 0.7rem; background: #64748b; } + +/* Hide the actual file input */ +input[type="file"] { + display: none; +} + \ No newline at end of file diff --git a/app/components/Poll.vue b/app/components/Poll.vue index b676d4c..a782f30 100644 --- a/app/components/Poll.vue +++ b/app/components/Poll.vue @@ -44,7 +44,7 @@

Poll: {{ activePollId }}

Note: Add at least one Option to save the Poll.

-
+
@@ -54,7 +54,7 @@ {{ optionName }}
{{ votes.length }} {{ votes.length === 1 ? 'vote' : 'votes' }} - +
@@ -71,11 +71,10 @@ newOption.value = ''; }; - const userGuid = useCookie('user_guid'); const voted = (votes: SignedData[]) => { for(let vote of votes){ - if(vote.data.userid == userGuid.value){ + if(vote.data.userid == props.userid){ return true; } } diff --git a/app/components/PollList.vue b/app/components/PollList.vue index c729874..875f9db 100644 --- a/app/components/PollList.vue +++ b/app/components/PollList.vue @@ -27,7 +27,7 @@

No polls found. Create the first one!

-
+