Ce projet capstone integre toutes les competences acquises dans la serie : 1. Ecrire un smart contract de vote en Solidity 2. Chiffrer les votes avec Paillier (chiffrement homomorphique) 3. Construire une preuve ZKP de validite du bulletin 4. Deployer et tester le système sur anvil puis testnet 5. Ecrire des tests Foundry pour le contrat
Construire un système de vote complet avec : 1. Smart contract Solidity pour la gouvernance (SC-9) 2. Chiffrement des votes avec Paillier (SC-16, SC-17) 3. Preuve ZKP de validite du bulletin (SC-15) 4. Deploiement sur testnet (SC-24) 5. Tests avec Foundry (SC-12)
Architecture
[Electeur] --> [Chiffre vote (Paillier)] --> [Prouve validite (ZKP)]
| |
v v
[Smart Contract: enregistre bulletin chiffre + preuve]
| |
v v
[Decompte homomorphique] --> [Dechiffrement résultat]
| |
v v
[Verification publique: tout le monde peut verifier]
Partie 1 : Smart Contract de Vote (Solidity)
Ecrivez un contrat VotingSystem qui : - Enregistre les electeurs autorises - Accepte des bulletins chiffres - Stocke les preuves de validite - Permet le decompte final
# Partie 1: Contrat Solidity# Utilisez compile_and_deploy() de SC-2try:from web3 import Web3import solcx WEB3_AVAILABLE =TrueexceptImportError: WEB3_AVAILABLE =Falseprint("web3 ou py-solc-x non installe. Installez avec: pip install web3 py-solc-x")VOTING_CONTRACT ="""// SPDX-License-Identifier: MITpragma solidity ^0.8.28;contract VotingSystem { // TODO: Definir la structure du systeme de vote // - Liste des electeurs autorises // - Mapping des bulletins chiffres (bytes) // - Phase de vote (enum: Registration, Voting, Tallying, Ended) // - Fonction registerVoter(address) // - Fonction submitBallot(bytes calldata encryptedBallot, bytes calldata proof) // - Fonction tally() qui declenche le decompte // - Events pour chaque action // Indices: // - enum Phase { Registration, Voting, Tallying, Ended } // - mapping(address => bool) public registeredVoters; // - mapping(address => bytes) public encryptedBallots; // - modifier onlyPhase(Phase _phase)}"""print("Completez le contrat VotingSystem ci-dessus")print("Inspirez-vous de SC-9 (DAO Governance) et SC-17 (E2E Voting)")
web3 ou py-solc-x non installe. Installez avec: pip install web3 py-solc-x
Completez le contrat VotingSystem ci-dessus
Inspirez-vous de SC-9 (DAO Governance) et SC-17 (E2E Voting)
Transition : Du smart contract au chiffrement client
La partie 1 a défini la structure on-chain du système de vote. Maintenant, nous devons chiffrer les votes cote client avant de les soumettre.
Pourquoi le chiffrement homomorphique ? - Le smart contract stocke les bulletins sans connaitre le contenu (privacy) - Le decompte peut se faire sur les données chiffrees (verifiability) - Seule l’autorite de decompte possede la cle privee pour dechiffrer le résultat
Prochaine étape : Implementer le chiffrement Paillier en Python (Partie 2).
Interpretation : Structure du contrat VotingSystem
Sortie obtenue : Squelette Solidity pour le contrat de vote decentralise.
Élément
Objectif
enum Phase
Machine a etats pour gerer le cycle de vie du vote
mapping(address => bool)
Suivi des electeurs autorises
mapping(address => bytes)
Stockage des bulletins chiffres et preuves
modifier onlyPhase
Contrôle d’acces selon la phase courante
Points cles : 1. Le contrat suit un pattern de state machine (4 phases distinctes) 2. Les bulletins sont stockes sous forme de bytes (données chiffrees opaques) 3. Les events permettent de tracer toutes les actions on-chain
Note technique : Le chiffrement Paillier et les ZKP se font cote client (Python). Le contrat stocke et verifie uniquement les résultats de ces opérations. Voir SC-17 pour un exemple complet d’implementation.
Indice : Structure du contrat VotingSystem
Rappel de SC-9 (DAO Governance) et SC-17 (E2E Voting) :
Éléments essentiels du contrat : 1. Enum Phase : Registration, Voting, Tallying, Ended pour gerer le cycle de vie 2. Modifiers : onlyPhase(Phase _phase) pour restreindre l’acces selon la phase 3. Events : VoterRegistered, BallotSubmitted, PhaseChanged pour la traçabilite 4. Mappings : - mapping(address => bool) public registeredVoters : Liste des electeurs autorises - mapping(address => bytes) public encryptedBallots : Bulletins chiffres par electeur - mapping(address => bytes) public proofs : Preuves ZKP par electeur
Fonctions a implementer : - registerVoter(address voter) : Admin uniquement, phase Registration - submitBallot(bytes calldata encryptedBallot, bytes calldata proof) : Phase Voting - tally() : n’importe qui peut declencher, phase Tallying - verifyProof(bytes calldata proof) : Verifie la ZKP avant d’accepter le bulletin
Propriete de securite : Un electeur ne peut voter qu’une seule fois (verifier encryptedBallots[voter] == bytes(0)).
Vocabulaire technique : - State machine : Le contrat est une machine a etats (enum Phase) - Access control : Restreindre qui peut appeler quelle fonction - Events : Logs EVM pour la traçabilite on-chain
Schema : la machine a etats du contrat VotingSystem
Le contrat de vote est une machine a etats (enum Phase) : chaque fonction n’est appelable que dans la phase courante (modifier onlyPhase), et l’admin fait progresser le cycle de vie en emettant l’event PhaseChanged. Le diagramme rend explicite ce cycle decrit ci-dessus :
Lecture. Le contrat avance strictement de gauche a droite : on ne peut voter (submitBallot) qu’en phase Voting, ni decompter (tally) avant Tallying. Cette discipline d’etats est ce qui garantit, au niveau du protocole, qu’aucun bulletin ne peut etre soumis après la cloture ni decompte avant la fin du vote – une propriete de securite structurelle, imposee par le code et non par la confiance dans l’administrateur.
Partie 2 : Chiffrement des votes (Python)
Utilisez Paillier (SC-16) pour chiffrer les votes cote client.
# Partie 2: Chiffrement Pailliertry:from phe import paillier PHE_AVAILABLE =TrueexceptImportError: PHE_AVAILABLE =Falseif PHE_AVAILABLE:# Etape: Implementer le chiffrement des votes# 1. Generer les cles Paillier# 2. Definir les candidats# 3. Chiffrer un vote (1 pour le candidat choisi, 0 pour les autres)# 4. Verifier que l'addition homomorphique fonctionne# Indice: voir SC-16 et SC-17 pour le pattern# public_key, private_key = paillier.generate_paillier_keypair()# encrypted_vote = [public_key.encrypt(1), public_key.encrypt(0), public_key.encrypt(0)]pass# Etape: Implementez le chiffrement des voteselse:print("Exercice a completer (phe non installe)")
Exercice a completer (phe non installe)
Transition : Du chiffrement a la preuve de validite
La partie 2 permet de chiffrer les votes, mais le contrat doit verifier que chaque bulletin est valide (exactement un candidat choisi). C’est le rôle de la ZKP.
Pourquoi une preuve de validite ? - Le chiffrement protege le contenu du vote (privacy) - La ZKP prouve que le vote est correct (validity) - Sans ZKP, un electeur pourrait soumettre [E(5), E(3), E(2)] (bulletin invalide)
Prochaine étape : Implementer une preuve que le bulletin chiffre contient exactement un 1 (Partie 3).
Interpretation : Structure du chiffrement client
Sortie obtenue : Squelette Python pour le chiffrement des votes avec Paillier.
Élément
Rôle
phe.paillier
Bibliotheque Python pour le chiffrement Paillier
generate_paillier_keypair()
Genere les cles publique et privee
public_key.encrypt(1)
Chiffre la valeur 1 (vote pour un candidat)
public_key.encrypt(0)
Chiffre la valeur 0 (vote contre un candidat)
Points cles : 1. Chaque vote est un tableau de valeurs chiffrees (une par candidat) 2. Seule une valeur est 1, les autres sont 0 (exactement un candidat choisi) 3. L’addition homomorphique permet d’additionner les votes chiffres sans dechiffrement
Note technique : La bibliotheque phe (Python Homomorphic Encryption) implemente le schema de Paillier. Verifiez que pip install phe est execute avant utilisation.
Indice : Chiffrement homomorphique avec Paillier
Rappel de SC-16 et SC-17 : - Paillier est un chiffrement additivement homomorphique : E(a) * E(b) = E(a+b) - Permet de compter les votes sans les dechiffrer individuellement - La cle publique sert a chiffrer, la cle privee a dechiffrer le résultat final
Workflow pour un vote : 1. Generer public_key, private_key = paillier.generate_paillier_keypair() 2. Pour 3 candidats, un vote pour le candidat 0 : [E(1), E(0), E(0)] 3. Soumettre le tableau de votes chiffres au contrat 4. Le decompte final : sum_votes = [sum(E(vote_i)) for i in candidates] 5. Dechiffrer uniquement sum_votes avec la cle privee
Propriete critique : Le contrat voit des bytes (votes chiffres) mais ne peut pas connaitre le contenu sans la cle privee.
Vocabulaire technique : - Homomorphique : Opération sur les données chiffrees equivalent a l’opération sur les données en clair - Cle publique : Utilisee pour chiffrer, partagee a tous - Cle privee : Utilisee pour dechiffrer, connue uniquement de l’autorite de decompte
Partie 3 : Preuve de validite (ZKP)
Chaque electeur doit prouver que son bulletin est valide sans reveler son vote.
# Partie 3: Preuve ZKP simplifieeimport hashlibimport secrets# Etape: Implementer une preuve que le bulletin chiffre contient# exactement un 1 et le reste des 0# # Approche simplifiee (commitment scheme):# 1. L'electeur genere un nonce r# 2. Commitment = hash(vote || r)# 3. La preuve montre que sum(votes) == 1 sans reveler les votes individuels## Voir SC-15 pour les protocoles Sigma et SC-17 pour l'application au votepass# Etape: Implementez la preuve de validiteprint("Exercice a completer")
Exercice a completer
Transition : De la ZKP a l’integration complete
Les parties 1-3 ont défini les composants individuels (smart contract, chiffrement, ZKP). La partie 4 consiste a les assembler en un système fonctionnel.
Workflow d’integration : 1. Deployer le contrat sur anvil (blockchain locale) 2. Enregistrer les electeurs autorises 3. Chiffrer les votes (Paillier) + generer les preuves (ZKP) 4. Soumettre les bulletins chiffres au contrat 5. Decompter les votes (addition homomorphique) 6. Dechiffrer le résultat final avec la cle privee
Prochaine étape : Assembler le pipeline complet et tester (Partie 4).
Interpretation : Structure de la preuve ZKP
Sortie obtenue : Squelette Python pour implementer une preuve de validite de bulletin.
Élément
Rôle
hashlib
Fonctions de hachage pour le commitment
secrets
Generation de nonces cryptographiquement securises
hash(vote \|\| r)
Commitment qui lie le vote au nonce
Points cles : 1. La preuve doit demontrer que sum(votes) == 1 (exactement un candidat choisi) 2. Le nonce r assure que la preuve est non-interactive (Fiat-Shamir heuristic) 3. Le contrat doit verifier la preuve avant d’accepter le bulletin chiffre
Note technique : Cette approche simplifiee utilise un commitment scheme. Une ZKP complete (comme zk-SNARK) serait plus complexe mais plus succincte. Voir SC-15 pour les concepts avances.
Indice : Preuve de validite sans revelation
Objectif : Prouver que le bulletin chiffre contient exactement un vote pour un candidat (1) et des zeros pour les autres, sans reveler pour qui.
Approche suggeree : 1. Commitment scheme : L’electeur calcule C = hash(votes || r) où r est un nonce aleatoire 2. Challenge-response : Le contrat peut demander une preuve que sum(votes) == 1 3. ZKP simplifie : Utiliser le fait que le chiffrement Paillier est additivement homomorphique
Rappel de SC-15 et SC-17 : - Protocol Sigma : engagement → challenge → reponse - Preuve de connaissance : “Je connais un vote tel que sum=1” - Pas besoin de reveler le vote lui-même, seulement sa validite
Vocabulaire technique : - Commitment : Valeur qui engage l’electeur a son vote sans le reveler - Zero-knowledge : La preuve ne reve rien d’autre que la validite - Soundness : Impossible de prouver un bulletin invalide - Zero-knowledge : La preuve ne revele rien d’autre que la validite
Partie 4 : Integration et deploiement
Assemblez les 3 parties et deployez sur anvil (local) puis sur Sepolia (testnet).
# Partie 4: Integration complete# TODO:# 1. Deployer VotingSystem sur anvil# 2. Enregistrer 3 electeurs# 3. Chaque electeur chiffre et soumet son vote# 4. Effectuer le decompte homomorphique# 5. Verifier les resultats# 6. (Bonus) Deployer sur Sepolia# Workflow:# ANVIL_URL = "http://127.0.0.1:8545"# w3 = Web3(Web3.HTTPProvider(ANVIL_URL))# contract, receipt = compile_and_deploy(w3, VOTING_CONTRACT, deployer)# ... enregistrer, voter, decompterpass# Etape: Assemblez et deployez le systeme completprint("Exercice a completer")
Exercice a completer
Transition : De l’integration aux tests
La partie 4 permet de deployer et tester manuellement le système. La partie 5 consiste a ecrire des tests automatises avec Foundry.
Pourquoi des tests Foundry ? - Tests reproductibles et automatisables - Couverture de tous les cas d’erreur (revert, access control) - Integration dans CI/CD pour développement continu - Les cheatcodes Foundry permettent de simuler n’importe quel scénario
Prochaine étape : Ecrire des tests Foundry pour valider le contrat (Partie 5, bonus).
Interpretation : Workflow d’integration
Sortie obtenue : Squelette du workflow complet d’integration des 3 composants.
Étape
Composant
Objectif
1
Anvil
Blockchain locale pour développement
2
Web3.py
Interface Python pour interagir avec le contrat
3
Chiffrement
Votes transformes en bytes via Paillier
4
Soumission
contract.functions.submitBallot().transact()
5
Decompte
Addition homomorphique des votes chiffres
Points cles : 1. Anvil (http://127.0.0.1:8545) fournit un environnement Ethereum local instantane 2. Chaque electeur doit etre enregistre avant de pouvoir voter 3. Le decompte homomorphique permet d’additionner les votes chiffres sans les dechiffrer individuellement
Note technique : Pour Sepolia, remplacez ANVIL_URL par votre RPC provider (Infura/Alchemy) et ajoutez une cle privee avec des fonds Sepolia dans .env.
Partie 5 : Tests Foundry (Bonus)
Ecrivez des tests Foundry pour le contrat VotingSystem.
# Partie 5: Tests FoundryVOTING_TEST ="""// SPDX-License-Identifier: MITpragma solidity ^0.8.28;import "forge-std/Test.sol";// import "../src/VotingSystem.sol";// TODO: Ecrivez les tests pour VotingSystem// - test_RegisterVoter()// - test_SubmitBallot()// - test_OnlyRegisteredCanVote()// - test_CannotVoteTwice()// - test_Tally()// - test_PhaseTransitions()// Utilisez les cheatcodes de SC-12:// - vm.prank(voter) pour simuler un electeur// - vm.expectRevert() pour les cas d'erreur// - vm.warp() si vous avez des deadlines"""print("Squelette de test Foundry pour VotingSystem")print(VOTING_TEST)print("\nPour executer: forge test -vvv")
Squelette de test Foundry pour VotingSystem
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import "forge-std/Test.sol";
// import "../src/VotingSystem.sol";
// TODO: Ecrivez les tests pour VotingSystem
// - test_RegisterVoter()
// - test_SubmitBallot()
// - test_OnlyRegisteredCanVote()
// - test_CannotVoteTwice()
// - test_Tally()
// - test_PhaseTransitions()
// Utilisez les cheatcodes de SC-12:
// - vm.prank(voter) pour simuler un electeur
// - vm.expectRevert() pour les cas d'erreur
// - vm.warp() si vous avez des deadlines
Pour executer: forge test -vvv
Interpretation : Structure de test Foundry
Sortie obtenue : Squelette de code Solidity pour les tests du contrat VotingSystem.
Élément
Objectif
import "forge-std/Test.sol"
Importe les utilitaires de test de Foundry
vm.prank(voter)
Simule l’envoi d’une transaction depuis un compte spécifique
vm.expectRevert()
Verifie qu’une transaction echoue avec un revert
vm.warp(timestamp)
Modifie le timestamp du bloc (pour deadlines)
Points cles : 1. Les tests Foundry utilisent des “cheatcodes” (vm.*) pour manipuler l’environnement blockchain 2. Chaque fonction de test doit commencer par test_ pour etre decouverte par Forge 3. Les tests doivent couvrir les “happy paths” et les cas d’erreur
Note technique : La commande forge test -vvv execute tous les tests avec une verbosite maximale (affiche les console.log et les traces d’exécution).
Critères d’evaluation
Critere
Points
Description
Smart Contract
30%
Contrat compile, deploye, fonctionnel
Chiffrement
25%
Votes chiffres, decompte homomorphique correct
ZKP
20%
Preuve de validite du bulletin
Deploiement
15%
Fonctionne sur anvil + testnet
Tests
10%
Au moins 3 tests Foundry qui passent
Bonus
Vote sur mainnet L2 (+5%)
Interface web basique (+5%)
Multi-candidats (>2) (+5%)
Verification publique complete (+5%)
Conclusion : Synthese du projet
Ce projet capstone a permis d’integrer tous les concepts de la serie SmartContracts dans une application complete.
Composant
Notebook d’origine
Rôle dans le projet
Smart Contract
SC-9 (Governance)
Stockage on-chain, state machine, access control
Chiffrement Paillier
SC-16 (HE)
Privacy des votes, decompte homomorphique
ZKP
SC-15 (ZKP)
Preuve de validite sans revelation du vote
E2E Voting
SC-17 (Voting)
Pattern complet de vote verifiable
Deploiement
SC-24 (Testnet)
anvil local + Sepolia testnet
Tests
SC-12 (Foundry)
Validation automatisee du contrat
Points cles retires : 1. Privacy + Verifiability : Le chiffrement protege le contenu, la ZKP prouve la validite 2. Homomorphic encryption : Permet le decompte sans dechiffrement individuel 3. State machine : Le contrat suit un cycle de vie strict (Registration → Voting → Tallying → Ended) 4. Tests automatises : Foundry permet de valider tous les scénarios
Prochaine étape : Deployer sur un testnet public (Sepolia) et, en bonus, sur mainnet L2 (Arbitrum/Optimism).
Ce projet capstone a permis de consolider l’ensemble des competences developpees tout au long de la serie SmartContracts. En construisant un système de vote decentralise complet, nous avons articule cinq piliers fondamentaux : la programmation de smart contracts en Solidity avec une machine a etats robuste (Registration, Voting, Tallying, Ended), le chiffrement homomorphique Paillier pour garantir la confidentialite des bulletins tout en permettant un decompte verifiable, les preuves a divulgation nulle (ZKP) pour prouver la validite de chaque bulletin sans reveler son contenu, le deploiement progressif d’anvil vers les testnets publics, et la validation automatisee via les tests Foundry avec cheatcodes.
L’integration de ces composants illustre un principe central de la securite blockchain : la separation entre privacy (protection du contenu individuel via chiffrement) et verifiability (preuve publique de correction via ZKP). Le modèle de state machine du contrat, combine au typage fort du chiffrement homomorphique et aux commitment schemes, forme une architecture ou chaque couche apporte une garantie complementaire. Ce pattern de composition de primitives cryptographiques se retrouve dans les systèmes de vote reels (comme les implementations basées sur MACI ou Semaphore) et dans les protocoles DeFi avances utilisant des preuves a divulgation nulle pour la conformite reglementaire.
Les perspectives d’approfondissement incluent l’utilisation de zk-SNARKs (Groth16, PLONK) pour des preuves plus succinctes et verificables on-chain a cout reduit, l’implementation d’un mécanisme de seuil de dechiffrement (threshold decryption) pour eliminer le point de confiance unique de l’autorite de decompte, et le deploiement sur un L2 (Base, Arbitrum) pour beneficier de couts de transaction negligeables tout en heritant de la securite d’Ethereum.