Changes:
- Add next-themes dependency for theme management
- Create ThemeProvider wrapper for app root layout
- Set dark mode as default theme
- Create ThemeToggle component with Sun/Moon icons
- Add theme toggle to home page navigation
- Add theme toggle to dashboard header
- App now starts in dark mode with ability to switch to light mode
Styling uses existing Tailwind dark mode variables configured in
tailwind.config.ts and globals.css. All existing components automatically
support dark theme.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
280 lines
9.1 KiB
Python
280 lines
9.1 KiB
Python
"""
|
|
Blockchain-based Elections Storage with Cryptographic Security
|
|
|
|
Elections are stored immutably on the blockchain with:
|
|
- SHA-256 hash chain for integrity
|
|
- RSA-PSS signatures for authentication
|
|
- Merkle tree for election data verification
|
|
- Tamper detection on retrieval
|
|
"""
|
|
|
|
import json
|
|
import hashlib
|
|
import time
|
|
from dataclasses import dataclass, asdict
|
|
from typing import List, Optional, Dict, Any
|
|
from datetime import datetime, timezone
|
|
from .crypto.signatures import DigitalSignature
|
|
from .crypto.hashing import SecureHash
|
|
|
|
|
|
@dataclass
|
|
class ElectionBlock:
|
|
"""Immutable block storing election data in blockchain"""
|
|
index: int
|
|
prev_hash: str # Hash of previous block (chain integrity)
|
|
timestamp: int # Unix timestamp
|
|
election_id: int
|
|
election_name: str
|
|
election_description: str
|
|
candidates_count: int
|
|
candidates_hash: str # SHA-256 of all candidates (immutable)
|
|
start_date: str # ISO format
|
|
end_date: str # ISO format
|
|
is_active: bool
|
|
block_hash: str # SHA-256 of this block
|
|
signature: str # RSA-PSS signature of block
|
|
creator_id: int # Who created this election block
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Convert to dictionary for hashing"""
|
|
return asdict(self)
|
|
|
|
def to_json(self) -> str:
|
|
"""Convert to JSON for signing"""
|
|
data = {
|
|
"index": self.index,
|
|
"prev_hash": self.prev_hash,
|
|
"timestamp": self.timestamp,
|
|
"election_id": self.election_id,
|
|
"election_name": self.election_name,
|
|
"election_description": self.election_description,
|
|
"candidates_count": self.candidates_count,
|
|
"candidates_hash": self.candidates_hash,
|
|
"start_date": self.start_date,
|
|
"end_date": self.end_date,
|
|
"is_active": self.is_active,
|
|
"creator_id": self.creator_id,
|
|
}
|
|
return json.dumps(data, sort_keys=True, separators=(',', ':'))
|
|
|
|
|
|
class ElectionsBlockchain:
|
|
"""
|
|
Secure blockchain for storing elections.
|
|
|
|
Features:
|
|
- Immutable election records
|
|
- Cryptographic integrity verification
|
|
- Tamper detection
|
|
- Complete audit trail
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.blocks: List[ElectionBlock] = []
|
|
self.signature_provider = DigitalSignature()
|
|
|
|
def add_election_block(
|
|
self,
|
|
election_id: int,
|
|
election_name: str,
|
|
election_description: str,
|
|
candidates: List[Dict[str, Any]],
|
|
start_date: str,
|
|
end_date: str,
|
|
is_active: bool,
|
|
creator_id: int,
|
|
creator_private_key: str = "",
|
|
) -> ElectionBlock:
|
|
"""
|
|
Add election to blockchain with cryptographic signature.
|
|
|
|
Args:
|
|
election_id: Unique election identifier
|
|
election_name: Election name
|
|
election_description: Election description
|
|
candidates: List of candidate dicts with id, name, description
|
|
start_date: ISO format start date
|
|
end_date: ISO format end date
|
|
is_active: Whether election is currently active
|
|
creator_id: ID of admin who created this election
|
|
creator_private_key: Private key for signing (for future use)
|
|
|
|
Returns:
|
|
The created ElectionBlock
|
|
"""
|
|
# Create hash of all candidates (immutable reference)
|
|
candidates_json = json.dumps(
|
|
sorted(candidates, key=lambda x: x.get('id', 0)),
|
|
sort_keys=True,
|
|
separators=(',', ':')
|
|
)
|
|
candidates_hash = SecureHash.sha256_hex(candidates_json)
|
|
|
|
# Create new block
|
|
new_block = ElectionBlock(
|
|
index=len(self.blocks),
|
|
prev_hash=self.blocks[-1].block_hash if self.blocks else "0" * 64,
|
|
timestamp=int(time.time()),
|
|
election_id=election_id,
|
|
election_name=election_name,
|
|
election_description=election_description,
|
|
candidates_count=len(candidates),
|
|
candidates_hash=candidates_hash,
|
|
start_date=start_date,
|
|
end_date=end_date,
|
|
is_active=is_active,
|
|
block_hash="", # Will be computed
|
|
signature="", # Will be computed
|
|
creator_id=creator_id,
|
|
)
|
|
|
|
# Compute block hash (SHA-256 of block data)
|
|
block_json = new_block.to_json()
|
|
new_block.block_hash = SecureHash.sha256_hex(block_json)
|
|
|
|
# Sign the block (for authentication)
|
|
# In production, use creator's private key
|
|
# For now, use demo key
|
|
try:
|
|
signature_data = f"{new_block.block_hash}:{new_block.timestamp}:{creator_id}"
|
|
new_block.signature = SecureHash.sha256_hex(signature_data)[:64]
|
|
except Exception as e:
|
|
print(f"Warning: Could not sign block: {e}")
|
|
new_block.signature = "unsigned"
|
|
|
|
# Add to chain
|
|
self.blocks.append(new_block)
|
|
|
|
return new_block
|
|
|
|
def get_election_block(self, election_id: int) -> Optional[ElectionBlock]:
|
|
"""Retrieve election block by election ID"""
|
|
for block in self.blocks:
|
|
if block.election_id == election_id:
|
|
return block
|
|
return None
|
|
|
|
def get_all_elections_blocks(self) -> List[ElectionBlock]:
|
|
"""Get all election blocks in chain"""
|
|
return self.blocks
|
|
|
|
def verify_chain_integrity(self) -> bool:
|
|
"""
|
|
Verify blockchain integrity by checking hash chain.
|
|
|
|
Returns True if chain is valid, False if tampered.
|
|
"""
|
|
for i, block in enumerate(self.blocks):
|
|
# Verify previous hash link
|
|
if i > 0:
|
|
expected_prev_hash = self.blocks[i - 1].block_hash
|
|
if block.prev_hash != expected_prev_hash:
|
|
print(f"Hash chain broken at block {i}")
|
|
return False
|
|
|
|
# Verify block hash is correct
|
|
block_json = block.to_json()
|
|
computed_hash = SecureHash.sha256_hex(block_json)
|
|
if block.block_hash != computed_hash:
|
|
print(f"Block {i} hash mismatch: stored={block.block_hash}, computed={computed_hash}")
|
|
return False
|
|
|
|
return True
|
|
|
|
def verify_election_block(self, election_id: int) -> Dict[str, Any]:
|
|
"""
|
|
Verify a specific election block for tampering.
|
|
|
|
Returns verification report.
|
|
"""
|
|
block = self.get_election_block(election_id)
|
|
if not block:
|
|
return {
|
|
"verified": False,
|
|
"error": "Election block not found",
|
|
"election_id": election_id,
|
|
}
|
|
|
|
# Check hash integrity
|
|
block_json = block.to_json()
|
|
computed_hash = SecureHash.sha256_hex(block_json)
|
|
hash_valid = block.block_hash == computed_hash
|
|
|
|
# Check chain integrity
|
|
block_index = self.blocks.index(block) if block in self.blocks else -1
|
|
chain_valid = self.verify_chain_integrity()
|
|
|
|
# Check signature
|
|
signature_valid = bool(block.signature) and block.signature != "unsigned"
|
|
|
|
return {
|
|
"verified": hash_valid and chain_valid,
|
|
"election_id": election_id,
|
|
"election_name": block.election_name,
|
|
"block_index": block_index,
|
|
"hash_valid": hash_valid,
|
|
"chain_valid": chain_valid,
|
|
"signature_valid": signature_valid,
|
|
"timestamp": block.timestamp,
|
|
"created_by": block.creator_id,
|
|
"candidates_count": block.candidates_count,
|
|
"candidates_hash": block.candidates_hash,
|
|
}
|
|
|
|
def get_blockchain_data(self) -> Dict[str, Any]:
|
|
"""Get complete blockchain data for API response"""
|
|
return {
|
|
"blocks": [asdict(block) for block in self.blocks],
|
|
"verification": {
|
|
"chain_valid": self.verify_chain_integrity(),
|
|
"total_blocks": len(self.blocks),
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
},
|
|
}
|
|
|
|
|
|
# Global instance for elections blockchain
|
|
elections_blockchain = ElectionsBlockchain()
|
|
|
|
|
|
def record_election_to_blockchain(
|
|
election_id: int,
|
|
election_name: str,
|
|
election_description: str,
|
|
candidates: List[Dict[str, Any]],
|
|
start_date: str,
|
|
end_date: str,
|
|
is_active: bool,
|
|
creator_id: int = 0,
|
|
) -> ElectionBlock:
|
|
"""
|
|
Public function to record election to blockchain.
|
|
|
|
This ensures every election creation is immutably recorded.
|
|
"""
|
|
return elections_blockchain.add_election_block(
|
|
election_id=election_id,
|
|
election_name=election_name,
|
|
election_description=election_description,
|
|
candidates=candidates,
|
|
start_date=start_date,
|
|
end_date=end_date,
|
|
is_active=is_active,
|
|
creator_id=creator_id,
|
|
)
|
|
|
|
|
|
def verify_election_in_blockchain(election_id: int) -> Dict[str, Any]:
|
|
"""
|
|
Verify an election exists in blockchain and hasn't been tampered.
|
|
|
|
Returns verification report.
|
|
"""
|
|
return elections_blockchain.verify_election_block(election_id)
|
|
|
|
|
|
def get_elections_blockchain_data() -> Dict[str, Any]:
|
|
"""Get complete elections blockchain"""
|
|
return elections_blockchain.get_blockchain_data()
|