CIA/e-voting-system/BLOCKCHAIN_FLOW.md

15 KiB

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

// 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)

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)

@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)

# 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

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:

// 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)

@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)

# 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