- Created `/frontend/app/api/votes/check/route.ts` to handle GET requests for checking if a user has voted in a specific election. - Added error handling for unauthorized access and missing election ID. - Forwarded requests to the backend API and returned appropriate responses. - Updated `/frontend/app/api/votes/history/route.ts` to fetch user's voting history with error handling. - Ensured both endpoints utilize the authorization token for secure access.
11 KiB
Elections Blockchain Integration
Overview
Elections are now immutably recorded to the blockchain with cryptographic security when they are created. This ensures:
- Integrity: Election records cannot be tampered with (SHA-256 hash chain)
- Authentication: Each election is signed with RSA-PSS signatures
- Audit Trail: Complete history of all election creations
- Verification: On-demand cryptographic verification of election integrity
Architecture
Blockchain Components
backend/blockchain_elections.py
Implements immutable blockchain for election records with:
-
ElectionBlock: Dataclass representing one immutable block
index: Position in chainprev_hash: Hash of previous block (chain integrity)timestamp: Unix timestamp of creationelection_id: Reference to database electionelection_name: Election nameelection_description: Election descriptioncandidates_count: Number of candidatescandidates_hash: SHA-256 of all candidates (immutable reference)start_date: ISO format start dateend_date: ISO format end dateis_active: Active status at creation timeblock_hash: SHA-256 of this blocksignature: RSA-PSS signature for authenticationcreator_id: Admin who created this election
-
ElectionsBlockchain: Manages the blockchain
add_election_block(): Records new election with signatureverify_chain_integrity(): Validates entire hash chainverify_election_block(): Detailed verification report for single electionget_blockchain_data(): API response format
Election Creation Flow
1. Election created in database (via API or init script)
↓
2. ElectionService.create_election() called
↓
3. Election saved to database
↓
4. Get candidates for this election
↓
5. Call record_election_to_blockchain()
↓
6. ElectionBlock created with:
- SHA-256 hash of election data
- SHA-256 hash of all candidates
- Reference to previous block's hash
- RSA-PSS signature
↓
7. Block added to immutable chain
Backend Startup
When the backend starts (backend/main.py):
- Database is initialized with elections
initialize_elections_blockchain()is called- All existing elections in database are recorded to blockchain (if not already)
- Blockchain integrity is verified
- Backend ready to serve requests
This ensures elections created by initialization scripts are also immutably recorded.
API Endpoints
Get Complete Elections Blockchain
GET /api/elections/blockchain
Returns all election blocks with verification status:
{
"blocks": [
{
"index": 0,
"prev_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": 1730772000,
"election_id": 1,
"election_name": "Election Présidentielle 2025",
"election_description": "Vote pour la présidence",
"candidates_count": 4,
"candidates_hash": "a7f3e9c2b1d4f8a5c3e1b9d2f4a6c8e0b3d5f7a9c1e3b5d7f9a1c3e5b7d9",
"start_date": "2025-11-07T01:59:00",
"end_date": "2025-11-14T01:59:00",
"is_active": true,
"block_hash": "7f3e9c2b1d4f8a5c3e1b9d2f4a6c8e0b3d5f7a9c1e3b5d7f9a1c3e5b7d9a1",
"signature": "8a2e1f3d5c9b7a4e6c1d3f5a7b9c1e3d5f7a9b1c3d5e7f9a1b3c5d7e9f1a3",
"creator_id": 0
}
],
"verification": {
"chain_valid": true,
"total_blocks": 1,
"timestamp": "2025-11-07T03:00:00.123456"
}
}
Verify Election Blockchain Integrity
GET /api/elections/{election_id}/blockchain-verify
Returns detailed verification report:
{
"verified": true,
"election_id": 1,
"election_name": "Election Présidentielle 2025",
"block_index": 0,
"hash_valid": true,
"chain_valid": true,
"signature_valid": true,
"timestamp": 1730772000,
"created_by": 0,
"candidates_count": 4,
"candidates_hash": "a7f3e9c2b1d4f8a5c3e1b9d2f4a6c8e0b3d5f7a9c1e3b5d7f9a1c3e5b7d9"
}
Security Features
SHA-256 Hash Chain
Each block contains the hash of the previous block, creating an unbreakable chain:
Block 0: prev_hash = "0000..."
block_hash = "7f3e..." ← depends on all block data
Block 1: prev_hash = "7f3e..." ← links to Block 0
block_hash = "a2b4..." ← depends on all block data
Block 2: prev_hash = "a2b4..." ← links to Block 1
block_hash = "c5d9..." ← depends on all block data
If any block is modified, its hash changes, breaking the chain.
Candidate Verification
Each election includes a candidates_hash - SHA-256 of all candidates at creation time:
candidates_json = json.dumps(
sorted(candidates, key=lambda x: x.get('id', 0)),
sort_keys=True,
separators=(',', ':')
)
candidates_hash = sha256(candidates_json)
This proves that the candidate list for this election cannot be modified.
RSA-PSS Signatures
Each block is signed for authentication:
signature_data = f"{block_hash}:{timestamp}:{creator_id}"
signature = sha256(signature_data)[:64] # Demo signature
In production, this would use full RSA-PSS with the election creator's private key.
Tamper Detection
The blockchain is verified on every read:
def verify_chain_integrity() -> bool:
for i, block in enumerate(blocks):
# Check previous hash link
if i > 0 and block.prev_hash != blocks[i-1].block_hash:
return False # Chain broken!
# Check block hash matches data
computed_hash = sha256(block.to_json())
if block.block_hash != computed_hash:
return False # Block modified!
return True
If any block is tampered with, verification fails and an error is returned.
Testing
Test 1: Create Election and Verify Blockchain Recording
# Start backend
docker compose up -d backend
# Wait for initialization
sleep 10
# Check blockchain has elections
curl http://localhost:8000/api/elections/blockchain
# Should show blocks array with election records
Test 2: Verify Election Integrity
# Get verification report
curl http://localhost:8000/api/elections/1/blockchain-verify
# Should show:
# "verified": true
# "hash_valid": true
# "chain_valid": true
# "signature_valid": true
Test 3: Simulate Tampering Detection
# In Python REPL or test script:
from backend.blockchain_elections import elections_blockchain
# Tamper with a block
block = elections_blockchain.blocks[0]
original_hash = block.block_hash
block.block_hash = "invalid_hash"
# Verify fails
result = elections_blockchain.verify_election_block(block.election_id)
print(result["verified"]) # False
print(result["hash_valid"]) # False
# Restore and verify passes
block.block_hash = original_hash
result = elections_blockchain.verify_election_block(block.election_id)
print(result["verified"]) # True
Test 4: Multi-Election Blockchain
# Create multiple elections (e.g., via database initialization)
# The blockchain should have a hash chain:
blocks[0].prev_hash = "0000000..." # Genesis
blocks[0].block_hash = "abc123..."
blocks[1].prev_hash = "abc123..." # Links to block 0
blocks[1].block_hash = "def456..."
blocks[2].prev_hash = "def456..." # Links to block 1
blocks[2].block_hash = "ghi789..."
# Tampering with block 1 breaks chain for block 2
blocks[1].block_hash = "invalid"
verify_chain_integrity() # False - block 2's prev_hash won't match
Database Initialization
Elections created by database scripts are automatically recorded to blockchain on backend startup:
docker/init.sql
Creates initial election:
INSERT INTO elections (name, description, start_date, end_date, elgamal_p, elgamal_g, is_active)
VALUES (
'Élection Présidentielle 2025',
'Vote pour la présidence',
NOW(),
DATE_ADD(NOW(), INTERVAL 7 DAY),
23,
5,
TRUE
);
On backend startup, this election is recorded to blockchain.
docker/create_active_election.sql
Ensures election is active and records to blockchain on startup.
docker/populate_past_elections.sql
Creates past elections for historical data - all are recorded to blockchain.
API Integration
Creating Elections Programmatically
from backend.database import SessionLocal
from backend.services import ElectionService
from datetime import datetime, timedelta
db = SessionLocal()
now = datetime.utcnow()
# Create election (automatically recorded to blockchain)
election = ElectionService.create_election(
db=db,
name="New Election",
description="Test election",
start_date=now,
end_date=now + timedelta(days=7),
elgamal_p=23,
elgamal_g=5,
is_active=True,
creator_id=1 # Admin ID
)
# Election is now in:
# 1. Database table `elections`
# 2. Blockchain (immutable record)
# 3. Accessible via /api/elections/blockchain
Verifying Without Creating
# Verify an existing election
curl http://localhost:8000/api/elections/1/blockchain-verify
# Returns verification report
# Can be used by auditors to verify elections weren't tampered with
Future Enhancements
- RSA-PSS Full Signatures: Use actual private keys instead of hash-based signatures
- Merkle Tree: Replace candidates_hash with full Merkle tree for candidate verification
- Distributed Blockchain: Replicate blockchain across multiple backend nodes
- Voter Blockchain: Record voter registration to blockchain for audit trail
- Smart Contracts: Vote tally validation via blockchain proofs
- Export/Audit Reports: Generate cryptographic proof documents for elections
Troubleshooting
Blockchain Not Recording Elections
Check backend logs:
docker compose logs backend | grep blockchain
Should see:
✓ Recorded election 1 (Election Présidentielle 2025) to blockchain
✓ Blockchain integrity verified - 1 blocks
Verification Fails
curl http://localhost:8000/api/elections/1/blockchain-verify
# If "verified": false, check:
# 1. "hash_valid": false → block data was modified
# 2. "chain_valid": false → previous block was modified
# 3. "signature_valid": false → signature is missing/invalid
Empty Blockchain
Ensure database initialization completed:
# Check elections in database
curl http://localhost:8000/api/elections/debug/all
# If elections exist but blockchain empty:
# 1. Restart backend
# 2. Check init_blockchain.py logs
# 3. Verify database connection
Files
backend/blockchain_elections.py- Core blockchain implementationbackend/init_blockchain.py- Startup initializationbackend/services.py- ElectionService.create_election() with blockchain recordingbackend/main.py- Blockchain initialization on startupbackend/routes/elections.py- API endpoints for blockchain access