- FastAPI backend with JWT authentication - ElGamal, RSA-PSS, ZK-proofs crypto modules - HTML5/JS frontend SPA - MariaDB database with 5 tables - Docker Compose with 3 services (frontend, backend, mariadb) - Comprehensive tests for cryptography - Typst technical report (30+ pages) - Makefile with development commands
123 lines
3.0 KiB
Python
123 lines
3.0 KiB
Python
"""
|
|
Preuves de connaissance zéro (Zero-Knowledge Proofs).
|
|
Pour démontrer la validité d'un vote sans révéler le contenu.
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
import random
|
|
from typing import Tuple
|
|
|
|
|
|
@dataclass
|
|
class ZKProofChallenge:
|
|
"""Défi pour une preuve ZK interactive"""
|
|
challenge: int
|
|
|
|
|
|
@dataclass
|
|
class ZKProofResponse:
|
|
"""Réponse à un défi ZK"""
|
|
response: int
|
|
|
|
|
|
@dataclass
|
|
class ZKProof:
|
|
"""Preuve de connaissance zéro complète"""
|
|
commitment: int
|
|
challenge: int
|
|
response: int
|
|
|
|
|
|
class ZKProofProtocol:
|
|
"""
|
|
Protocole de Fiat-Shamir pour preuves de connaissance zéro.
|
|
|
|
Cas d'usage dans le vote:
|
|
- Prouver qu'on a déjà voté sans révéler le vote
|
|
- Prouver la correctionction d'un bullettin
|
|
"""
|
|
|
|
@staticmethod
|
|
def generate_proof(secret: int, p: int, g: int) -> Tuple[int, int]:
|
|
"""
|
|
Générer un commitment (première étape du protocole).
|
|
|
|
Args:
|
|
secret: Le secret à prouver (ex: clé privée)
|
|
p: Module premier
|
|
g: Générateur
|
|
|
|
Returns:
|
|
(commitment, random_value)
|
|
"""
|
|
r = random.randint(2, p - 2)
|
|
commitment = pow(g, r, p)
|
|
return commitment, r
|
|
|
|
@staticmethod
|
|
def generate_challenge(p: int) -> int:
|
|
"""Générer un défi aléatoire"""
|
|
return random.randint(1, p - 2)
|
|
|
|
@staticmethod
|
|
def compute_response(
|
|
secret: int,
|
|
random_value: int,
|
|
challenge: int,
|
|
p: int
|
|
) -> int:
|
|
"""
|
|
Calculer la réponse au défi (non-interactif).
|
|
response = random_value + challenge * secret
|
|
"""
|
|
return (random_value + challenge * secret) % (p - 1)
|
|
|
|
@staticmethod
|
|
def verify_proof(
|
|
commitment: int,
|
|
challenge: int,
|
|
response: int,
|
|
public_key: int,
|
|
p: int,
|
|
g: int
|
|
) -> bool:
|
|
"""
|
|
Vérifier la preuve.
|
|
Vérifie que: g^response = commitment * public_key^challenge (mod p)
|
|
"""
|
|
left = pow(g, response, p)
|
|
right = (commitment * pow(public_key, challenge, p)) % p
|
|
return left == right
|
|
|
|
@staticmethod
|
|
def fiat_shamir_proof(
|
|
secret: int,
|
|
public_key: int,
|
|
message: bytes,
|
|
p: int,
|
|
g: int
|
|
) -> ZKProof:
|
|
"""
|
|
Générer une preuve Fiat-Shamir non-interactive.
|
|
"""
|
|
# Étape 1: commitment
|
|
commitment, r = ZKProofProtocol.generate_proof(secret, p, g)
|
|
|
|
# Étape 2: challenge généré via hash(commitment || message)
|
|
import hashlib
|
|
challenge_bytes = hashlib.sha256(
|
|
str(commitment).encode() + message
|
|
).digest()
|
|
challenge = int.from_bytes(challenge_bytes, 'big') % (p - 1)
|
|
|
|
# Étape 3: réponse
|
|
response = ZKProofProtocol.compute_response(
|
|
secret, r, challenge, p
|
|
)
|
|
|
|
return ZKProof(
|
|
commitment=commitment,
|
|
challenge=challenge,
|
|
response=response
|
|
)
|