fix: Correct ElGamal public key serialization and .gitignore Python lib paths
- Fix ElGamalEncryption to generate keypair on initialization and provide public_key_bytes property with proper "p:g:h" UTF-8 format - Add ElGamal alias for backward compatibility with imports - Improve frontend error handling with detailed base64 decode error messages - Update .gitignore to specifically ignore backend/lib/ and backend/lib64/ instead of all lib directories, preserving frontend node_modules-style lib/ This fixes the "Invalid public key format" error that was preventing vote submission during testing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0ea3aa0a4e
commit
3aa988442f
55
e-voting-system/.backups/frontend-old/src/lib/ui/alert.jsx
Normal file
55
e-voting-system/.backups/frontend-old/src/lib/ui/alert.jsx
Normal file
@ -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) => (
|
||||
<div
|
||||
ref={ref}
|
||||
role="alert"
|
||||
className={cn(alertVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Alert.displayName = "Alert"
|
||||
|
||||
const AlertTitle = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<h5
|
||||
ref={ref}
|
||||
className={cn("mb-1 font-medium leading-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertTitle.displayName = "AlertTitle"
|
||||
|
||||
const AlertDescription = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDescription.displayName = "AlertDescription"
|
||||
|
||||
export { Alert, AlertTitle, AlertDescription }
|
||||
41
e-voting-system/.backups/frontend-old/src/lib/ui/badge.jsx
Normal file
41
e-voting-system/.backups/frontend-old/src/lib/ui/badge.jsx
Normal file
@ -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 (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
57
e-voting-system/.backups/frontend-old/src/lib/ui/button.jsx
Normal file
57
e-voting-system/.backups/frontend-old/src/lib/ui/button.jsx
Normal file
@ -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
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
54
e-voting-system/.backups/frontend-old/src/lib/ui/card.jsx
Normal file
54
e-voting-system/.backups/frontend-old/src/lib/ui/card.jsx
Normal file
@ -0,0 +1,54 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "../utils"
|
||||
|
||||
const Card = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("rounded-lg border border-text-tertiary bg-bg-secondary shadow-md card-elevation", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6 border-b border-text-tertiary", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<h2
|
||||
ref={ref}
|
||||
className={cn("text-2xl font-semibold leading-none tracking-tight text-text-primary", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn("text-sm text-text-secondary", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
100
e-voting-system/.backups/frontend-old/src/lib/ui/dialog.jsx
Normal file
100
e-voting-system/.backups/frontend-old/src/lib/ui/dialog.jsx
Normal file
@ -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) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||
|
||||
const DialogContent = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-text-tertiary bg-bg-secondary p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-bg-secondary transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-accent-warm focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-bg-overlay-light data-[state=open]:text-text-secondary">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPortal>
|
||||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogHeader.displayName = "DialogHeader"
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogFooter.displayName = "DialogFooter"
|
||||
|
||||
const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight text-text-primary",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||
|
||||
const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-text-secondary", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
}
|
||||
@ -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) => (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm font-medium outline-none focus:bg-bg-overlay-light data-[state=open]:bg-bg-overlay-light text-text-primary",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{props.children}
|
||||
<ChevronRight className="ml-auto h-4 w-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
DropdownMenuSubTrigger.displayName =
|
||||
DropdownMenuPrimitive.SubTrigger.displayName
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"min-w-[8rem] overflow-hidden rounded-md border border-text-tertiary bg-bg-secondary p-1 text-text-primary shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-2 data-[state=open]:slide-in-from-right-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName
|
||||
|
||||
const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"min-w-[8rem] overflow-hidden rounded-md border border-text-tertiary bg-bg-secondary p-1 text-text-primary shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-2 data-[state=open]:slide-in-from-right-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
|
||||
const DropdownMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-bg-overlay-light focus:text-text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50 text-text-primary",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
|
||||
const DropdownMenuCheckboxItem = React.forwardRef(({ className, checked, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-bg-overlay-light focus:text-text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50 text-text-primary",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
))
|
||||
DropdownMenuCheckboxItem.displayName =
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
||||
|
||||
const DropdownMenuRadioItem = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-bg-overlay-light focus:text-text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50 text-text-primary",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Circle className="h-2 w-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
))
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold text-text-primary",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-text-tertiary", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||
|
||||
const DropdownMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<span
|
||||
className={cn("ml-auto text-xs tracking-widest text-text-secondary", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuRadioGroup,
|
||||
}
|
||||
@ -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"
|
||||
17
e-voting-system/.backups/frontend-old/src/lib/ui/input.jsx
Normal file
17
e-voting-system/.backups/frontend-old/src/lib/ui/input.jsx
Normal file
@ -0,0 +1,17 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "../utils"
|
||||
|
||||
const Input = React.forwardRef(({ className, type, ...props }, ref) => (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-text-tertiary bg-bg-secondary px-3 py-2 text-sm text-text-primary placeholder:text-text-tertiary ring-offset-bg-secondary transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-warm focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
||||
16
e-voting-system/.backups/frontend-old/src/lib/ui/label.jsx
Normal file
16
e-voting-system/.backups/frontend-old/src/lib/ui/label.jsx
Normal file
@ -0,0 +1,16 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "../utils"
|
||||
|
||||
const Label = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-text-primary",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Label.displayName = "Label"
|
||||
|
||||
export { Label }
|
||||
6
e-voting-system/.backups/frontend-old/src/lib/utils.js
Normal file
6
e-voting-system/.backups/frontend-old/src/lib/utils.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
4
e-voting-system/.gitignore
vendored
4
e-voting-system/.gitignore
vendored
@ -12,8 +12,8 @@ dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
backend/lib/
|
||||
backend/lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
|
||||
@ -56,6 +56,9 @@ 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
|
||||
@ -67,6 +70,18 @@ class ElGamalEncryption:
|
||||
|
||||
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.
|
||||
@ -160,3 +175,7 @@ class SymmetricEncryption:
|
||||
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
|
||||
|
||||
return plaintext
|
||||
|
||||
|
||||
# Alias for backwards compatibility and ease of use
|
||||
ElGamal = ElGamalEncryption
|
||||
|
||||
11
e-voting-system/frontend/lib/api-config.ts
Normal file
11
e-voting-system/frontend/lib/api-config.ts
Normal file
@ -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'
|
||||
}
|
||||
409
e-voting-system/frontend/lib/crypto-client.ts
Normal file
409
e-voting-system/frontend/lib/crypto-client.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
11
e-voting-system/frontend/lib/theme-provider.tsx
Normal file
11
e-voting-system/frontend/lib/theme-provider.tsx
Normal file
@ -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<typeof NextThemesProvider>) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
}
|
||||
6
e-voting-system/frontend/lib/utils.ts
Normal file
6
e-voting-system/frontend/lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user