Created comprehensive openspec structure: openspec/specs/: - mvp.md: MVP feature overview - architecture.md: System architecture and data flows openspec/changes/add-pqc-voting-mvp/: - proposal.md: Project proposal with scope and rationale - tasks.md: Detailed implementation tasks (6 phases, 30+ tasks) - design.md: Complete design document - Cryptographic algorithms (Paillier, Kyber, Dilithium, ZKP) - Data structures (Block, Blockchain, Ballot) - API endpoint specifications - Security properties matrix - Threat model and mitigations Follows openspec three-stage workflow: 1. Creating changes (proposal-based) 2. Implementation (tracked via tasks) 3. Completion (with validation) Ready for implementation phase with clear requirements. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
7.2 KiB
7.2 KiB
Design Document
Change ID: add-pqc-voting-mvp
Cryptographic Design
Paillier Homomorphic Encryption
- Key Generation: Generate (n, λ, g) keypair
- Encryption: E(m) = g^m * r^n mod n^2 where r is random
- Decryption: D(c) = L(c^λ mod n^2) / L(g^λ mod n^2) mod n
- Homomorphic Property: E(m1) * E(m2) = E(m1 + m2) mod n^2
- Vote Property: E(0) * E(0) * ... * E(1) = E(total_votes)
- Usage: Encrypt votes (0 or 1), sum encrypted votes without decryption
Kyber (ML-KEM)
- Key Encapsulation: Generate (ek, dk) keypair
- Encapsulation: (c, ss) = Kyber.Encaps(ek) - shared secret ss
- Decapsulation: ss = Kyber.Decaps(c, dk)
- Usage: Encrypt Paillier private key with Kyber public key for PQC protection
- Benefit: Protects vote counts against future quantum attacks
Dilithium (ML-DSA)
- Key Generation: Generate (sk, vk) signing keypair per voter
- Signature: σ = Dilithium.Sign(msg, sk)
- Verification: 0/1 = Dilithium.Verify(msg, σ, vk)
- Usage:
- Voter signs encrypted ballot
- Authority signs blockchain blocks
- Benefit: Post-quantum resistant authentication
Zero-Knowledge Proof (Simplified)
- Goal: Prove E(v) is encryption of 0 or 1 without revealing v
- Protocol:
- Voter commits to 0 and 1: c0 = E(0), c1 = E(1)
- Voter selects random r, computes challenge response
- Server verifies without learning v
- Implementation: Simple version using Paillier properties
SHA-256 Hash Chain
- Block Hash: H = SHA256(index || prev_hash || timestamp || E(v) || signature)
- Chain Property: Each block contains previous block's hash
- Verification: Recompute all hashes and verify chain integrity
- Immutability: Changing any block invalidates entire chain
Data Structures
Block
class Block:
index: int # Block number (0, 1, 2, ...)
prev_hash: str # SHA256 of previous block
timestamp: float # Unix timestamp
encrypted_vote: str # Paillier encrypted ballot
transaction_id: str # Unique vote identifier
block_hash: str # SHA256 of this block
signature: str # Dilithium signature of block
Blockchain
class Blockchain:
chain: List[Block] # List of blocks
authority_sk: str # Authority's Dilithium private key
authority_vk: str # Authority's Dilithium public key
paillier_pubkey: str # Paillier public key
def add_block(encrypted_vote, tx_id) -> Block
def verify_chain() -> bool
def get_encrypted_sum() -> int (homomorphic)
Vote Ballot
interface Ballot {
voter_id: string # Voter identifier (verified once)
encrypted_vote: string # E(v) with Paillier
zkp_proof: string # ZKP that E(v) is 0 or 1
signature: string # Dilithium signature
timestamp: number # Client-side timestamp
}
Election Setup
class ElectionSetup:
paillier_pubkey: str # Distributed to voters
paillier_privkey: str # Kyber-encrypted, kept secret
authority_sign_sk: str # Dilithium signing key
authority_sign_vk: str # Dilithium verification key (public)
API Endpoints
POST /api/votes/setup
Purpose: Initialize election with cryptographic keys Request:
{
"election_id": "2025-vote-01"
}
Response:
{
"public_keys": {
"paillier": "...base64...",
"dilithium": "...base64..."
},
"status": "initialized"
}
GET /api/votes/public-keys
Purpose: Retrieve public keys for client encryption Response:
{
"paillier_pubkey": "...base64...",
"authority_pubkey": "...base64..."
}
POST /api/votes/register-voter
Purpose: Register voter with their Dilithium public key Request:
{
"voter_id": "user123",
"dilithium_pubkey": "...base64..."
}
Response:
{
"status": "registered",
"voter_id": "user123"
}
POST /api/votes/submit
Purpose: Submit encrypted ballot Request:
{
"voter_id": "user123",
"encrypted_vote": "...base64...",
"zkp_proof": "...base64...",
"signature": "...base64..."
}
Response:
{
"status": "recorded",
"transaction_id": "tx-abc123",
"block_index": 42
}
GET /api/votes/blockchain
Purpose: Retrieve blockchain state Response:
{
"blocks": [
{
"index": 0,
"prev_hash": "0000...",
"timestamp": 1730000000,
"encrypted_vote": "...base64...",
"transaction_id": "tx-0",
"block_hash": "abc123...",
"signature": "...base64..."
}
],
"verification": {
"chain_valid": true,
"signatures_valid": true,
"total_votes": 42
}
}
GET /api/votes/results
Purpose: Get vote counting results Response:
{
"total_votes": 42,
"results": {
"yes": 28,
"no": 14
},
"verification": {
"chain_valid": true,
"homomorphic_verified": true,
"proofs": "...base64..."
}
}
Security Properties
| Property | Implementation | Guarantee |
|---|---|---|
| Vote Secrecy | Paillier encryption | Encrypted before leaving client |
| Vote Integrity | Blockchain + Dilithium | Immutable, cryptographically signed |
| Anonymity | Transaction IDs | Voter verified once, not stored with vote |
| Individual Verifiability | ZKP + blockchain | Voter can verify their vote is counted |
| Universal Verifiability | Public blockchain | Anyone can verify results are correct |
| Post-Quantum | Kyber + Dilithium | Resistant to quantum attacks |
Threat Model
Threats & Mitigations
-
Vote Tampering
- Threat: Attacker modifies a vote after submission
- Mitigation: Blockchain prevents modification; hash chain breaks
- Verification: Recompute hashes to detect tampering
-
Double Voting
- Threat: Voter votes multiple times
- Mitigation: Emission list (voted voters tracked)
- Verification: Check voter_id not already submitted
-
Vote Disclosure
- Threat: Server leaks which voter voted for which candidate
- Mitigation: Paillier encryption; server never sees plaintext votes
- Verification: Homomorphic summation prevents vote disclosure
-
Authority Fraud
- Threat: Authority publishes false results
- Mitigation: Blockchain is public; results are cryptographically verified
- Verification: Anyone can recompute homomorphic sum
-
Quantum Attack
- Threat: Future quantum computer breaks encryption
- Mitigation: Kyber and Dilithium are post-quantum
- Verification: Algorithms certified by NIST (FIPS 203, 204)
-
Denial of Service
- Threat: Attacker prevents votes from being submitted
- Mitigation: Rate limiting on API endpoints
- Verification: Blockchain size and performance monitoring
Implementation Notes
- Vote Encoding: 0 = "No", 1 = "Yes" (can extend for multiple candidates)
- Key Storage: Paillier private key protected by Kyber encryption at rest
- Database: Store blocks, voter keys, emission list
- Frontend Crypto: Use WASM or Node.js crypto libs for performance
- Verification: Anyone can download blockchain and verify
- Audit Trail: Complete blockchain is the audit trail