# 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 概觀

*(註:圖中 Users 與 Votes 之間無 Foreign Key,邏輯關聯僅存在於 ZK 電路驗證中)*
### 5.2 資料表關聯圖

### 5.3 詳細 Schema 定義
#### Users (使用者)
紀錄具投票權的學生資訊。

* **sid**: 學號 (Primary Key)
* **hashed_password**: (若使用 OAuth,此欄位可能不需要,或改存 OAuth Sub)
* **is_eligible**: 是否在學
* **department/college**: 用於判斷選區
#### Elections (選舉專案)
定義一場選舉的規則與時間。

* **start_date / end_date**: 投票區間
* **type**: 選舉種類 (1:會長, 2:議員, 3:不分區)
* **settings**: JSON 欄位,儲存門檻、席次等設定
#### Candidates (候選人)

* **eid**: 關聯到 Elections
* **no**: 候選人號次
#### Votes (選票 - 匿名儲存)
**最關鍵的資料表**,不紀錄 User ID。

* **nullifier**: ZK 產生的唯一識別碼,用於防重複投票 (Unique Key)。
* **proof**: 儲存完整的 ZK Proof (JSON string: a, b, c)。
* **candidate_id**: 投給誰 (從 PublicSignals 解出)。
* **eid**: 所屬選舉。
* **created_at**: 投票時間。
#### Logs (系統日誌)
用於稽核與除錯。

---
## 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 驗證全校選票的數學合法性。