blob: 0de9ec044689de3a313430317ee377dd9c0d2b9a [file] [log] [blame] [view]
# Database Schema Documentation
This document describes the SQLite database schema for an election management system. The schema supports creating and managing elections, defining issues to vote on, tracking eligible voters, and securely recording votes. It emphasizes security through cryptographic fields (e.g., salts, hashed tokens, encrypted votes) and enforces data integrity with constraints like foreign keys and strict typing.
## Overview
The database consists of five tables:
- `election`: Stores election metadata, including ownership and cryptographic data.
- `issue`: Defines specific issues within an election, with voting mechanisms and metadata.
- `person`: Tracks individuals involved in elections (e.g., voters, owners).
- `mayvote`: Specifies which persons are eligible to vote on specific issues.
- `vote`: Records encrypted votes, supporting re-voting by tracking insertion order.
All tables use SQLites `STRICT` mode to enforce type constraints. The schema avoids `AUTOINCREMENT` for IDs (except in `vote`) to prevent predictable URLs, using 10-character hexadecimal IDs instead.
## Table Details
### `election`
Stores metadata for each election.
- **Columns**:
- `eid` (TEXT, PRIMARY KEY): A 10-character hexadecimal election ID (e.g., "1a2b3c4d5e"). Not auto-incremented to prevent URL deduction. Must match the pattern `[0-9a-f]{10}`.
- `title` (TEXT, NOT NULL): The elections title.
- `owner_pid` (TEXT, NOT NULL): The ID of the person who created/owns the election. References `person(pid)`.
- `authz` (TEXT): Specifies an LDAP group (e.g., group name or identifier) allowed to edit the election. If `NULL`, only `owner_pid` can edit. The exact format is TBD but will likely map to LDAP-based persons.
- `salt` (BLOB): A 16-byte salt generated using `secrets.token_bytes()` in Python, set when the election is opened. `NULL` if not opened.
- `opened_key` (BLOB): A 32-byte hash (using Argon2) derived from election data to detect tampering, set when the election is opened. `NULL` if not opened.
- `closed` (INTEGER): Indicates election status: `NULL` or `0` for not closed, `1` for closed (implies opened). Must be `NULL`, `0`, or `1`.
- **Constraints**:
- Foreign key: `owner_pid` references `person(pid)` with `RESTRICT` on delete and `NO ACTION` on update.
- Check constraints ensure `eid` format, `salt` (16 bytes or `NULL`), `opened_key` (32 bytes or `NULL`), and `closed` values.
### `issue`
Represents issues (questions or proposals) within an election.
- **Columns**:
- `iid` (TEXT, PRIMARY KEY): A 10-character hexadecimal issue ID (e.g., "a1b2c3d4e5"). Must match `[0-9a-f]{10}`.
- `eid` (TEXT, NOT NULL): The election ID this issue belongs to. References `election(eid)`.
- `title` (TEXT, NOT NULL): A one-line title for the issue.
- `description` (TEXT): An optional detailed description of the issue.
- `type` (TEXT, NOT NULL): The voting mechanism. Currently supports:
- `yna`: Yes/No/Abstain voting.
- `stv`: Single Transferable Vote.
Additional types may be added in the future.
- `kv` (TEXT): JSON-formatted key-value pairs for issue-specific data, varying by `type`. For example, `stv` issues may include a `"candidates"` key listing candidate names or IDs.
- **Constraints**:
- Foreign key: `eid` references `election(eid)` with `RESTRICT` on delete and `NO ACTION` on update.
- Check constraint ensures `iid` format.
- **Indexes**:
- `idx_issue_eid`: Index on `eid` for efficient lookups of issues by election.
### `person`
Stores information about individuals (voters, owners).
- **Columns**:
- `pid` (TEXT, PRIMARY KEY): A unique person ID (e.g., LDAP username).
- `name` (TEXT): An optional human-readable name.
- `email` (TEXT, NOT NULL): Contact email for sending ballot links.
- **Constraints**:
- No foreign keys or additional constraints.
### `mayvote`
Defines which persons are eligible to vote on specific issues.
- **Columns**:
- `pid` (TEXT, NOT NULL): The person ID. References `person(pid)`.
- `iid` (TEXT, NOT NULL): The issue ID. References `issue(iid)`.
- `salt` (BLOB): A 16-byte salt generated using `secrets.token_bytes()`, used for generating a vote token and encryption key. `NULL` until the election is opened.
- **Constraints**:
- Primary key: `(pid, iid)` ensures a person can be eligible for an issue only once.
- Foreign keys: `pid` references `person(pid)`, `iid` references `issue(iid)`, both with `RESTRICT` on delete and `NO ACTION` on update.
- Check constraint ensures `salt` is 16 bytes or `NULL`.
### `vote`
Records votes cast for issues, supporting re-voting.
- **Columns**:
- `vid` (INTEGER, PRIMARY KEY, AUTOINCREMENT): Auto-incrementing ID to track insertion order (aliases `_ROWID_`).
- `vote_token` (BLOB, NOT NULL): A 32-byte hash (using Argon2) of a person-issue pair (from `mayvote`), used for encryption key derivation.
- `ciphertext` (BLOB, NOT NULL): The encrypted vote data, currently using Fernet encryption, with plans to transition to XChaCha20-Poly1305.
- **Constraints**:
- Check constraint ensures `vote_token` is 32 bytes.
- No foreign keys, but `vote_token` is derived from `mayvote` data.
- **Indexes**:
- `idx_by_vote_token`: Index on `vote_token` for efficient vote lookups.
## Key Features
- **Security**:
- Cryptographic fields ensure vote and election integrity:
- `salt` (16 bytes) in `election` and `mayvote` uses `secrets.token_bytes()`.
- `opened_key` (32 bytes) in `election` uses Argon2 to hash election data for tamper detection.
- `vote_token` (32 bytes) in `vote` uses Argon2 to hash person-issue pairs.
- `ciphertext` in `vote` uses Fernet encryption (to be replaced with XChaCha20-Poly1305).
- Non-predictable IDs (`eid`, `iid`) use 10-character hex strings to prevent URL guessing.
- **Re-voting**: The `vote` table allows multiple votes per person-issue pair. The latest vote is identified using `MAX(vid)`, leveraging the auto-incrementing `vid`. Older votes are retained for auditing.
- **Flexible Issues**: The `issue` table supports multiple voting mechanisms (`yna`, `stv`) with extensible JSON metadata (`kv`). For example, `stv` issues may specify candidates in `kv`.
- **Data Integrity**: Foreign keys with `RESTRICT` prevent deletion of referenced records, and `STRICT` mode enforces type safety.
- **Election Lifecycle**:
- **Editable**: The election is created and can be modified (e.g., adding issues, voters).
- **Open**: A Python method call opens the election, setting `salt` and `opened_key`. Eligible voters (in `mayvote`) can vote on issues.
- **Closed**: A Python method call closes the election (sets `closed = 1`), enabling tallying.
- The owner (`owner_pid`) can monitor and edit the election at all stages, including viewing voter eligibility and vote counts.
## Notes
- **Authorization**: The `authz` fields format is TBD but will likely use LDAP group identifiers to define editing permissions. Validation occurs outside the schema (e.g., via LDAP integration).
- **Query Optimization**: The schema references a `queries.yaml` file, which maps query names to SQL statements for all database interactions. This file provides insight into common operations (e.g., vote tallying, eligibility checks). Specific queries are not included here but can be analyzed for index optimization.
- **Future Changes**: The `ciphertext` encryption will transition from Fernet to XChaCha20-Poly1305. Additional `issue` types may be added beyond `yna` and `stv`.