
La plus subtile application de ZK : une analyse du principe et de la logique commerciale de Tornado Cash
TechFlow SélectionTechFlow Sélection

La plus subtile application de ZK : une analyse du principe et de la logique commerciale de Tornado Cash
TornadoCash permet de dissimuler le lien entre le déposant et le retraitant ; dans le cas d'un grand nombre d'utilisateurs, cela revient à un quartier animé où, une fois que le suspect s'est mêlé à la foule, les forces de l'ordre ont du mal à le retrouver.
Auteur : Faust, Geek web3
Introduction : Récemment, Vitalik et plusieurs chercheurs ont cosigné un nouvel article scientifique mentionnant comment Tornado Cash pourrait implémenter un schéma anti-blanchiment (en faisant simplement la preuve que son dépôt appartient à un ensemble ne contenant pas d'argent sale). Cependant, cet article manque d'explications détaillées sur la logique métier et les principes fondamentaux de Tornado Cash, ce qui le rend difficile à comprendre.
Par ailleurs, il est important de noter que les projets de confidentialité comme Tornado sont parmi les rares à exploiter réellement la propriété de « connaissance nulle » de l'algorithme ZK-SNARK. En revanche, la plupart des solutions Rollup qui se réclament du terme « ZK » n'utilisent en réalité que la propriété de concision du ZK-SNARK. On confond souvent Validity Proof et ZK, alors que Tornado constitue justement un excellent cas d'étude pour bien comprendre les applications réelles de la connaissance nulle.
L'auteur de cet article avait rédigé en 2022, pour Web3Caff Research, un article sur les principes de Tornado. Il en extrait ici certains passages qu'il développe davantage afin de proposer une explication complète et accessible du fonctionnement de Tornado Cash.
Le principe du « Tornade »
Tornado Cash est un protocole de mixage utilisant la preuve à connaissance nulle (zero-knowledge proof). La version ancienne a été mise en service en 2019, tandis qu'une version bêta de la nouvelle version a été lancée fin 2021. L’ancienne version de Tornado est essentiellement décentralisée : ses contrats sur la blockchain sont open source, sans contrôle par signature multiple (multisig), et son interface frontale est également open source, avec une sauvegarde hébergée sur le réseau IPFS. Étant donné que l’architecture de l’ancienne version est plus simple et plus facile à comprendre, c’est celle-ci qui sera analysée dans cet article.
L'idée centrale de Tornado est la suivante : mélanger un grand nombre d'opérations de dépôt et de retrait. Un utilisateur dépose des jetons (tokens) dans Tornado, puis présente une preuve ZK attestant de ce dépôt, avant de retirer les fonds vers une nouvelle adresse, brisant ainsi tout lien entre les adresses de dépôt et de retrait.

Pour résumer plus précisément, Tornado ressemble à une boîte vitrée remplie de pièces de monnaie déposées par différentes personnes. On peut voir qui a mis les pièces, mais celles-ci étant parfaitement interchangeables. Si quelqu’un, inconnu jusqu’alors, prend une pièce dans la boîte, il devient très difficile de déterminer à qui elle appartenait initialement.

Ce type de situation n’est pas rare : lorsque nous échangeons quelques ETH via Uniswap, nous ne savons jamais exactement qui a fourni ces ETH, car de nombreuses personnes ont contribué à la liquidité du pool. La différence majeure est que chaque transaction sur Uniswap exige un paiement équivalent en jetons, et ne permet pas de transférer des fonds de manière privée à autrui. En revanche, un mixeur comme Tornado autorise le retrait dès lors que le demandeur peut présenter un justificatif de dépôt.
Pour que les opérations de dépôt et de retrait semblent homogènes, chaque dépôt dans le pool Tornado et chaque retrait du pool impliquent un montant fixe identique. Par exemple, dans un même pool, 100 déposants et 100 retraits peuvent être publics, mais aucun lien apparent n’existe entre eux, et chaque personne dépose et retire exactement le même montant. Cette uniformité brouille les pistes : impossible de faire correspondre un dépôt à un retrait selon le montant, ce qui rompt la traçabilité des flux financiers. Il va sans dire que cela offre un terrain favorable au blanchiment d’argent.

Mais une question cruciale se pose : comment un retraitant peut-il prouver qu’il a effectivement déposé des fonds ? L’adresse qui demande le retrait n’a aucun lien visible avec les adresses ayant effectué des dépôts. Comment alors valider sa légitimité ? La solution la plus directe serait de révéler publiquement quel dépôt lui correspond, mais cela trahirait immédiatement son identité. C’est ici que la preuve à connaissance nulle entre en jeu.
Le retraitant produit une preuve ZK attestant qu’il possède un enregistrement de dépôt dans le contrat Tornado, et que ce dépôt n’a pas encore été retiré. Grâce à cette preuve, il peut procéder au retrait. La preuve à connaissance nulle garantit la confidentialité : le monde extérieur sait seulement que le retraitant a bien déposé des fonds dans le pool, mais ignore totalement à quel dépôt cela correspond.

Prouver « j’ai déposé des fonds dans Tornado » revient à prouver « mon enregistrement de dépôt peut être trouvé dans le contrat Tornado ». Si on note Cn l’enregistrement de dépôt, le problème se reformule ainsi :
Sachant que l’ensemble des enregistrements de dépôt dans Tornado est {C1, C2, …, C100, …}, Bob doit prouver qu’à l’aide de sa clé privée, il a généré l’un des Cn, sans révéler lequel grâce à une preuve ZK.
C’est ici qu’intervient une propriété particulière de la preuve Merkle. Tous les enregistrements de dépôt de Tornado sont stockés dans une arborescence de Merkle (Merkle Tree) sur la blockchain, chacun formant une feuille de l’arbre. Le nombre total de feuilles est environ 2²⁰ (> 1 million), dont la majorité restent vides (initialisées avec une valeur par défaut). À chaque nouveau dépôt, le contrat inscrit la valeur d’engagement (commitment) correspondante dans une feuille, puis met à jour la racine (root) de l’arbre Merkle.

Par exemple, si le dépôt de Bob est le 10 000ᵉ de l’histoire de Tornado, la valeur d’engagement associée Cn sera inscrite dans la 10 000ᵉ feuille de l’arbre Merkle, soit C₁₀₀₀₀ = Cn. Le contrat calcule ensuite automatiquement la nouvelle racine (root) et la met à jour. (Remarque : pour limiter les coûts de calcul, le contrat Tornado met en cache certaines données intermédiaires modifiées précédemment, telles que Fs1, Fs2 et Fs0 sur l’illustration ci-dessous.)

La preuve Merkle est intrinsèquement concise et légère, tirant parti de l’efficacité structurelle des arbres pour la recherche ou la vérification. Pour prouver qu’une transaction TD existe bien dans l’arbre Merkle, il suffit de fournir la preuve Merkle correspondant à la racine (comme illustré ci-dessous à droite), ce qui reste très court. Même si l’arbre est immense — disons avec 2²⁰ feuilles (soit environ 1 million de dépôts) — la preuve Merkle ne nécessite que 21 valeurs de nœuds, ce qui est extrêmement compact.

Pour prouver qu’une transaction H₃ est bien incluse dans l’arbre Merkle, il faut démontrer que, combinée à d'autres données partielles de l’arbre, elle permet de recréer la racine. L’ensemble de ces données intermédiaires (y compris Td) constitue précisément la preuve Merkle.
Lorsque Bob souhaite retirer des fonds, il doit prouver que son justificatif correspond à une entrée Cn existante dans l’arbre Merkle du contrat Tornado. Autrement dit, il doit prouver deux choses :
• Cn existe bien dans l’arbre Merkle du contrat Tornado, ce qui peut être vérifié via une preuve Merkle incluant Cn ;
• Cn est lié aux informations secrètes (justificatifs) détenues par Bob.

Détail de la logique métier de Tornado
L’interface utilisateur de Tornado Cash intègre déjà plusieurs fonctions. Lorsqu’un utilisateur clique sur le bouton de dépôt, le code frontal exécute localement un programme qui génère deux nombres aléatoires K et r, puis calcule Cn = Hash(K, r), transmettant ensuite Cn (appelé « commitment » sur l’image ci-dessous) au contrat Tornado, où il sera inséré comme nouvelle feuille dans l’arbre Merkle. En somme, K et r agissent comme des clés privées. Elles sont critiques et l’utilisateur est fortement encouragé à les sauvegarder soigneusement, car elles seront nécessaires lors du retrait.

(Le champ « encryptedNote » est facultatif : il permet à l’utilisateur de chiffrer ses justificatifs K et r avec sa clé privée et de les stocker sur la blockchain, afin d’éviter de les perdre.)
Il est crucial de souligner que toutes ces opérations ont lieu hors chaîne (off-chain) : ni le contrat Tornado ni les observateurs externes ne connaissent K et r. Si K et r sont divulgués, cela revient à compromettre la clé privée d’un portefeuille.

Lorsque le contrat Tornado reçoit le dépôt de l’utilisateur ainsi que Cn = Hash(K, r), il insère Cn comme nouvelle feuille à la base de l’arbre Merkle et met à jour la valeur de la racine (root). Ainsi, chaque Cn est strictement associé à une opération de dépôt. Les tiers peuvent savoir quels utilisateurs ont déposé des jetons, reconnaître chaque dépôt par son Cn, et associer chaque Cn à son déposant.
Lors du retrait, l’utilisateur saisit ses justificatifs (les nombres K et r générés au moment du dépôt) dans l’interface web. Le programme intégré à Tornado Cash utilise alors K, r, Cn = Hash(K, r), et la preuve Merkle correspondant à Cn comme entrées pour générer une preuve ZK, démontrant que Cn est bien un enregistrement valide dans l’arbre Merkle, et que K et r sont les justificatifs associés à ce Cn.
Cette étape équivaut à prouver : je connais la clé secrète associée à un dépôt enregistré dans l’arbre Merkle. Lorsque cette preuve ZK est soumise au contrat Tornado, ces quatre paramètres restent cachés : ni le contrat ni les observateurs externes ne peuvent les découvrir, garantissant ainsi la confidentialité.
Les autres paramètres utilisés pour générer la preuve ZK incluent : la racine actuelle de l’arbre Merkle dans le contrat Tornado au moment du retrait, l’adresse de destination personnalisée A, et un identifiant nf destiné à prévenir les attaques par rejeu (que nous expliquerons plus loin). Ces trois derniers paramètres sont publiés publiquement sur la blockchain, accessibles à tous, mais leur divulgation n’affecte pas la confidentialité.

Un détail mérite attention : pourquoi utiliser deux nombres aléatoires K et r pour générer Cn, plutôt qu’un seul ? Car un seul nombre aléatoire serait moins sécurisé, avec un risque non négligeable de collision : deux déposants différents pourraient par hasard choisir le même nombre aléatoire, produisant ainsi le même Cn.
Concernant l’élément A sur l’image, il désigne l’adresse de réception des fonds, renseignée librement par le retraitant. Quant à nf, il s’agit d’un identifiant anti-rejeu, calculé comme nf = Hash(K), où K est l’un des deux nombres aléatoires utilisés lors de la création de Cn (avec r). Ainsi, nf est lié à Cn : chaque Cn a un nf unique qui lui correspond.
Pourquoi prévenir les attaques par rejeu ? En raison de la conception même du mixeur, lors d’un retrait, on ignore à quelle feuille Cn du dépôt correspond le retrait, donc on ne sait pas non plus quel déposant est concerné, ni combien de fois il a déposé. Un malveillant pourrait exploiter cette ambiguïté pour retirer plusieurs fois, lançant une attaque par rejeu et vidant progressivement le pool de liquidités.

Ici, l’identifiant nf joue un rôle similaire au nonce (compteur de transactions) présent dans chaque adresse Ethereum, dont la fonction est précisément d’éviter la reproduction d’une transaction. Lors d’un retrait, le demandeur doit soumettre un nf, que le système vérifie s’il a déjà été utilisé. S’il l’a été, le retrait est invalide. Sinon, le retrait est accepté et le nf est enregistré comme utilisé. Toute tentative ultérieure d’utiliser ce même nf sera rejetée.

Peut-on fabriquer arbitrairement un nf non enregistré par le contrat ? Non, car lors de la génération de la preuve ZK, il faut garantir que nf = Hash(K), or K est lié à un dépôt Cn valide. Ainsi, nf est indirectement lié à un dépôt existant. Si l’on invente un nf quelconque, il ne correspondra à aucun Cn enregistré, empêchant la génération d’une preuve ZK valide, et rendant le retrait impossible.
Certains pourraient se demander : peut-on se passer de nf ? Puisque chaque retrait nécessite une preuve ZK liée à un Cn, pourquoi ne pas simplement vérifier si cette preuve ZK a déjà été soumise auparavant ?
En pratique, cela serait trop coûteux. Le contrat Tornado Cash ne conserve pas indéfiniment les preuves ZK soumises, car cela consommerait trop d’espace de stockage. Comparer chaque nouvelle preuve ZK à toutes les preuves passées serait bien plus inefficace que de stocker durablement un petit identifiant nf, occupant très peu de place.
Voici à quoi ressemble typiquement la fonction de retrait, avec ses paramètres et sa logique métier :
L’utilisateur soumet une preuve ZK, nf (NullifierHash) = Hash(K), et spécifie une adresse de réception personnalisée (recipient). La preuve ZK masque les valeurs de Cn, K et r, empêchant toute identification. L’adresse recipient est généralement une nouvelle adresse « propre », ne révélant aucune information personnelle.

Toutefois, un petit problème subsiste : pour garantir l’anonymat, les utilisateurs lancent souvent la transaction de retrait depuis une nouvelle adresse, qui ne possède alors aucun ETH pour payer les frais de gaz (gas). Ainsi, lors du retrait, l’adresse doit explicitement désigner un relais (relayer) chargé de payer les frais de gaz. Le contrat Tornado déduira ensuite une partie du montant retiré pour la transférer au relayer, en guise de rémunération.

En résumé, TornadoCash parvient à dissimuler tout lien entre retraitants et déposants. Lorsque le nombre d’utilisateurs est élevé, cela revient à perdre un individu dans une foule dense : comme un criminel se fondant dans la masse, il devient quasi impossible à traquer. Le processus de retrait repose sur ZK-SNARK, et la partie « witness » (témoin) cachée contient les informations sensibles de l’utilisateur — c’est là le point le plus critique du mixeur. À ce jour, Tornado reste l’un des projets applicatifs les plus ingénieux liés à la technologie ZK.
Bienvenue dans la communauté officielle TechFlow
Groupe Telegram :https://t.me/TechFlowDaily
Compte Twitter officiel :https://x.com/TechFlowPost
Compte Twitter anglais :https://x.com/BlockFlow_News














