Tornado Cash et le Zero-knowledge proof

Tornado

Un des évènements tech important du mois d’Août qui a été, à mon avis, sous médiatisé est l’arrestation de Alexey Pertsey aux pays bas. Alexey est supposé être un des développeurs du service Tornado Cash. Service qui a été utilisé pour rendre difficile à suivre des transferts de fonds sur la blockchain Ethereum. Mais Tornado Cash n’est pas un service web classique, c’est un Smart Contract. Le code est ouvert, les développeurs ne prenaient pas de fees sur les transactions, ils n’ont donc fait qu’assembler des algorithmes connus. L’Office of Foreign Assets Control (OFAC) a même interdit l’utilisation de Tornado Cash début Août. Mais dans un monde décentralisé c’est simplement impossible. Personne ne peut interdire l’appel d’un Smart Contract. Petite cerise sur le gâteau, Github a bien sûr fermé leur repository sur demande du département du Trésor Américain (ils ne doivent pas connaitre l’effet Streisand.

Mais ce qui m’intéresse c’est l’aspect technique :) Comment on peut “cacher” des mouvements de crypto monnaie dans une blockchain où toutes les transactions sont publiques ? Et c’est à ce moment que j’ai découvert les algorithmes de Zero-Knowledge Proof ou preuves à divulgation nulle de connaissance en français.

Tornado Cash permet de déposer une somme de crypto monnaie sur le compte du contrat et une autre personne vient la prendre plus tard. Et sur la blockchain, vous pouvez suivre toutes les transactions et donc les appels aux smart contract. Par exemple ci dessous un appel à un contact du type Tornado Cash pour récupérer des fonds :

Transaction

Donc comment cacher le lien entre la personne qui dépose la monnaie et la personne qui la récupère ? Premièrement en imposant un montant unique de dépôt par contrat. Sinon il est assez simple de faire le lien entre un dépôt de 4,23 ETH et un retrait du même montant. Tornado a donc publié plusieurs contrats à 0.1 ETH, 1 ETH, 10 ETH et même 100 ETH.

Mix

Ensuite il faut que les données envoyées au moment du dépôt ne soient pas les mêmes que pour le retrait. Comme les paramètres sont lisibles en clair, si on transmet un “secret” au moment du dépôt nécessaire pour le retrait n’importe qui peut le lire et prendre le montant déposé.

C’est à ce moment là qu’intervient la notion de Zero-knowledge proof (ZKP). Ca répond à la question : Comment je peux prouver que je possède un secret sans le dévoiler. Je ne vais pas expliquer en détail ici le concept. Wikipedia le fait bien mieux que moi. J’ai même trouvé une excellente vidéo sur la chaine de Octo d’un projet de stage sur le sujet. Et chapeau Aymeric Noel pour avoir aussi bien expliqué cela pour un stage de fin d’étude. C’est le type de stagiaire que l’on rêve tous d’avoir dans son équipe !

Donc maintenant que vous connaissez le principe de ZKP, vous comprenez que pour un smart contract il faut utiliser une version non interactive et Tornado a choisi d’utiliser des preuves SNARKs car elles ne sont pas trop grosses et rapides à vérifier.

Pour résumer le principe, la personne qui fait le dépôt est le propriétaire d’un secret. Ce secret, il va l’associer à un autre élément (le nullifier) et en faire un hash que l’on va appeler commitment. Ensuite, avec ce même secret, il va construire une preuve pour que le contrat puisse être sûr qu’il est en possession du secret qui a créé ce commitment et qu’il peut reprendre les fonds.

Zkp

Etudions maintenant le code du contrat. Même si des projets ont été fermés, il est assez simple de retrouver des copies.

Le constructeur

Constructeur

C’est au moment de la création du contrat qu’on fixe la valeur du montant que l’on va pouvoir échanger (dénomination). On voit aussi que le vérifier et le hasher sont des liens vers d’autres smart contracts. Ca permet de pouvoir les changer pour les faire évoluer. Le hasher permet de réaliser un hash MiMC. Algorithme qui a été créé pour minimiser son coût d’exécution sur EVM. Le contrat doit répondre à cette interface :

Interface1

Le contrat verifier va être utilisé au moment de retirer les crédits pour vérifier la preuve.

Interface2

Le dépôt

Depot

Le seul paramètre à passer c’est le commitment. C’est un pedersen hash du nullifier et du secret. La première ligne vérifie si ce commitment n’a pas déjà été utilisé. Ensuite on le stocke quand un arbre de merkle. Pour optimiser au maximum la taille des variables du contrat, on ne garde qu’un historique de n racines de l’arbre. Je vous invite à étudier le contrat MerkleTreeWithHistory, les optimisations au niveau temps d’exécution et taille de stockage sont impressionnantes.

Ensuite on sauvegarde l’utilisation de notre commitment dans un tableau de boolean et on appelle _processDeposit(). Aujourd’hui il y a 2 implémentations, une pour un contrat de transfert ETH et un autre pour des transferts de token fongible ERC20. Nous allons nous concentrer juste sur le transfert ETH. Et dans ce cas la fonction ne fait que vérifier que la transaction emporte bien la bonne quantité de monnaie.

ETH

A la fin on émet un évènement pour signaler l’ajout de ce commitment, sa position dans la chaine et le timestamp du block de la transaction.

Le retrait

withdraw

C’est là que ça devient intéressant ! La liste de paramètres devient importante:

Les premières lignes vérifient si les frais ne sont pas plus élevés que le montant à transférer, que le nullifier n’a pas déjà été utilisé et si la racine de l’arbre de Merkle est bien dans notre historique.

Arrive ensuite la ligne “magique”, avec ces paramètres on va prouver que nous avons bien le secret qui a créé le commitment et que ce commitment est bien dans l’arbre de Merkle. Sans savoir de quel commitment il s’agit ! Et c’est la toute la force du contrat, même lui ne peut pas faire le lien entre le dépôt et le retrait. On sait juste que nous sommes à l’origine de ce commitment et qu’il est bien dans notre arbre. On va aussi éviter les rejeux en notant le hash du nullifier comme déjà utilisé.

On passe la main ensuite à la méthode _processWithdraw() spécifique à ETH :

withdraw

C’est elle qui va gérer les transferts de fonds entre le recipient et le relayer si des fees sont appliqués.

Conclusion

Et voilà comment 500 lignes de code Solidity peut faire trembler le département du Trésor Américain. Je ne suis pas plus entré dans le détail au niveau de la création et de la vérification de la preuve pour 2 raisons. Déjà l’article est déjà assez long, mais en plus je n’ai pas complètement compris son fonctionnement. C’est super complexe et j’ai du mal à le refaire. Je vais donc continuer à creuser cette partie pour essayer de proposer un projet Github plus détaillé.

Et maintenant que vous savez ce que fait Tornado Cash, que pensez-vous de l’arrestation de Alexey Pertsey ? La FIOD (Fiscal Information and Investigation Service des Pays Bas) dit :

le développement d’un outil n’est pas interdit, mais si un outil a été créé dans le seul but de commettre des actes criminels, par exemple, pour dissimuler des flux d’argent criminels, alors la mise en ligne/la mise à disposition d’un outil développé peut être punissable

Mais le développeur n’est pas forcément la personne qui a déployé le contrat et comment un développeur peut être tenu responsable de l’utilisation de son code ? Est-ce que Tim Berners-Lee est responsable de la création de site pédophile ? Et c’est une bonne chose de s’attaquer au blanchiment d’argent, mais les dernières statistiques montrent que aux US le Bitcoin représente 1% de la fraude. Les 99 autres % sont faits en dollars.

Est-ce que les développeurs de jeux de casino ont du souci à ce faire ? Je vois bien que c’est facile de diaboliser les blockchains à chaque occasion, mais s’il vous plait si vous avez un minimum de compétences techniques, renseignez vous avant. Essayez de comprendre avant de critiquer systématiquement.


Une vidéo qui résume aussi très bien le fonctionnement de Tornado : ici

Le site Tornado étant supprimé, j’ai recopié ici les PDF de l’audit du contrat :