# NCUESA 學生會選舉系統開發規格書 (v2.0 - OAuth & ZK Ver.) ## 1. 專案概觀 (Overview) 本系統旨在建立一個安全、匿名且可驗證的電子投票平台,服務國立彰化師範大學學生會選舉。核心技術採用 **Groth16 零知識證明 (Zero-Knowledge Proof)** 確保「票票不等值」(資格驗證)與「投票匿名性」(去識別化),並透過學校 OAuth 服務進行身份認證。 ### 1.1 參考資料與法規 :::info **法規依據** * [選舉罷免暨推舉自治條例](https://drive.ncuesa.org.tw/d/s/12ZyD8uoAFaPeKBqt5wJ9shEqPMSK2vN/ehZMCvnK2A54H57oeWmI6tOM7QkIIEY1-MLagWOpKiQw) **技術參考** * [Groth16 數學理論](https://www.zeroknowledgeblog.com/index.php/groth16?utm_source=chatgpt.com) * [Quadratic Arithmetic Programs: from Zero to Hero](https://medium.com/%40VitalikButerin/quadratic-arithmetic-programs-from-zero-to-hero-f6d558cea649) * [橢圓曲線 ECC 加密原理](https://ithelp.ithome.com.tw/articles/10251031) * [Powers of Tau Groth16 Trusted Setup](https://aping-dev.com/index.php/archives/781/) **外部系統資訊** * [學校 OIDC Well-Known](https://nam.ncue.edu.tw/nidp/oauth/nam/.well-known/openid-configuration) * 範例參考: [台北大學選舉系統](https://ntpusu-vote.vercel.app/) ::: --- ## 2. 身份與權限定義 (Identity & Roles) ### 2.1 學號編碼定義 系統需依據學號前綴判斷學籍身份,以區分選區(系/院): * **S**: 學士班 (Bachelor) * **M**: 碩士班 (Master) * **D**: 博士班 (PhD) * **A**: 在職碩班 (In-service Master) * *範例*: `S1234567` (大學部), `M1234567` (碩士班) ### 2.2 系統權限 (RBAC) 1. **User (一般會員/投票者)** * 需具有「在學學籍」。 * 功能:登入、查看選舉公報、投票、驗證自己的票。 2. **Admin (選委會/監票人員)** * 功能:查看即時投票率、查看開票結果(但不應能看到個別票的內容)。 3. **Superadmin (系統管理員)** * 功能:建立選舉(設定種類、候選人、時間、席次)、管理 API 接介。 ### 2.3 認證機制 (Authentication) - OAuth Update 原 SAML 機制改為 **OAuth 2.0 / OIDC**。 * **Provider**: NCUE NAM (NetIQ Access Manager) * **流程**: 1. 前端導向學校 OAuth Authorization Endpoint。 2. 使用者在學校頁面登入。 3. Callback 回傳 `Authorization Code`。 4. 後端使用 Code 交換 `Access Token` 與 `ID Token`。 5. 解析 Token 取得 `sub` (學號) 與使用者屬性 (系所、年級)。 6. **資格核對**: 比對學校 API 或匯入的選舉人名冊 (White List)。 --- ## 3. 選舉制度與邏輯 (Election Rules) ### 3.1 職位與選制 | 職位 | 選區 | 選制邏輯 | 當選/門檻條件 | | :--- | :--- | :--- | :--- | | **正副會長** | 全校 | 一人一票 | **同額競選**: 須達總選舉人 **10%** 同意票。<br>**差額競選**: 票數最多者當選(相對多數決)。<br>**同票**: 直接重選。 | | **選區議員** | 大學部(系)<br>研究所(院) | 一人一票 | **票多者當選**。<br>**同票**: 抽籤決定。 | | **不分區議員** | 全校 | SNTV (單記不可讓渡) | **門檻**: 得票數須達總選舉人 **1%** 以上。<br>**席次**: 取前 **16** 名。<br>**限制**: 一人只能投一票。 | ### 3.2 特殊席次(暫不實現) * 轉聯會、畢聯會、僑聯會、彰師原動力。 --- ## 4. 系統核心架構:Groth16 零知識投票 (Core Architecture) 本系統利用 Groth16 演算法解決電子投票的兩難:**「需驗證投票資格」** 但 **「不可追蹤投給誰」**。 ### 4.1 核心概念 * **匿名性 (Anonymity)**: 透過 ZK Proof,後端資料庫只知道「這是一個合法的票」,但無法將「票」連結回「學號」。 * **可驗證性 (Verifiability)**: 使用者持有 `Nullifier` (票據雜湊),可事後確認票是否被寫入資料庫。 * **防重複 (Anti-Double Voting)**: 透過 `Nullifier` 的唯一性限制,同一組 `(User Secret, Election ID)` 只能產生一次有效的 Nullifier。 ### 4.2 加密投票流程 (ZKP Workflow) #### Phase 1: 準備 (Setup) & 登入 1. **User 登入**: 透過 NCUE OAuth 登入。 2. **資格檢查**: 後端確認 User 在該 Election 的 Voter List 中。 3. **秘密生成**: * 系統或前端為 User 計算/生成 `Nullifier Secret`。 * *建議*: 為了極致匿名,Secret 應由前端生成並暫存,或由 User 的 OAuth Sub 進行單向 Hash 衍生,確保後端無法逆推。 #### Phase 2: 投票 (Voting) - 前端計算 Proof *(採用前端 WASM 計算以確保後端無法窺探原始意圖)* 1. **選擇候選人**: User 在 UI 選擇候選人 (Choice)。 2. **生成 Witness**: 前端 Web Worker 結合 `Choice` (選票內容), `Nullifier Secret`, `Election ID`, `Mask` (候選人遮罩) 生成電路輸入。 3. **計算 Proof**: * 輸入:`vote.wasm` + `vote_final.zkey` * 輸出:`Proof (π_a, π_b, π_c)` + `PublicSignals` * `PublicSignals` 包含:`Hash(Nullifier)`, `Hash(Candidate/Choice)`, `ElectionID` 等公開參數。 * **關鍵**: 原始的 `Choice` 透過電路被隱藏,但 `PublicSignals` 需包含後端計票所需的資訊 (此處設計為 PublicSignals 直接揭露 CandidateID 給後端計票,但因為 Proof 隱藏了 User Secret,後端無法知道是誰投的)。 #### Phase 3: 提交與存證 (Submit & Store) 1. 前端 POST `/votes/submit` 傳送 `Proof` 與 `PublicSignals`。 2. 後端驗證: * `snarkjs.groth16.verify(vk, PublicSignals, Proof)` * 檢查 `Nullifier` 是否已存在 DB (防重複)。 3. 寫入 DB: * 存入 `Nullifier` (標記已投)。 * 存入 `Proof` (供驗證)。 * 存入 `Candidate ID` (從 PublicSignals 解析,用於計票)。 * **注意**: DB 中 `Votes` 表**沒有**欄位紀錄 User ID。 #### Phase 4: 驗票 (Verification) 1. 前端顯示 `Nullifier Hash` 給使用者當作收據。 2. 使用者可至「驗證中心」輸入 Hash,查詢 DB 是否有此紀錄。 --- ## 5. 資料庫設計 (Database Design) 本系統資料庫設計強調**斷開使用者與選票的關聯**。 ### 5.1 ER Model 概觀 ![image](https://hackmd.io/_uploads/SkAeJYZWZe.png) *(註:圖中 Users 與 Votes 之間無 Foreign Key,邏輯關聯僅存在於 ZK 電路驗證中)* ### 5.2 資料表關聯圖 ![image](https://hackmd.io/_uploads/SJXFTutZbg.png) ### 5.3 詳細 Schema 定義 #### Users (使用者) 紀錄具投票權的學生資訊。 ![image](https://hackmd.io/_uploads/rkraTuKWZl.png) * **sid**: 學號 (Primary Key) * **hashed_password**: (若使用 OAuth,此欄位可能不需要,或改存 OAuth Sub) * **is_eligible**: 是否在學 * **department/college**: 用於判斷選區 #### Elections (選舉專案) 定義一場選舉的規則與時間。 ![image](https://hackmd.io/_uploads/ryZVkYWW-x.png) * **start_date / end_date**: 投票區間 * **type**: 選舉種類 (1:會長, 2:議員, 3:不分區) * **settings**: JSON 欄位,儲存門檻、席次等設定 #### Candidates (候選人) ![image](https://hackmd.io/_uploads/SJN4yK-W-e.png) * **eid**: 關聯到 Elections * **no**: 候選人號次 #### Votes (選票 - 匿名儲存) **最關鍵的資料表**,不紀錄 User ID。 ![image](https://hackmd.io/_uploads/Skp4ytWb-g.png) * **nullifier**: ZK 產生的唯一識別碼,用於防重複投票 (Unique Key)。 * **proof**: 儲存完整的 ZK Proof (JSON string: a, b, c)。 * **candidate_id**: 投給誰 (從 PublicSignals 解出)。 * **eid**: 所屬選舉。 * **created_at**: 投票時間。 #### Logs (系統日誌) 用於稽核與除錯。 ![image](https://hackmd.io/_uploads/H1O4kF-bWg.png) --- ## 6. API 規格設計 (API Specification) ### 6.1 User / Auth * `POST /auth/login`: (前端導向學校 OAuth) * `GET /auth/callback`: 處理 OAuth Redirect,發放系統 JWT。 * `GET /users/me`: 取得當前登入者資訊 (SID, Dept, College)。 * `POST /users/verify`: 驗證使用者當前憑證合法性。 ### 6.2 Election * `POST /elections`: (Admin) 建立選舉。 * `GET /elections`: 顯示所有選舉清單。 * `GET /elections/:eid`: 取得特定選舉詳情 (含候選人列表、公報連結)。 * `POST /elections/:eid/candidates`: (Admin) 新增候選人。 * `DELETE /elections/:eid`: (Superadmin) 刪除選舉。 ### 6.3 Votes (核心功能) * `GET /votes/eligibility/:eid`: 檢查當前 User 是否有資格投這場選舉 (回傳 Boolean 與 Mask)。 * `POST /votes/submit`: 提交選票。 * **Body**: `{ eid, proof, publicSignals }` * **Process**: 1. Verify Proof (zk check). 2. Check `publicSignals.nullifier` exists? (Double vote check). 3. Save to DB. * `GET /votes/:eid/result`: (Admin/Public) 查詢目前開票結果 (需視設定決定是否即時公開)。 ### 6.4 Verification Center * `POST /verify/check_ticket`: * **Body**: `{ nullifier }` * **Response**: `{ exists: boolean, candidate: encrypted_or_hidden, verified: boolean }` * `GET /verify/:eid/logs`: 下載完整計票日誌 (Proof list) 供第三方驗證。 --- ## 7. 前端頁面規劃 (UI/UX) 1. **Landing Page**: 系統介紹、登入按鈕 (SSO)。 2. **使用說明頁**: 圖文教學 ZK 投票流程。 3. **選舉列表**: 顯示進行中/已結束選舉。 4. **投票頁面**: * 選舉公報渲染 (PDF Viewer)。 * 候選人選擇卡片。 * **確認與加密**: 點擊後跳出 Loading (Web Worker 計算 Proof)。 * **完成頁**: 顯示「投票成功」並展示 `Nullifier Hash` (請使用者截圖保存)。 5. **驗證中心**: 輸入 Nullifier Hash 查詢選票狀態。 6. **管理後台**: 建立選舉、監控票數、匯出結果。 --- ## 8. 系統整合與行政流程 (Integration & Admin) ### 8.1 學校資料介接 為了確認投票權(是否在學、系級),需與學校系統對接。 * **需求資料**: YAML 或 JSON 格式 `{學號, 班級, 系所代碼, 在學狀態}`。 * **API 授權**: * 需獲得 **系統開發組** (System Dev Group) 同意使用 API。 * 需獲得 **教務處** (Academic Affairs) 授權提供學生個資 (在學與否)。 * **行政協調**: * ⚠️ **Risk**: 聯絡窗口(如:漪庭姐)可能較為忙碌/消極,需預留溝通時間。 * **Action Item**: 召開「選委會、學生會、系統開發組」三方會議,確立資料欄位與 API Token 提供方式。 ### 8.2 部署建議 * **Frontend**: Vercel / Netlify (靜態檔 + WASM)。 * **Backend**: Node.js / Go / Rust (需高效處理 ZK Verify)。 * **Database**: PostgreSQL (關聯式資料庫適合此結構)。 * **Zero Knowledge Files**: `.zkey`, `.wasm` 需放在 CDN 加速下載。 --- ## 9. 技術問答與決策 (QA & Decisions) **Q1: 前端計算 Proof 還是後端計算?** * **決策**: 若要追求**極致匿名** (連 DB 管理員都不知道誰投誰),必須在 **前端 (Client-side)** 計算。 * **實作**: 瀏覽器載入 `.wasm`,在 Web Worker 內生成 Proof。後端只接收 Proof 與 Nullifier,完全看不到 User Secret。 * **妥協**: 若前端效能成問題 (舊手機跑不動),可保留「後端代算」的選項,但需在隱私條款中聲明(此時後端 log 可能會洩漏投票意向)。目前規劃優先採用 **前端計算**。 **Q2: 如何防止 User 亂傳 Proof?** * **驗證**: 後端使用 `snarkjs.groth16.verify` 結合 `verification_key.json`。只要 Proof 數學上不成立,驗證就會失敗,資料庫拒絕寫入。 * **防重放**: 資料庫設有 Unique Key Constraint 在 `nullifier` 欄位。 **Q3: 學生如何驗票?** * **簡易版**: 系統提供查詢頁面,輸入 `Nullifier Hash` 確認「我的票被系統收錄了」。 * **進階版 (Geek)**: 開放下載所有匿名後的 Proofs (`proofs.json`) 與 `verification_key`,學生可自己在本地端跑 script 驗證全校選票的數學合法性。