NOUVEAU : Helius acquiert Light Protocol
Solana Builders — ZK Compression
Blog/Développement

Solana Builders : ZK Compression

Ingénieur logicieldubbelosix sur X
17 min de lecture

Dans les jours qui ont suivi l’annonce par Helius et Light Protocol de leur projet Zero-Knowledge (ZK) Compression sur Solana, ZK Compression a suscité de nombreuses discussions. Une grande partie des échanges s’est concentrée sur la nomenclature. S’agit-il d’un rollup ZK ? D’une L2 ? Ou de tout autre chose ?

Pourquoi est-ce important ? Certaines personnes au sein de l’écosystème Solana estiment que les débats sur la nomenclature sont inutiles. Je conviens en partie que le nom utilisé importe moins que le fonctionnement — mais il reste important, car ces noms désignent des constructions dotées de propriétés précises et permettent de les regrouper. Ainsi, qualifier une technologie de XYZ peut nous renseigner sur ses propriétés et ses hypothèses de confiance — et nous devons nous en préoccuper !

Propriétés

Avant de les évaluer dans le contexte de ZK-Compression, énumérons certaines propriétés de Solana qui nous intéressent :

  1. Composabilité atomique synchrone
  2. Concurrence
  3. Sûreté
  4. Vivacité
  5. Résistance à la censure

Hypothèses de confiance initiales

Nous emploierons le terme « sans confiance » pour désigner les hypothèses de sécurité d’un nœud complet. Cette définition constitue notre référence. Tout ce qu’un nœud complet ne peut pas faire seul implique des hypothèses de confiance supplémentaires.

Quelques éléments de contexte

Je vais tenter de présenter brièvement, sous forme de liste, les notions essentielles sur Solana nécessaires pour comprendre ZK-Compression. 

  • L’état de Solana est stocké sur les disques des nœuds complets dans l’« AccountsDB »
  • L’unité de stockage est appelée un « compte »
  • Les comptes possèdent des adresses (32 octets chacune)
  • La quantité de données qu’un compte peut stocker varie de 0 à 10 Mo (maximum)
  • Stocker 10 Mo sur Solana coûte environ 70 SOL, payés par le créateur du compte. Ce coût dépend du stockage et non du nombre de comptes — il peut s’agir de 1 compte de 10 Mo ou de 1 000 comptes de 10 Ko.
  • La taille actuelle de l’ensemble des comptes sur Solana est de 76 Go (compressés)
  • Chaque transaction Solana doit préciser tous les comptes qu’elle lit et modifie
  • Les transactions Solana sont actuellement limitées à 1 232 octets (une proposition vise à augmenter cette limite)
  • Chaque transaction Solana doit préciser certains éléments
    • Signatures (64 octets chacune)
    • Comptes (32 octets chacun)
    • Données d’instruction (longueur arbitraire)
    • Hash de bloc récent (32 octets)
    • Adresses de programmes (32 octets chacune) (CPI : appels interprogrammes)
  • Les transactions Solana comprennent un « hash de bloc récent » de 32 octets qui doit être inclus dans les 150 blocs les plus récents, faute de quoi elles sont considérées comme invalides et doivent être de nouveau signées et soumises

Cycle de vie d’une transaction normale

Lorsqu’une transaction est exécutée normalement, son cycle de vie est le suivant :

  1. La transaction fait d’abord l’objet d’une vérification de son ancienneté (seules les transactions récentes sont valides), d’une déduplication, d’une vérification structurelle, ainsi que de contrôles des frais (gas) et des signatures
  2. Le bytecode du programme est chargé à partir de l’adresse du programme, puis la Solana Virtual Machine (SVM) est instanciée
  3. Tous les comptes référencés par la transaction sont vérifiés, chargés du stockage vers la mémoire, puis transmis à la SVM
  4. Le bytecode du programme est exécuté
  5. Tous les comptes modifiés sont resynchronisés avec le stockage dans leur état mis à jour

Les principales motivations de ZK-Compression sont les suivantes :

  • L’état on-chain coûte cher. Par exemple, mille comptes coûtent 70 SOL, si bien que des produits comme Drip Haus deviennent rapidement coûteux
  • Même sans merkélisation complète de l’état, davantage de comptes stockés sur disque impliquent des snapshots, des index, etc. plus volumineux
  • Tous les comptes ne sont pas consultés fréquemment ; il est donc inutile de supporter en permanence le coût des ressources associées

Quelle est donc la manière la plus simple de réaliser cette compression ?

Au lieu de stocker les comptes sur disque et de les lire lorsque cela est nécessaire (étape 3 du cycle de vie de l’exécution d’une transaction), une transaction peut transmettre les données du compte dans son payload, évitant ainsi les coûts liés au stockage on-chain. Mais cela soulève un nouveau problème : comment garantir que les utilisateurs ne mentent pas sur l’état ?

Supposons, par exemple, que la valeur off-chain d’un compte stockant un solde de tokens soit de 1 200 et que son champ de propriétaire contienne « BYixJwV32DjeuyRww72PwZMyKcaedN533GBrv7CDh4n9 ».

Si vous soumettez une transaction contenant ces données à la blockchain, comment celle-ci pourrait-elle savoir que vous n’avez pas menti sur le nombre de tokens détenus par l’adresse « BYixJwV32DjeuyRww72PwZMyKcaedN533GBrv7CDh4n9 » ?

Après tout, le nœud complet qui traite la transaction n’a pas accès aux données off-chain — il attend de vous que vous fournissiez ces données avec la transaction.

Vous pouvez utiliser des preuves de Merkle à cette fin. Sans entrer dans les détails, considérez-les comme un moyen de « s’engager » sur certaines données de manière vérifiable, avec une faible empreinte de stockage on-chain. Tous les nœuds complets synchronisés avec la blockchain stockent ce petit « engagement ». Lorsqu’une personne fournit les données dans une transaction, elle peut également inclure dans cette même transaction une « preuve » vérifiable par rapport à l’engagement. Cette preuve est sécurisée par cryptographie.

Cela pose-t-il des problèmes ?

Le problème est que les preuves de Merkle peuvent être volumineuses. Si un arbre contient 100 000 comptes, la taille de la preuve pour l’un de ces comptes est de 17 * 32 = 544 octets. Si vous souhaitez fournir des preuves pour plusieurs comptes, dans le pire des cas, la taille est multipliée par celle de la preuve. Ainsi, dix comptes nécessiteraient 10 * 544 = 5 440 octets dans le pire des cas. Ce problème d’espace est propre à Solana, car une transaction Solana est actuellement limitée à 1 232 octets, alors que les autres blockchains ont tendance à être moins restrictives. Reportez-vous à la section ci-dessus, où nous indiquons la taille de chaque composant — programmes, signatures, hash de bloc récent, etc. Même dans le meilleur des cas, la preuve de Merkle occupe donc à elle seule la moitié de la taille de la transaction. 

Plusieurs questions se posent ici.

Si la taille d’une transaction est de 1 232 octets, comment envoyez-vous toutes les données dans la transaction ?

Excellente question. ZK Compression est utile pour un très grand nombre de comptes contenant de petites quantités de données. Les soldes de tokens (8 octets par token), de petites quantités de métadonnées pour les NFT, etc. — 100 octets de données tiennent facilement dans une transaction. En revanche, 1 000 octets sont difficiles à intégrer, compte tenu des autres éléments qui doivent y figurer. Si vos comptes doivent stocker de plus grandes quantités de données, cette méthode — et ZK Compression — ne fonctionnera pas.

Cette affirmation doit toutefois être nuancée : la logique employée par ZK Compression peut être appliquée à certaines parties de l’état d’un compte. Ainsi, même s’il est impossible d’intégrer toutes les données d’un compte au payload d’une seule transaction, il existe des solutions de contournement — notamment créer un engagement et fournir une preuve pour des données partielles.

Les preuves de Merkle sont-elles le seul moyen d’y parvenir ?

Non. Une preuve de Merkle n’est qu’un type d’engagement vectoriel. La taille de l’engagement est de 32 octets et celle de la preuve de Log2(N) * 32 octets, où N correspond à la taille du vecteur sur lequel porte l’engagement. C’est ainsi que nous avons obtenu 17, puisque Log2(100000) vaut 17. Il existe toutefois des engagements dont la taille de preuve est constante (KZG, Pedersen) ; ZK-Compression utilise justement l’une de ces méthodes ! 

Vous indiquez que les données du compte, normalement stockées sur un nœud complet dans le cadre de l’état, doivent être fournies avec la transaction. Où ces données sont-elles stockées ?

Très bonne question, qui affecte nos hypothèses de confiance et nos propriétés. La réponse est : n’importe où ! Des serveurs RPC spécialisés peuvent stocker ces données ; elles peuvent faire partie de Filecoin ou d’IPFS, et l’utilisateur peut même les stocker sur sa propre machine. L’important est que, tant que tous les éléments du vecteur sont stockés quelque part, les preuves peuvent être calculées à la volée. Nous aborderons les implications du lieu de stockage dans la section consacrée aux propriétés. 

D’autres blockchains peuvent-elles faire de même ?

ZK-Compression exige spécifiquement que la vérification zk-SNARK soit peu coûteuse. Cela fonctionne donc bien sur Solana, où le calcul coûte moins cher que le stockage. Le concept général qui consiste à utiliser des engagements vectoriels et des preuves pour les données fournies à chaque transaction peut s’appliquer à d’autres blockchains. Mais comme le calcul y est coûteux, le compromis est moins avantageux que sur Solana. En réalité, le coût récurrent en gas de la vérification, comparé à une opération SSTORE ponctuelle, s’avère plus élevé sur les blockchains basées sur l’EVM.

Qu’est-ce que ZK Compression ?

  • Si vous ne savez pas ce qu’est ZK Compression, cette section vous l’explique
  • Si vous n’en avez qu’une vague idée, je vous conseille tout de même de lire cette section, car certaines idées reçues sont courantes
  • Si vous savez exactement de quoi il s’agit, je vous invite à la lire afin de pouvoir me corriger si je me suis trompé :) 

Si vous avez compris la section précédente, vous avez compris 90 % de ce qu’est ZK Compression. Le principal problème concernait la taille des preuves de Merkle. ZK Compression consiste donc simplement à utiliser un système permettant de prouver un calcul.

Si vous ne savez pas ce qu’est la ZK, ce n’est pas très important. Il vous suffit de savoir qu’elle permet de prouver que vous avez effectué un calcul « correctement ». Prenons un exemple simple : vous souhaitez prouver que vous avez multiplié deux nombres pour en obtenir un troisième, soit 4*3 = 12

La « méthode ZK » permettant de le prouver consiste à utiliser la fonction suivante :

f(x,y) = x*y

Si vous générez un circuit pour le code ci-dessus, le prouveur produit une preuve attestant que le calcul est correct. Le circuit lui-même constitue l’engagement ; tout le monde connaît donc le « calcul » que vous exécutez. Mais ce qui est remarquable, c’est que personne n’a besoin d’en connaître les entrées. Lorsque vous exécutez f(3,4), la fonction renvoie 12 et la « preuve ». N’importe qui peut alors prendre 12, « preuve », et vérifier que vous avez multiplié DEUX nombres pour obtenir 12. Personne ne sait si vous avez utilisé 4,3, 6,2, ou même 12,1. Le fait que vous puissiez masquer ces nombres tout en permettant à quelqu’un de vérifier le calcul correspond à la partie « zero knowledge ».

Pourquoi est-ce que je vous explique cela ?

Ce concept général est extrêmement puissant pour prouver que vous avez effectué un calcul donné et obtenu un résultat précis. Dès qu’une personne dispose du résultat et de la preuve, elle peut vérifier si vous avez effectué le calcul correctement sans réellement l’exécuter. Et cela vaut pour N’IMPORTE QUEL calcul arbitraire. Je me suis contenté de multiplier deux nombres, mais vous pouvez aussi l’utiliser pour affirmer : « J’ai vérifié ces dix signatures et elles sont toutes valides ». C’est le deuxième avantage, et l’une des principales raisons pour lesquelles les preuves à divulgation nulle de connaissance sont utilisées même lorsqu’il n’est pas nécessaire de « masquer » quelque chose. Vous transformez en effet un problème nécessitant 1 000 étapes de calcul — voire un million — en un problème qui exige seulement de vérifier une preuve pour savoir si le calcul a été correctement effectué. Seul bémol : la génération de la preuve prend du temps.

ZK Compression utilise la même technologie pour exécuter la logique réelle d’appartenance à l’arbre de Merkle. Son circuit peut donc recevoir les données du compte et une preuve (128 octets), puis vérifier que ces données font effectivement partie de l’« engagement » sur la blockchain. (La preuve réelle fait 256 octets, mais les courbes elliptiques et les points présentent un avantage pratique : si vous connaissez la courbe, un seul point suffit pour obtenir le second.)

L’objectif principal est de réduire la taille de la preuve à une valeur constante de 128 octets, ce qui laisse encore beaucoup de place — relativement parlant — aux données des petits comptes. Alors que la taille d’une preuve de Merkle normale est de Log2(N), celle de ZK Compression reste constante, ce qui permet de regrouper un très grand nombre de comptes sous un même engagement. (À titre de référence, une preuve de Merkle portant sur 100 000 comptes ferait environ 550 octets, soit la moitié du payload de la transaction.) 

Cette preuve peut être générée off-chain, mais elle doit être vérifiée on-chain, car un programme doit savoir que vous avez fourni les bonnes données pour un compte avant d’autoriser la poursuite de l’exécution. Le mécanisme de base permettant de vérifier les preuves ZK doit donc être présent. Le système de preuve utilisé par ZK-Compression s’appelle Groth16 et repose lui-même sur le syscall alt_bn128, qui est actuellement protégé par un feature gate sur le mainnet et en cours de test.

Ce qui est intéressant, c’est que le mécanisme utilisé par ZK-Compression permet de vérifier des calculs arbitraires, et pas seulement de répondre à la question « Cette feuille appartient-elle à un arbre possédant cette racine ? ».

L’un des principaux avantages de ZK Compression est de fournir toute l’infrastructure nécessaire pour éviter aux développeurs d’avoir à gérer la partie « ZK ». Du point de vue d’un développeur, il s’agit simplement d’un compte comme un autre, avec les mêmes champs, etc. Le programme peut donc le traiter comme un compte ordinaire. Il est utile d’abstraire l’essentiel de la « magie ZK » afin que les développeurs n’aient pas à s’en occuper.

Rollups ZK

Sans entrer dans trop de détails, les rollups ZK utilisent globalement les mêmes concepts que ZK-Compression. Leur principale similitude tient au fait que l’intégralité de l’état du rollup est représentée par une racine unique sur la couche de base (Ethereum), d’où certaines affirmations selon lesquelles ZK-Compression serait un rollup. Il existe toutefois des différences cruciales.

Prenons 100 transactions de rollup.

L’intégralité du rollup ZK est traitée comme un circuit, à l’image du programme de multiplication utilisé dans notre exemple. Les 100 transactions sont toutes vérifiées — signature, logique du contrat, contrôle de déduplication, etc. — puis une seule preuve est générée pour

« Après l’application de 100 transactions, la racine de l’état passe de A à B ». Une fois la preuve vérifiée, le smart contract remplace la racine de l’état A par B.

Dans ZK-Compression, en revanche, chacune des 100 transactions contient une preuve qui indique simplement que les données du compte sont correctes. Les transitions d’état — générées par les transactions — sont toutefois réellement exécutées on-chain par la SVM elle-même. Une fois la preuve validée, le compte est traité comme un compte ordinaire. Ce point est essentiel pour la propriété de composabilité que nous allons maintenant aborder.

Nouvel examen des propriétés

Nous arrivons maintenant à la partie intéressante. Quelles propriétés de Solana ZK-Compression conserve-t-elle ?

Composabilité atomique synchrone

Si une transaction référence 2 comptes compressés avec ZK et 10 comptes « normaux », la composabilité reste intacte. Une instruction référençant un compte compressé avec ZK peut appeler une autre instruction ou un autre programme référençant un compte « normal » non compressé. Cette fonctionnalité est entièrement préservée même si deux comptes sont compressés dans des arbres différents. Si une instruction échoue, l’intégralité de la transaction est annulée (atomicité), et les modifications apportées par une instruction appelée à la ligne 1 sont visibles à la ligne 2 (synchronisme).

Ce n’est pas le cas des rollups, car les rollups ZK ne peuvent pas s’appeler mutuellement de manière synchrone ou atomique — à moins de prendre des verrous globaux et d’autoriser les annulations entre rollups.

Parallélisme

Cette fonctionnalité a certaines répercussions sur le parallélisme, et chaque cas mérite d’être examiné :

Écritures dans plusieurs comptes compressés au sein du même arbre

Chaque arbre est concurrent de manière autonome. Cela signifie que si des utilisateurs lisent ou modifient deux comptes compressés sous la même racine d’état, les opérations peuvent s’exécuter simultanément et la racine d’état peut être mise à jour de façon concurrente. La logique serait ici identique à celle utilisée par Solana pour les mises à jour concurrentes des arbres de Merkle dans les cNFT

Écritures dans le même compte compressé

Chaque compte compressé n’est pas concurrent. Si deux utilisateurs tentent d’écrire dans le même compte compressé, l’une des transactions échouera, quel que soit leur ordre. Lors d’une exécution normale, les écritures effectuées dans un compte par l’instruction précédente sont disponibles pour l’instruction suivante. Avec les comptes compressés avec ZK, en revanche, la preuve des données du compte serait invalide, puisqu’elle porte sur l’état précédent

Il faut également noter que l’importante consommation d’unités de calcul (CU) de la compression réduit la concurrence maximale par arbre, car chaque compte ne peut consommer que 12 millions d’unités de calcul par bloc, compte tenu de la limite de CU par compte.

Hypothèses de confiance

N’importe qui peut stocker toutes les données brutes nécessaires pour générer les preuves et soumettre des transactions. Cela constitue néanmoins une hypothèse de confiance supplémentaire qui affecte la vivacité de l’état compressé. Si, pour une raison quelconque, les données sont « perdues » ou retardées, vous ne pourrez soumettre aucune transaction à moins d’avoir vous-même stocké ces données. Heureusement, il s’agit de ce que l’on appelle un problème f+1, et non d’un problème 3f+1 nécessitant une tolérance aux pannes byzantines. Un problème f+1 nécessite seulement qu’un nœud honnête fournisse les données. Comme les preuves sont « auto-vérifiables », cela ne pose aucun problème de « sûreté ». Il s’agit principalement d’un problème de « vivacité » et d’un vecteur de censure.

Les rollups classiques comme ZK-Compression nécessitent de fournir une preuve de validité. Toutefois, alors que les rollups encodent l’intégralité de la fonction de transition d’état dans cette preuve, ZK-Compression encode uniquement la question « Les données du compte sont-elles correctes ? ». Les hypothèses de confiance diffèrent donc légèrement. Dans le cas de la compression, elles concernent principalement l’accès à l’état — tandis que la transition d’état est exécutée intégralement. Dans le cas d’un rollup, elles portent sur l’ensemble de la fonction de transition d’état — du point de vue de la couche de base. Les hypothèses de sécurité relatives au nombre de bits ou à la difficulté et à l’intractabilité du problème sous-jacent sont les mêmes — hypothèse de Diffie-Hellman bilinéaire —, mais c’est *ce pour quoi* vous vous fiez à ce modèle de sécurité qui diffère : l’accès à l’état plutôt que l’exécution. Je ne le mentionne que parce qu’il est important de savoir où des hypothèses de confiance supplémentaires sont introduites et où elles ne le sont pas.

À l’heure actuelle, le programme qui vérifie les comptes compressés avec ZK peut être mis à niveau. Il pourra toutefois être rendu immuable ou figé à l’avenir, puisqu’il n’effectue qu’une opération très spécifique — l’ouverture de preuves de Merkle — qui ne nécessite pas réellement de mises à niveau régulières.

La compression de l’état peut également être réalisée de deux autres manières.

  1. Une proposition visant à augmenter la taille des transactions (canal proj-3x-tx du Discord de Solana) est en cours de développement. Une fois déployée, elle permettra d’utiliser des preuves de Merkle classiques si leur taille est compatible
  2. Une fois le syscall alt_bn128 déployé, il pourra également servir à un engagement vectoriel classique avec une taille de preuve constante (KZG fonctionne avec toutes les courbes compatibles avec les appariements, y compris alt_bn128). Cela ne nécessite aucun circuit de prouveur ZK

Comment devons-nous appeler cette technologie ?

Malheureusement, des termes comme rollup, L2 et Validium ont été employés de façon si vague que certains rollups n’en sont même pas. Ils n’héritent ni de la vivacité, ni de la sûreté, ni de la résistance à la censure de la couche de base. Helius a été accusée d’utiliser un « terme marketing », mais les mêmes personnes ont employé « rollup » de manière très vague pour désigner des projets dans lesquels elles avaient investi, et ce pour la même raison : le marketing. En réalité, des systèmes qui ne sont pas des rollups sont classés selon différentes étapes dans le seul but de leur permettre de continuer à se présenter comme tels.

Tout le monde n’est pas coupable de cette pratique : certaines personnes ont fait preuve d’une franchise sans détour quant à l’emploi d’une terminologie précise et ont passé des mois-personnes à débattre avec ceux qui utilisent des termes imprécis pour induire les utilisateurs en erreur (mention spéciale à Toghrul, qui réclame systématiquement une terminologie précise de la part de *tout le monde*).

Comme ZK-Compression possède des propriétés et des hypothèses de confiance différentes de celles d’un rollup, la qualifier de rollup pourrait semer la confusion chez les utilisateurs. Le terme Validium est également trop large : il ignore que la composabilité atomique synchrone et le parallélisme restent intacts, que la disponibilité des données (DA) est on-chain et, surtout, que la fonction de transition d’état elle-même fonctionne sans tiers de confiance — puisque les nœuds complets exécutent réellement les programmes dans leur intégralité au lieu de simplement vérifier une preuve de validité portant sur l’exécution. Certaines personnes pourraient affirmer que les preuves ZK fonctionnent sans tiers de confiance, mais ce n’est tout simplement pas vrai. Même si elles réduisent considérablement la confiance nécessaire, leurs hypothèses de sécurité ne sont mathématiquement pas les mêmes. Elles peuvent suffire pour 99 % des cas d’usage, mais elles ajoutent une hypothèse de confiance par rapport à un nœud complet — par exemple, l’hypothèse de Diffie-Hellman bilinéaire dans le cas d’un zk-SNARK fondé sur une courbe d’appariement. Bien entendu, puisque ZK-Compression utilise le snark pour vérifier la validité du compte lui-même, il est juste de dire que ZK-Compression ne fonctionne pas sans confiance : les comptes compressés avec ZK reposent sur des hypothèses de confiance qui n’existent pas pour les comptes « normaux ». Cette technologie se situe donc quelque part entre un système sans confiance et un rollup ZK complet.

Si les noms servent à déduire les propriétés, j’affirmerais que le terme « rollup » ne traduit pas la présence de ces propriétés ou hypothèses de confiance. Faudrait-il inventer un nouveau nom ? ZK-Compression convient parfaitement, tant que les hypothèses de confiance sont clairement établies.

Abonnez-vous à Helius

Suivez les dernières actualités du développement sur Solana et recevez une notification à chaque publication