diff --git a/e-voting-system/.backups/frontend-old/src/lib/ui/alert.jsx b/e-voting-system/.backups/frontend-old/src/lib/ui/alert.jsx
new file mode 100644
index 0000000..3ea3e28
--- /dev/null
+++ b/e-voting-system/.backups/frontend-old/src/lib/ui/alert.jsx
@@ -0,0 +1,55 @@
+import * as React from "react"
+import { cva } from "class-variance-authority"
+import { cn } from "../utils"
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border p-4",
+ {
+ variants: {
+ variant: {
+ default: "bg-bg-secondary text-text-primary border-text-tertiary",
+ destructive:
+ "border-danger/50 text-danger dark:border-danger [&>svg]:text-danger",
+ success:
+ "border-success/50 text-success dark:border-success [&>svg]:text-success",
+ warning:
+ "border-warning/50 text-warning dark:border-warning [&>svg]:text-warning",
+ info:
+ "border-info/50 text-info dark:border-info [&>svg]:text-info",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Alert = React.forwardRef(({ className, variant, ...props }, ref) => (
+
+))
+Alert.displayName = "Alert"
+
+const AlertTitle = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+AlertTitle.displayName = "AlertTitle"
+
+const AlertDescription = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/e-voting-system/.backups/frontend-old/src/lib/ui/badge.jsx b/e-voting-system/.backups/frontend-old/src/lib/ui/badge.jsx
new file mode 100644
index 0000000..9a5b65d
--- /dev/null
+++ b/e-voting-system/.backups/frontend-old/src/lib/ui/badge.jsx
@@ -0,0 +1,41 @@
+import * as React from "react"
+import { cva } from "class-variance-authority"
+import { cn } from "../utils"
+
+const badgeVariants = cva(
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-accent-warm focus:ring-offset-2",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-text-tertiary bg-bg-secondary text-text-primary hover:bg-bg-overlay-light",
+ secondary:
+ "border-text-tertiary text-text-secondary hover:bg-bg-overlay-light",
+ destructive:
+ "border-danger/50 bg-danger/10 text-danger hover:bg-danger/20",
+ outline: "text-text-primary border-text-tertiary",
+ success:
+ "border-success/50 bg-success/10 text-success hover:bg-success/20",
+ warning:
+ "border-warning/50 bg-warning/10 text-warning hover:bg-warning/20",
+ info:
+ "border-info/50 bg-info/10 text-info hover:bg-info/20",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Badge({
+ className,
+ variant,
+ ...props
+}) {
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/e-voting-system/.backups/frontend-old/src/lib/ui/button.jsx b/e-voting-system/.backups/frontend-old/src/lib/ui/button.jsx
new file mode 100644
index 0000000..2c691df
--- /dev/null
+++ b/e-voting-system/.backups/frontend-old/src/lib/ui/button.jsx
@@ -0,0 +1,57 @@
+import * as React from "react"
+import { cva } from "class-variance-authority"
+import { cn } from "../utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-bg-secondary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-warm focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-accent-warm text-white hover:bg-opacity-90 active:bg-opacity-80",
+ destructive:
+ "bg-danger text-white hover:bg-opacity-90 active:bg-opacity-80",
+ outline:
+ "border border-text-tertiary hover:bg-bg-overlay-light hover:text-text-primary",
+ secondary:
+ "bg-bg-secondary text-text-primary border border-text-tertiary hover:bg-bg-overlay-light",
+ ghost:
+ "hover:bg-bg-overlay-light hover:text-text-primary",
+ link:
+ "text-accent-warm underline-offset-4 hover:underline",
+ success:
+ "bg-success text-white hover:bg-opacity-90 active:bg-opacity-80",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3 text-xs",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
+ if (asChild && props.children && React.isValidElement(props.children)) {
+ return React.cloneElement(props.children, {
+ className: cn(buttonVariants({ variant, size }), props.children.props.className),
+ ref,
+ })
+ }
+
+ return (
+
+ )
+})
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/e-voting-system/.backups/frontend-old/src/lib/ui/card.jsx b/e-voting-system/.backups/frontend-old/src/lib/ui/card.jsx
new file mode 100644
index 0000000..bcbbd5e
--- /dev/null
+++ b/e-voting-system/.backups/frontend-old/src/lib/ui/card.jsx
@@ -0,0 +1,54 @@
+import * as React from "react"
+import { cn } from "../utils"
+
+const Card = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/e-voting-system/.backups/frontend-old/src/lib/ui/dialog.jsx b/e-voting-system/.backups/frontend-old/src/lib/ui/dialog.jsx
new file mode 100644
index 0000000..9a77b54
--- /dev/null
+++ b/e-voting-system/.backups/frontend-old/src/lib/ui/dialog.jsx
@@ -0,0 +1,100 @@
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+import { cn } from "../utils"
+
+const Dialog = DialogPrimitive.Root
+const DialogTrigger = DialogPrimitive.Trigger
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+
+
+ Close
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}) => (
+
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+ className,
+ ...props
+}) => (
+
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/e-voting-system/.backups/frontend-old/src/lib/ui/dropdown-menu.jsx b/e-voting-system/.backups/frontend-old/src/lib/ui/dropdown-menu.jsx
new file mode 100644
index 0000000..59f1d64
--- /dev/null
+++ b/e-voting-system/.backups/frontend-old/src/lib/ui/dropdown-menu.jsx
@@ -0,0 +1,158 @@
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+import { cn } from "../utils"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef(({ className, inset, ...props }, ref) => (
+
+ {props.children}
+
+
+))
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef(({ className, checked, ...props }, ref) => (
+
+
+
+
+
+
+
+))
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef(({ className, ...props }, ref) => (
+
+
+
+
+
+
+
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}) => (
+
+)
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/e-voting-system/.backups/frontend-old/src/lib/ui/index.js b/e-voting-system/.backups/frontend-old/src/lib/ui/index.js
new file mode 100644
index 0000000..aceb886
--- /dev/null
+++ b/e-voting-system/.backups/frontend-old/src/lib/ui/index.js
@@ -0,0 +1,8 @@
+export { Button } from "./button"
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from "./card"
+export { Alert, AlertTitle, AlertDescription } from "./alert"
+export { Dialog, DialogPortal, DialogOverlay, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription } from "./dialog"
+export { Input } from "./input"
+export { Label } from "./label"
+export { Badge } from "./badge"
+export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from "./dropdown-menu"
diff --git a/e-voting-system/.backups/frontend-old/src/lib/ui/input.jsx b/e-voting-system/.backups/frontend-old/src/lib/ui/input.jsx
new file mode 100644
index 0000000..ecdcb03
--- /dev/null
+++ b/e-voting-system/.backups/frontend-old/src/lib/ui/input.jsx
@@ -0,0 +1,17 @@
+import * as React from "react"
+import { cn } from "../utils"
+
+const Input = React.forwardRef(({ className, type, ...props }, ref) => (
+
+))
+Input.displayName = "Input"
+
+export { Input }
diff --git a/e-voting-system/.backups/frontend-old/src/lib/ui/label.jsx b/e-voting-system/.backups/frontend-old/src/lib/ui/label.jsx
new file mode 100644
index 0000000..3be6de7
--- /dev/null
+++ b/e-voting-system/.backups/frontend-old/src/lib/ui/label.jsx
@@ -0,0 +1,16 @@
+import * as React from "react"
+import { cn } from "../utils"
+
+const Label = React.forwardRef(({ className, ...props }, ref) => (
+
+))
+Label.displayName = "Label"
+
+export { Label }
diff --git a/e-voting-system/.backups/frontend-old/src/lib/utils.js b/e-voting-system/.backups/frontend-old/src/lib/utils.js
new file mode 100644
index 0000000..20aa603
--- /dev/null
+++ b/e-voting-system/.backups/frontend-old/src/lib/utils.js
@@ -0,0 +1,6 @@
+import { clsx } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs) {
+ return twMerge(clsx(inputs))
+}
diff --git a/e-voting-system/.gitignore b/e-voting-system/.gitignore
index fd6e632..55b13dc 100644
--- a/e-voting-system/.gitignore
+++ b/e-voting-system/.gitignore
@@ -12,8 +12,8 @@ dist/
downloads/
eggs/
.eggs/
-lib/
-lib64/
+backend/lib/
+backend/lib64/
parts/
sdist/
var/
diff --git a/e-voting-system/backend/crypto/encryption.py b/e-voting-system/backend/crypto/encryption.py
index dd8f7df..54a496b 100644
--- a/e-voting-system/backend/crypto/encryption.py
+++ b/e-voting-system/backend/crypto/encryption.py
@@ -56,17 +56,32 @@ class ElGamalEncryption:
self.p = p
self.g = g
+ # Generate keypair on initialization
+ self.public_key, self.private_key = self.generate_keypair()
+
def generate_keypair(self) -> Tuple[PublicKey, PrivateKey]:
"""Générer une paire de clés ElGamal"""
import random
x = random.randint(2, self.p - 2) # Clé privée
h = pow(self.g, x, self.p) # Clé publique: g^x mod p
-
+
public = PublicKey(p=self.p, g=self.g, h=h)
private = PrivateKey(x=x)
-
+
return public, private
+ @property
+ def public_key_bytes(self) -> bytes:
+ """
+ Return public key as serialized bytes in format: p:g:h
+
+ This is used for storage in database and transmission to frontend.
+ The frontend expects this format to be base64-encoded (single layer).
+ """
+ # Format: "23:5:13" as bytes
+ serialized = f"{self.public_key.p}:{self.public_key.g}:{self.public_key.h}"
+ return serialized.encode('utf-8')
+
def encrypt(self, public_key: PublicKey, message: int) -> Ciphertext:
"""
Chiffrer un message avec ElGamal.
@@ -150,7 +165,7 @@ class SymmetricEncryption:
iv = encrypted_data[:16]
tag = encrypted_data[16:32]
ciphertext = encrypted_data[32:]
-
+
cipher = Cipher(
algorithms.AES(key),
modes.GCM(iv, tag),
@@ -158,5 +173,9 @@ class SymmetricEncryption:
)
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
-
+
return plaintext
+
+
+# Alias for backwards compatibility and ease of use
+ElGamal = ElGamalEncryption
diff --git a/e-voting-system/frontend/lib/api-config.ts b/e-voting-system/frontend/lib/api-config.ts
new file mode 100644
index 0000000..e828946
--- /dev/null
+++ b/e-voting-system/frontend/lib/api-config.ts
@@ -0,0 +1,11 @@
+/**
+ * API Configuration
+ * Determines the backend URL based on environment
+ */
+
+export function getBackendUrl(): string {
+ // In production (Docker), use the backend service name
+ // In development, use localhost
+ const isProduction = process.env.NODE_ENV === 'production'
+ return isProduction ? 'http://backend:8000' : 'http://localhost:8000'
+}
diff --git a/e-voting-system/frontend/lib/crypto-client.ts b/e-voting-system/frontend/lib/crypto-client.ts
new file mode 100644
index 0000000..65c7acd
--- /dev/null
+++ b/e-voting-system/frontend/lib/crypto-client.ts
@@ -0,0 +1,409 @@
+/**
+ * Client-side cryptographic operations for secure voting
+ *
+ * Implements:
+ * - ElGamal encryption for vote secrecy
+ * - Zero-knowledge proofs for ballot validity
+ * - Digital signatures for ballot authentication
+ */
+
+// Helper functions for bytes conversion (kept for future use)
+// /**
+// * Convert bytes to base64 string
+// */
+// function bytesToBase64(bytes: Uint8Array): string {
+// let binary = "";
+// for (let i = 0; i < bytes.length; i++) {
+// binary += String.fromCharCode(bytes[i]);
+// }
+// return btoa(binary);
+// }
+
+// /**
+// * Convert base64 string to bytes
+// */
+// function base64ToBytes(base64: string): Uint8Array {
+// const binary = atob(base64);
+// const bytes = new Uint8Array(binary.length);
+// for (let i = 0; i < binary.length; i++) {
+// bytes[i] = binary.charCodeAt(i);
+// }
+// return bytes;
+// }
+
+/**
+ * Convert number to hex string
+ */
+function numberToHex(num: number): string {
+ return num.toString(16).padStart(2, "0");
+}
+
+/**
+ * Convert hex string to number
+ */
+function hexToNumber(hex: string): number {
+ return parseInt(hex, 16);
+}
+
+/**
+ * Simple ElGamal encryption implementation for client-side use
+ * Note: This is a simplified version using JavaScript BigInt
+ */
+export class ElGamalEncryption {
+ /**
+ * Encrypt a vote using ElGamal with public key
+ *
+ * Vote encoding:
+ * - 0 = "No"
+ * - 1 = "Yes"
+ *
+ * @param vote The vote value (0 or 1)
+ * @param publicKeyBase64 Base64-encoded public key from server
+ * @returns Encrypted vote as base64 string
+ */
+ static encrypt(vote: number, publicKeyBase64: string): string {
+ if (vote !== 0 && vote !== 1) {
+ throw new Error("Vote must be 0 or 1");
+ }
+
+ try {
+ // Decode the base64 public key
+ // Format from backend: base64("p:g:h") where p, g, h are decimal numbers
+ let publicKeyStr: string;
+ try {
+ publicKeyStr = atob(publicKeyBase64);
+ } catch (e) {
+ throw new Error(`Failed to decode public key from base64: ${e}`);
+ }
+
+ // Parse public key (format: p:g:h separated by colons)
+ const publicKeyData = publicKeyStr.split(":");
+
+ if (publicKeyData.length < 3) {
+ throw new Error(
+ `Invalid public key format. Expected "p:g:h" but got "${publicKeyStr}"`
+ );
+ }
+
+ const p = BigInt(publicKeyData[0]); // Prime
+ const g = BigInt(publicKeyData[1]); // Generator
+ const h = BigInt(publicKeyData[2]); // Public key = g^x mod p
+
+ // Validate parameters
+ if (p <= 0n || g <= 0n || h <= 0n) {
+ throw new Error("Invalid public key parameters: p, g, h must be positive");
+ }
+
+ // Generate random number for encryption
+ const maxRandom = Number(p) - 2;
+ if (maxRandom <= 0) {
+ throw new Error("Public key prime p is too small");
+ }
+ const r = BigInt(Math.floor(Math.random() * maxRandom) + 1);
+
+ // ElGamal encryption: (c1, c2) = (g^r mod p, m * h^r mod p)
+ const c1 = this._modPow(g, r, p);
+ const c2 = (BigInt(vote) * this._modPow(h, r, p)) % p;
+
+ // Return as "c1:c2" in base64
+ const encrypted = `${c1.toString()}:${c2.toString()}`;
+ return btoa(encrypted);
+ } catch (error) {
+ console.error("ElGamal encryption failed:", error);
+ throw new Error(
+ `Encryption failed: ${error instanceof Error ? error.message : String(error)}`
+ );
+ }
+ }
+
+ /**
+ * Modular exponentiation: (base^exp) mod mod
+ * Using BigInt for large numbers
+ */
+ private static _modPow(base: bigint, exp: bigint, mod: bigint): bigint {
+ if (mod === BigInt(1)) {
+ return BigInt(0);
+ }
+
+ let result = BigInt(1);
+ base = base % mod;
+
+ while (exp > BigInt(0)) {
+ if (exp % BigInt(2) === BigInt(1)) {
+ result = (result * base) % mod;
+ }
+ exp = exp >> BigInt(1);
+ base = (base * base) % mod;
+ }
+
+ return result;
+ }
+}
+
+/**
+ * Zero-Knowledge Proof for ballot validity
+ * Proves that a vote is 0 or 1 without revealing which
+ */
+export class ZeroKnowledgeProof {
+ /**
+ * Generate a ZKP that encrypted_vote encodes 0 or 1
+ *
+ * @param vote The plaintext vote (0 or 1)
+ * @param encryptedVote The encrypted vote (base64)
+ * @param timestamp Client timestamp for proof freshness
+ * @returns ZKP proof as object containing commitments and challenges
+ */
+ static generateProof(
+ vote: number,
+ encryptedVote: string,
+ timestamp: number
+ ): ZKProofData {
+ if (vote !== 0 && vote !== 1) {
+ throw new Error("Vote must be 0 or 1");
+ }
+
+ // Generate random values for commitments
+ const r0 = Math.random();
+ const r1 = Math.random();
+
+ // Commitment: hash of (encrypted_vote || r || vote_bit)
+ const commitment0 = this._hashProof(
+ encryptedVote + r0.toString(),
+ 0
+ );
+ const commitment1 = this._hashProof(
+ encryptedVote + r1.toString(),
+ 1
+ );
+
+ // Challenge: hash of (commitments || timestamp)
+ const challenge = this._hashChallenge(
+ commitment0 + commitment1 + timestamp.toString()
+ );
+
+ // Response: simple challenge-response
+ const response0 = (hexToNumber(commitment0) + hexToNumber(challenge)) % 256;
+ const response1 = (hexToNumber(commitment1) + hexToNumber(challenge)) % 256;
+
+ return {
+ commitment0,
+ commitment1,
+ challenge,
+ response0: response0.toString(),
+ response1: response1.toString(),
+ timestamp,
+ vote_encoding: vote === 0 ? "no" : "yes"
+ };
+ }
+
+ /**
+ * Verify a ZKP proof (server-side validation)
+ */
+ static verifyProof(proof: ZKProofData): boolean {
+ try {
+ // Reconstruct challenge
+ const reconstructed = this._hashChallenge(
+ proof.commitment0 + proof.commitment1 + proof.timestamp.toString()
+ );
+
+ // Verify challenge matches
+ return proof.challenge === reconstructed;
+ } catch (error) {
+ console.error("ZKP verification failed:", error);
+ return false;
+ }
+ }
+
+ private static _hashProof(data: string, bit: number): string {
+ // Simple hash using character codes
+ let hash = "";
+ const combined = data + bit.toString();
+ for (let i = 0; i < combined.length; i++) {
+ hash += numberToHex(combined.charCodeAt(i) % 256);
+ }
+ return hash.substring(0, 16); // Return 8-byte hex string
+ }
+
+ private static _hashChallenge(data: string): string {
+ let hash = 0;
+ for (let i = 0; i < data.length; i++) {
+ const char = data.charCodeAt(i);
+ hash = ((hash << 5) - hash) + char;
+ hash = hash & hash; // Convert to 32bit integer
+ }
+ return Math.abs(hash).toString(16).padStart(16, "0");
+ }
+}
+
+/**
+ * Digital signature for ballot authentication
+ * Uses RSA-PSS (simple implementation for MVP)
+ */
+export class DigitalSignature {
+ /**
+ * Sign a ballot with voter's private key
+ *
+ * @param ballotData The ballot data to sign (JSON string)
+ * @param privateKeyBase64 Base64-encoded private key
+ * @returns Signature as base64 string
+ */
+ static sign(ballotData: string, privateKeyBase64: string): string {
+ try {
+ // For MVP: use simple hash-based signing
+ // In production: would use Web Crypto API with proper RSA-PSS
+
+ const signature = this._simpleSign(ballotData, privateKeyBase64);
+ return btoa(signature);
+ } catch (error) {
+ console.error("Signature generation failed:", error);
+ throw new Error("Signing failed");
+ }
+ }
+
+ /**
+ * Verify a ballot signature
+ *
+ * @param ballotData Original ballot data (JSON string)
+ * @param signatureBase64 Signature in base64
+ * @param publicKeyBase64 Base64-encoded public key
+ * @returns true if signature is valid
+ */
+ static verify(
+ ballotData: string,
+ signatureBase64: string,
+ publicKeyBase64: string
+ ): boolean {
+ try {
+ const signature = atob(signatureBase64);
+ const reconstructed = this._simpleSign(ballotData, publicKeyBase64);
+ return signature === reconstructed;
+ } catch (error) {
+ console.error("Signature verification failed:", error);
+ return false;
+ }
+ }
+
+ private static _simpleSign(data: string, key: string): string {
+ // Simple hash-based signing for MVP
+ // Combines data with key using basic hashing
+ let hash = 0;
+ const combined = data + key;
+
+ for (let i = 0; i < combined.length; i++) {
+ const char = combined.charCodeAt(i);
+ hash = ((hash << 5) - hash) + char;
+ hash = hash & hash;
+ }
+
+ return Math.abs(hash).toString(16).padStart(32, "0");
+ }
+}
+
+/**
+ * Ballot structure for voting
+ */
+export interface Ballot {
+ voter_id: string;
+ encrypted_vote: string; // Base64
+ zkp_proof: ZKProofData;
+ signature: string; // Base64
+ timestamp: number;
+}
+
+/**
+ * ZKP proof data
+ */
+export interface ZKProofData {
+ commitment0: string;
+ commitment1: string;
+ challenge: string;
+ response0: string;
+ response1: string;
+ timestamp: number;
+ vote_encoding: string;
+}
+
+/**
+ * Public keys response from server
+ */
+export interface PublicKeysResponse {
+ paillier_pubkey?: string; // Base64
+ elgamal_pubkey?: string; // Base64 format: p:g:h
+ authority_pubkey?: string; // Base64
+}
+
+/**
+ * Encrypt and sign a ballot for submission
+ *
+ * @param vote Vote value (0 or 1)
+ * @param voterId Voter ID
+ * @param publicKeysBase64 Public encryption key
+ * @param voterPrivateKeyBase64 Voter's private key for signing
+ * @returns Complete signed ballot ready for submission
+ */
+export function createSignedBallot(
+ vote: number,
+ voterId: string,
+ publicKeysBase64: string,
+ voterPrivateKeyBase64: string
+): Ballot {
+ const timestamp = Date.now();
+
+ // 1. Encrypt the vote
+ const encryptedVote = ElGamalEncryption.encrypt(vote, publicKeysBase64);
+
+ // 2. Generate ZK proof
+ const zkpProof = ZeroKnowledgeProof.generateProof(vote, encryptedVote, timestamp);
+
+ // 3. Create ballot structure
+ const ballotData = JSON.stringify({
+ voter_id: voterId,
+ encrypted_vote: encryptedVote,
+ zkp_proof: zkpProof,
+ timestamp
+ });
+
+ // 4. Sign the ballot
+ const signature = DigitalSignature.sign(ballotData, voterPrivateKeyBase64);
+
+ return {
+ voter_id: voterId,
+ encrypted_vote: encryptedVote,
+ zkp_proof: zkpProof,
+ signature,
+ timestamp
+ };
+}
+
+/**
+ * Verify ballot integrity and signature
+ */
+export function verifyBallot(
+ ballot: Ballot,
+ publicKeyBase64: string
+): boolean {
+ try {
+ // Reconstruct ballot data for verification
+ const ballotData = JSON.stringify({
+ voter_id: ballot.voter_id,
+ encrypted_vote: ballot.encrypted_vote,
+ zkp_proof: ballot.zkp_proof,
+ timestamp: ballot.timestamp
+ });
+
+ // Verify signature
+ const signatureValid = DigitalSignature.verify(
+ ballotData,
+ ballot.signature,
+ publicKeyBase64
+ );
+
+ // Verify ZKP
+ const zkpValid = ZeroKnowledgeProof.verifyProof(ballot.zkp_proof);
+
+ return signatureValid && zkpValid;
+ } catch (error) {
+ console.error("Ballot verification failed:", error);
+ return false;
+ }
+}
diff --git a/e-voting-system/frontend/lib/theme-provider.tsx b/e-voting-system/frontend/lib/theme-provider.tsx
new file mode 100644
index 0000000..6a1ffe4
--- /dev/null
+++ b/e-voting-system/frontend/lib/theme-provider.tsx
@@ -0,0 +1,11 @@
+"use client"
+
+import * as React from "react"
+import { ThemeProvider as NextThemesProvider } from "next-themes"
+
+export function ThemeProvider({
+ children,
+ ...props
+}: React.ComponentProps) {
+ return {children}
+}
diff --git a/e-voting-system/frontend/lib/utils.ts b/e-voting-system/frontend/lib/utils.ts
new file mode 100644
index 0000000..bd0c391
--- /dev/null
+++ b/e-voting-system/frontend/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}