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/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
lib/
|
backend/lib/
|
||||||
lib64/
|
backend/lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
|
|||||||
@ -56,6 +56,9 @@ class ElGamalEncryption:
|
|||||||
self.p = p
|
self.p = p
|
||||||
self.g = g
|
self.g = g
|
||||||
|
|
||||||
|
# Generate keypair on initialization
|
||||||
|
self.public_key, self.private_key = self.generate_keypair()
|
||||||
|
|
||||||
def generate_keypair(self) -> Tuple[PublicKey, PrivateKey]:
|
def generate_keypair(self) -> Tuple[PublicKey, PrivateKey]:
|
||||||
"""Générer une paire de clés ElGamal"""
|
"""Générer une paire de clés ElGamal"""
|
||||||
import random
|
import random
|
||||||
@ -67,6 +70,18 @@ class ElGamalEncryption:
|
|||||||
|
|
||||||
return public, private
|
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:
|
def encrypt(self, public_key: PublicKey, message: int) -> Ciphertext:
|
||||||
"""
|
"""
|
||||||
Chiffrer un message avec ElGamal.
|
Chiffrer un message avec ElGamal.
|
||||||
@ -160,3 +175,7 @@ class SymmetricEncryption:
|
|||||||
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
|
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
|
||||||
|
|
||||||
return plaintext
|
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