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!

-
+