CIA/e-voting-system/BLOCKCHAIN_FLOW.md

433 lines
15 KiB
Markdown

# Blockchain Voting Flow - Complete Documentation
## Overview
When a user votes through the web interface, the entire flow is:
```
User selects candidate
Vote encrypted (ElGamal) on client
Vote submitted to API (/api/votes/submit)
Backend validates voter & election
Vote recorded in database
Vote added to blockchain (immutable)
User sees success with transaction ID
Vote visible in blockchain viewer
```
## Detailed Flow
### 1. Frontend: Vote Selection & Encryption
**File**: `frontend/components/voting-interface.tsx`
```typescript
// Step 1: Get voter profile and public keys
const voterResponse = await fetch("/api/auth/profile")
const voterId = voterData.id
// Step 2: Get election's public keys for encryption
const keysResponse = await fetch(`/api/votes/public-keys?election_id=${electionId}`)
const publicKeys = keysResponse.data
// Step 3: Create signed ballot with client-side encryption
const ballot = createSignedBallot(
voteValue, // 1 for selected candidate
voterId, // Voter ID
publicKeys.elgamal_pubkey, // ElGamal public key
"" // Private key for signing
)
// Result includes:
// - encrypted_vote: Base64 encoded ElGamal ciphertext
// - zkp_proof: Zero-knowledge proof of validity
// - signature: RSA-PSS signature
```
### 2. Frontend: Vote Submission
**File**: `frontend/components/voting-interface.tsx` (lines 116-130)
```typescript
const submitResponse = await fetch("/api/votes/submit", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
body: JSON.stringify({
election_id: electionId,
candidate_id: selectedCandidate,
encrypted_vote: ballot.encrypted_vote,
zkp_proof: ballot.zkp_proof,
signature: ballot.signature,
timestamp: ballot.timestamp
})
})
```
### 3. Backend: Vote Validation & Recording
**File**: `backend/routes/votes.py` (lines 99-210)
```python
@router.post("/submit")
async def submit_vote(
vote_bulletin: schemas.VoteBulletin,
current_voter: Voter = Depends(get_current_voter),
db: Session = Depends(get_db),
request: Request = None
):
# 1. Verify voter hasn't already voted
if services.VoteService.has_voter_voted(db, current_voter.id, election_id):
raise HTTPException(detail="Already voted")
# 2. Verify election exists
election = services.ElectionService.get_election(db, election_id)
# 3. Verify candidate exists
candidate = db.query(Candidate).filter(...).first()
# 4. Decode encrypted vote (from base64)
encrypted_vote_bytes = base64.b64decode(vote_bulletin.encrypted_vote)
# 5. Generate ballot hash (immutable record)
ballot_hash = SecureHash.hash_bulletin(
vote_id=current_voter.id,
candidate_id=candidate_id,
timestamp=int(time.time())
)
# 6. Record vote in database
vote = services.VoteService.record_vote(
db=db,
voter_id=current_voter.id,
election_id=election_id,
candidate_id=candidate_id,
encrypted_vote=encrypted_vote_bytes,
ballot_hash=ballot_hash,
ip_address=request.client.host
)
```
### 4. Backend: Blockchain Recording
**File**: `backend/routes/votes.py` (lines 181-210)
```python
# Generate unique transaction ID (anonymized)
transaction_id = f"tx-{uuid.uuid4().hex[:12]}"
# Add vote to blockchain
blockchain = blockchain_manager.get_or_create_blockchain(election_id)
block = blockchain.add_block(
encrypted_vote=vote_bulletin.encrypted_vote,
transaction_id=transaction_id
)
# Mark voter as voted
services.VoterService.mark_as_voted(db, current_voter.id)
# Return success with blockchain info
return {
"id": vote.id,
"transaction_id": transaction_id,
"block_index": block.index,
"ballot_hash": ballot_hash,
"timestamp": vote.timestamp
}
```
### 5. Backend: Blockchain Structure
**File**: `backend/blockchain.py`
```python
class Block:
"""Immutable voting block"""
index: int
prev_hash: str # Hash of previous block (chain integrity)
timestamp: int
encrypted_vote: str # Base64 encrypted vote
transaction_id: str # Unique identifier for this vote
block_hash: str # SHA-256 hash of this block
signature: str # Digital signature
class Blockchain:
"""Election blockchain"""
blocks: List[Block] # All blocks for election
def add_block(self, encrypted_vote: str, transaction_id: str) -> Block:
"""Add new vote block and compute hash chain"""
new_block = Block(
index=len(self.blocks),
prev_hash=self.blocks[-1].block_hash if self.blocks else "0"*64,
timestamp=int(time.time()),
encrypted_vote=encrypted_vote,
transaction_id=transaction_id
)
# Compute SHA-256 hash of block
new_block.block_hash = sha256(json.dumps({...}).encode()).hexdigest()
self.blocks.append(new_block)
return new_block
def verify_chain_integrity(self) -> bool:
"""Verify no blocks have been tampered with"""
for i, block in enumerate(self.blocks):
# Check hash chain continuity
if i > 0 and block.prev_hash != self.blocks[i-1].block_hash:
return False
# Recompute hash and verify
if block.block_hash != compute_block_hash(block):
return False
return True
```
### 6. Frontend: Blockchain Viewer
**File**: `frontend/app/dashboard/blockchain/page.tsx`
The blockchain page fetches and displays the blockchain:
```typescript
// Fetch blockchain for selected election
const response = await fetch(
`/api/votes/blockchain?election_id=${selectedElection}`,
{ headers: { Authorization: `Bearer ${token}` } }
)
const data = await response.json()
// Returns:
// {
// blocks: [
// {
// index: 0,
// prev_hash: "0000...",
// timestamp: 1699328400,
// encrypted_vote: "aGVsbG8...", // Base64
// transaction_id: "tx-abc123...",
// block_hash: "e3b0c4...",
// signature: "d2d2d2..."
// },
// ...
// ],
// verification: {
// chain_valid: true,
// total_blocks: 3,
// total_votes: 2
// }
// }
```
**File**: `frontend/components/blockchain-visualizer.tsx`
Displays blockchain with:
- Statistics dashboard (blocks, votes, status, security)
- Expandable block cards showing all fields
- Copy-to-clipboard for hashes
- Visual integrity check
- Staggered animations
### 7. Backend: Blockchain Retrieval
**File**: `backend/routes/votes.py` (lines 351-370)
```python
@router.get("/blockchain")
async def get_blockchain(
election_id: int = Query(...),
db: Session = Depends(get_db)
):
"""Retrieve complete blockchain for election"""
blockchain = blockchain_manager.get_or_create_blockchain(election_id)
return blockchain.get_blockchain_data()
# Returns: {blocks: [...], verification: {...}}
```
## Data Flow Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ Frontend (Next.js) │
│ │
│ 1. User votes on /dashboard/votes/active/1 │
│ 2. VotingInterface encrypts vote (ElGamal) │
│ 3. Submits to POST /api/votes/submit │
│ 4. Shows success with transaction_id │
│ 5. User views blockchain on /dashboard/blockchain │
│ 6. BlockchainVisualizer displays all blocks │
└─────────────────────────────────────────────────────────────┘
↑↓ HTTP API
┌─────────────────────────────────────────────────────────────┐
│ Backend (FastAPI) │
│ │
│ POST /api/votes/submit: │
│ - Validate voter & election │
│ - Verify candidate exists │
│ - Record vote in database │
│ - ADD TO BLOCKCHAIN ← New block created │
│ - Return transaction_id & block_index │
│ │
│ GET /api/votes/blockchain: │
│ - Retrieve all blocks for election │
│ - Calculate chain verification status │
│ - Return blocks + verification data │
│ │
│ POST /api/votes/verify-blockchain: │
│ - Verify chain integrity (no tampering) │
│ - Check all hashes are correct │
│ - Return verification result │
└─────────────────────────────────────────────────────────────┘
↑↓ Database
┌─────────────────────────────────────────────────────────────┐
│ MariaDB Database │
│ │
│ votes table: │
│ - voter_id, election_id, candidate_id │
│ - encrypted_vote (stored encrypted) │
│ - ballot_hash, timestamp │
│ - ip_address │
│ │
│ blockchain (in-memory BlockchainManager): │
│ - All blocks for each election │
│ - Chain integrity verified on each access │
└─────────────────────────────────────────────────────────────┘
```
## Security Features
### 1. Encryption
- **ElGamal Homomorphic**: Vote encrypted client-side
- **Cannot decrypt individual votes** (only aggregate counts)
- **Public key available** at `/api/votes/public-keys`
### 2. Signatures
- **RSA-PSS**: Each ballot digitally signed
- **Ballot Hash**: SHA-256 hash of vote metadata
- **Transaction ID**: Anonymized identifier (not tied to voter)
### 3. Blockchain Integrity
- **Hash Chain**: Each block references previous hash
- **Tamper Detection**: Any change breaks chain verification
- **Immutable**: Blocks cannot be modified (verified on GET)
- **Voter Anonymity**: Transaction ID is anonymous UUID
### 4. Vote Validation
- **One vote per person**: `has_voter_voted()` check
- **Valid election**: Election must exist and be active
- **Valid candidate**: Candidate must be in election
- **Authentication**: Vote requires valid JWT token
## Testing the Flow
### 1. Via Web Interface
```
1. Go to http://localhost:3000/dashboard/votes/active
2. Click "Participer" on election
3. Select candidate and confirm
4. See "Vote enregistré avec succès"
5. Click "Voir la blockchain"
6. See your vote block added to chain
```
### 2. Via API (cURL)
```bash
# 1. Register user
curl -X POST http://localhost:8000/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "voter@test.fr",
"password": "Test@12345",
"first_name": "Jean",
"last_name": "Dupont",
"citizen_id": "12345ABC"
}'
# Response: {"access_token": "eyJ0eXA..."}
# 2. Get public keys
curl http://localhost:8000/api/votes/public-keys?election_id=1
# 3. Submit vote (encrypted client-side in real scenario)
curl -X POST http://localhost:8000/api/votes/submit \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJ0eXA..." \
-d '{
"election_id": 1,
"candidate_id": 2,
"encrypted_vote": "aGVsbG8gd29ybGQ=",
"zero_knowledge_proof": "...",
"signature": "..."
}'
# Response:
# {
# "id": 1,
# "transaction_id": "tx-abc123def456",
# "block_index": 1,
# "ballot_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
# }
# 4. Fetch blockchain
curl http://localhost:8000/api/votes/blockchain?election_id=1
# 5. Verify blockchain integrity
curl -X POST http://localhost:8000/api/votes/verify-blockchain \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJ0eXA..." \
-d '{"election_id": 1}'
# Response: {"chain_valid": true}
```
## Key Endpoints
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/votes/submit` | POST | Submit encrypted vote (adds to blockchain) |
| `/api/votes/blockchain` | GET | Retrieve all blocks for election |
| `/api/votes/verify-blockchain` | POST | Verify chain integrity |
| `/api/votes/results` | GET | Get election results with verification |
| `/api/votes/public-keys` | GET | Get public keys for encryption |
| `/api/elections/active` | GET | List active elections |
| `/api/elections/{id}` | GET | Get election details with candidates |
## Files Involved
### Frontend
- `frontend/components/voting-interface.tsx` - Vote submission form
- `frontend/app/dashboard/votes/active/page.tsx` - Elections list
- `frontend/app/dashboard/votes/active/[id]/page.tsx` - Vote detail page
- `frontend/app/dashboard/blockchain/page.tsx` - Blockchain viewer
- `frontend/components/blockchain-visualizer.tsx` - Blockchain visualization
- `frontend/lib/crypto-client.ts` - Encryption & signing
### Backend
- `backend/routes/votes.py` - Vote endpoints
- `backend/blockchain.py` - Blockchain implementation
- `backend/crypto/elgamal.py` - ElGamal encryption
- `backend/crypto/signatures.py` - Digital signatures
- `backend/crypto/hashing.py` - SHA-256 hashing
- `backend/services/vote_service.py` - Vote business logic
## Success Indicators
✓ User can select candidate and vote
✓ Vote encrypted before transmission
✓ Vote recorded in database
✓ Vote added to blockchain
✓ Transaction ID returned to user
✓ Blockchain viewer shows new block
✓ All hashes verify correctly
✓ Chain integrity: valid ✓
✓ One vote per person enforced
✓ Only active elections can be voted on