---
# System prepended metadata

title: 學生會選舉系統規格

---

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