Merci tout particulièrement à Yoav Weiss, Dan Finlay, Martin Koppelmann et aux équipes d'Arbitrum, Optimism, Polygon, Scroll et SoulWallet pour leurs commentaires.
Dans ce billet sur les trois transitions, j'ai expliqué certaines des principales raisons pour lesquelles il est utile de commencer à penser explicitement à la prise en charge L1 + Cross-L2, à la sécurité des portefeuilles et à la confidentialité en tant que fonctionnalités de base nécessaires de l'écosystème, plutôt que de créer chacun de ces éléments sous forme d'addons pouvant être conçus séparément pour chaque portefeuille.
Ce billet se concentrera plus directement sur les aspects techniques d'un sous-problème spécifique : comment faciliter la lecture de la L1 de la L2, de la L2 de la L1 ou d'une L2 d'une autre L2. La résolution de ce problème est cruciale pour mettre en œuvre une architecture de séparation des actifs et des clés, mais elle présente également de précieux cas d'utilisation dans d'autres domaines, notamment l'optimisation de la fiabilité des appels inter-L2, y compris des cas d'utilisation tels que le transfert d'actifs entre les niveaux 1 et 2.
Une fois que les L2 seront plus courantes, les utilisateurs disposeront d'actifs répartis sur plusieurs L2, et peut-être aussi sur la L1. Une fois que les portefeuilles à contrats intelligents (multisig, social recovery ou autre) seront courants, les clés nécessaires pour accéder à certains comptes changeront au fil du temps, et les anciennes clés devront ne plus être valides. Une fois que ces deux choses se seront produites, l'utilisateur devra trouver un moyen de modifier les clés qui lui permettent d'accéder à de nombreux comptes situés dans de nombreux endroits différents, sans effectuer un très grand nombre de transactions.
En particulier, nous avons besoin d'un moyen de gérer les adresses contrefactuelles : des adresses qui n'ont pas encore été « enregistrées » sur la chaîne, mais qui doivent néanmoins recevoir et détenir des fonds en toute sécurité. Nous dépendons tous d'adresses contrefactuelles : lorsque vous utilisez Ethereum pour la première fois, vous pouvez générer une adresse ETH que quelqu'un peut utiliser pour vous payer, sans « enregistrer » l'adresse en chaîne (ce qui impliquerait de payer des taxes, et donc de détenir déjà des ETH).
Avec les EOA, toutes les adresses commencent par être des adresses contrefactuelles. Avec les portefeuilles à contrats intelligents, les adresses contrefactuelles sont toujours possibles, en grande partie grâce à CREATE2, qui vous permet d'avoir une adresse ETH qui ne peut être renseignée que par un contrat intelligent dont le code correspond à un hachage particulier.
Algorithme de calcul d'adresse EIP-1014 (CREATE2).
Cependant, les portefeuilles à contrats intelligents présentent un nouveau défi : la possibilité de changer les clés d'accès. L'adresse, qui est un hash du code d'initialisation, ne peut contenir que la clé de vérification initiale du portefeuille. La clé de vérification actuelle serait stockée dans la mémoire du portefeuille, mais cet enregistrement ne se propage pas comme par magie aux autres L2.
Si un utilisateur possède de nombreuses adresses sur de nombreuses L2, y compris des adresses que le L2 auquel il se trouve (parce que c'est contrefactuel) ne connaît pas, alors il semble qu'il n'y a qu'un seul moyen de permettre aux utilisateurs de modifier leurs clés : l'architecture de séparation des actifs et des magasins de clés. Chaque utilisateur possède (i) un « contrat de stockage de clés » (sur la L1 ou sur une L2 en particulier), qui stocke la clé de vérification de tous les portefeuilles ainsi que les règles relatives au changement de clé, et (ii) des « contrats de portefeuille » sur la L1 et sur de nombreux L2, qui lisent les chaînes pour obtenir la clé de vérification.
Il existe deux manières de le mettre en œuvre :
Pour en montrer toute la complexité, nous allons explorer le cas le plus difficile : celui où le keystore se trouve sur une L2 et le portefeuille sur une autre L2. Si le keystore ou le portefeuille se trouvent sur L1, seule la moitié de ce design est nécessaire.
Supposons que le keystore se trouve sur Linea et que le portefeuille se trouve sur Kakarot. Une preuve complète des clés du portefeuille comprend :
Il y a deux principales questions de mise en œuvre ici :
Il existe cinq options principales :
En termes de travaux d'infrastructure requis et de coûts pour les utilisateurs, je les classe à peu près comme suit :
Le terme « agrégation » fait référence à l'idée de regrouper toutes les preuves fournies par les utilisateurs au sein de chaque bloc dans une grande méta-preuve qui les combine toutes. C'est possible pour SNArks et pour KZG, mais pas pour les agences Merkle (vous pouvez légèrement combiner les branches Merkle, mais cela vous permet d'économiser du log (txs par bloc) /log (nombre total de keystores), peut-être 15 à 30 % en pratique, donc ça ne vaut probablement pas le coût).
L'agrégation ne vaut la peine que lorsque le système compte un nombre important d'utilisateurs. En réalité, il est normal pour une implémentation en version 1 de laisser de côté l'agrégation et de l'implémenter pour la version 2.
Celui-ci est simple : suivez directement le schéma de la section précédente. Plus précisément, chaque « preuve » (en supposant que le cas de difficulté maximale consiste à prouver une L2 contre une autre) contiendrait :
Malheureusement, les preuves d'état d'Ethereum sont compliquées, mais il existe des bibliothèques pour les vérifier, et si vous utilisez ces bibliothèques, ce mécanisme n'est pas trop compliqué à mettre en œuvre.
Le plus gros problème, c'est le coût. Les preuves de Merkle sont longues, et les arbres Patricia le sont malheureusement environ 3,9 fois plus que nécessaire (précisément : une preuve de Merkle idéale pour un arbre contenant N objets fait 32 log2 (N) octets, et comme les arbres Patricia d'Ethereum ont 16 feuilles par enfant, les preuves de ces arbres font 32 15 log16 (N) ~= 125 log2 (N) octets). Dans un État comptant environ 250 millions (~2 m²) de comptes, chaque preuve fait 125 * 28 = 3 500 octets, soit environ 56 000 de gaz, plus les frais supplémentaires liés au décodage et à la vérification des hachages.
Deux preuves réunies coûteraient entre 100 000 et 150 000 gaz (sans compter la vérification de la signature si elle est utilisée par transaction), soit bien plus que les 21 000 gaz de base actuels par transaction. Mais la disparité ne fait qu'empirer si les preuves sont vérifiées en L2. Les calculs au sein d'une L2 ne coûtent pas cher, car ils se font hors chaîne et dans un écosystème comportant beaucoup moins de nœuds que la L1. Les données, quant à elles, doivent être publiées en L1. La comparaison n'est donc pas de 21 000 gaz contre 150 000 gaz ; il s'agit de 21 000 L2 contre 100 000 gaz L1.
Nous pouvons calculer ce que cela signifie en comparant les coûts du gaz L1 et les coûts du gaz L2 :
La L1 coûte actuellement 15 à 25 fois plus chère que la L2 pour les envois simples, et 20 à 50 fois plus chère pour les échanges de jetons. Les envois simples sont relativement gourmands en données, mais les swaps sont beaucoup plus gourmands en termes de calcul. Les swaps constituent donc une meilleure référence pour déterminer le coût approximatif du calcul L1 par rapport au calcul L2. Compte tenu de tout cela, si nous supposons un ratio de coûts multiplié par 30 entre les coûts de calcul L1 et les coûts de calcul L2, cela signifie que le fait d'ajouter une preuve Merkle à L2 coûtera l'équivalent de cinquante transactions régulières.
Bien sûr, l'utilisation d'un arbre de Merkle binaire peut réduire les coûts d'environ 4 fois, mais malgré tout, le coût sera trop élevé dans la plupart des cas. Et si nous sommes prêtes à faire le sacrifice de ne plus être compatibles avec l'arbre d'états hexaires actuel d'Ethereum, autant rechercher des options encore meilleures.
D'un point de vue conceptuel, l'utilisation des zk-SNARK est également facile à comprendre : il vous suffit de remplacer les preuves de Merkle dans le schéma ci-dessus par un ZK-SNARK prouvant que ces preuves de Merkle existent. Un ZK-SNARK coûte environ 400 000 gaz de calcul, et environ 400 octets (à comparer : 21 000 gaz et 100 octets pour une transaction de base, à l'avenir, réductible à environ 25 octets avec compression). Du point de vue informatique, un ZK-SNARK coûte 19 fois le coût d'une transaction de base aujourd'hui, et du point de vue des données, un ZK-SNARK coûte 4 fois plus cher qu'une transaction de base aujourd'hui, et 16 fois ce qu'une transaction de base pourrait coûter à l'avenir.
Ces chiffres constituent une nette amélioration par rapport aux preuves de Merkle, mais ils restent assez chers. Il existe deux manières d'améliorer cela : (i) des preuves KZG spécialisées, ou (ii) l'agrégation, similaire à l'agrégation ERC-4337 mais en utilisant des mathématiques plus sophistiquées. Nous pouvons examiner les deux.
Attention, cette section est beaucoup plus mathématique que les autres. C'est parce que nous allons au-delà des outils à usage général et que nous créons quelque chose de spécial pour être moins cher. Nous devons donc passer beaucoup plus de temps à « passer sous le capot ». Si vous n'aimez pas les mathématiques approfondies, passez directement à la section suivante.
Tout d'abord, un résumé du fonctionnement des engagements du KZG :
Certaines propriétés clés qu'il est important de comprendre sont les suivantes :
Nous avons donc une structure qui nous permet de continuer à ajouter des valeurs à la fin d'une liste qui ne cesse de s'allonger, mais avec une certaine limite de taille (en réalité, des centaines de millions pourraient être viables). Nous l'utilisons ensuite comme structure de données pour gérer (i) un engagement envers la liste des clés de chaque L2, stockée sur cette L2 et mise en miroir sur la L1, et (ii) un engagement envers la liste des engagements clés L2, stockée sur l'Ethereum L1 et reflétée sur chaque L2.
La mise à jour des engagements pourrait soit faire partie de la logique principale de la L2, soit être mise en œuvre sans modification du protocole principal de la L2 par le biais de passerelles de dépôt et de retrait.
Pour obtenir une preuve complète, il faudrait donc :
Il est en fait possible de fusionner les deux preuves KZG en une seule, donc nous obtenons une taille totale de 100 octets seulement.
Notez une subtilité : comme la liste des touches est une liste, et non une carte clé/valeur comme c'est le cas pour l'État, la liste des touches devra attribuer les positions de manière séquentielle. Le contrat d'engagement des clés contiendrait son propre registre interne associant chaque keystore à un identifiant, et pour chaque clé, il stockerait le hash (clé, adresse du keystore) au lieu d'une simple clé, afin de communiquer sans ambiguïté aux autres L2 de quel keystore parle une entrée en particulier.
L'avantage de cette technique, c'est qu'elle fonctionne très bien en L2. Les données font 100 octets, soit environ 4 fois plus courtes qu'un ZK-SNARK et bien plus courtes qu'une preuve Merkle. Le coût de calcul correspond en grande partie à un chèque d'appariement de taille 2, soit environ 119 000 essence. En L1, les données sont moins importantes que les calculs. Malheureusement, le KZG coûte un peu plus cher que les preuves de Merkle.
Les arbres Verkle consistent essentiellement à empiler les engagements KZG (ou les engagements IPA, qui peuvent être plus efficaces et utiliser une cryptographie plus simple) les uns sur les autres : pour stocker 2 valeurs, vous pouvez prendre un engagement KZG sur une liste de 2 valeurs, chacune d'entre elles constituant en elle-même un engagement KZG à 2 valeurs. Les arbres Verkle sont fortement < a href= " https://notes.ethereum.org/@vbuterin/verkle_tree_eip " > considéré pour l'arbre d'état d'Ethereum, parce que les arbres de Verkle peuvent être utilisés pour contenir des cartes de valeurs clés et pas seulement des listes (en gros, vous pouvez créer un arbre de 2 m² mais le démarrer vide, en ne remplissant que des parties spécifiques de l'arbre une fois que vous en avez réellement besoin).
À quoi ressemble un arbre Verkle. En pratique, vous pouvez donner à chaque nœud une largeur de 256 == 2 pour les arbres basés sur l'IPA, ou de 2 ² pour les arbres basés sur le KzG.
Les preuves dans les arbres Verkle sont légèrement plus longues qu'en KZG ; elles peuvent faire quelques centaines d'octets. Ils sont également difficiles à vérifier, surtout si vous essayez de regrouper de nombreuses preuves en une seule.
En réalité, les arbres Verkle devraient être considérés comme des arbres Merkle, mais ils sont plus viables sans SNARking (en raison de la baisse des coûts de données) et moins chers avec SnarKing (grâce à la baisse des coûts des prouvateurs).
Le principal avantage des arbres de Verkle est la possibilité d'harmoniser les structures de données : les preuves de Verkle peuvent être utilisées directement sur l'état L1 ou L2, sans structures de superposition, et en utilisant exactement le même mécanisme pour L1 et L2. Une fois que les ordinateurs quantiques poseront un problème, ou une fois que les branches de Merkle seront suffisamment efficaces, les arbres de Verkle pourraient être remplacés sur place par un arbre de hachage binaire doté d'une fonction de hachage adaptée à Snark.
Si N utilisateurs effectuent N transactions (ou, de façon plus réaliste, N ERC-4337 UserOperations) qui doivent prouver N réclamations inter-chaînes, nous pouvons économiser beaucoup de gaz en agrégeant ces preuves : le constructeur qui combinerait ces transactions dans un bloc ou un ensemble pouvant être intégré à un bloc peut créer une preuve unique prouvant toutes ces affirmations simultanément.
Cela peut vouloir dire :
Dans les trois cas, les preuves ne coûteraient que quelques centaines de milliers d'essence chacune. Le créateur devrait en créer un sur chaque L2 pour les utilisateurs de cette L2 ; par conséquent, pour que cela soit utile, le schéma dans son ensemble doit être suffisamment utilisé pour qu'il y ait très souvent au moins quelques transactions dans le même bloc sur plusieurs L2 majeurs.
Si des zk-SNARK sont utilisés, le principal coût marginal est simplement la « logique commerciale » qui consiste à transmettre des chiffres entre les contrats, soit peut-être quelques milliers de L2 par utilisateur. Si des appareils KZG sont utilisés, le testeur devra ajouter 48 litres de gaz pour chaque unité de stockage de gaz utilisée dans ce bloc. Ainsi, le coût marginal du système par utilisateur ajouterait environ 800 litres de gaz supplémentaires par litre (et non par utilisateur) en plus. Mais ces coûts sont bien inférieurs à ceux liés à l'absence d'agrégation, qui implique inévitablement plus de 10 000 litres de gaz L1 et des centaines de milliers de gaz L2 par utilisateur. Pour les arbres Verkle, vous pouvez soit utiliser directement les multi-preuves Verkle, en ajoutant environ 100 à 200 octets par utilisateur, soit créer un ZK-SNARK à partir d'un Verkle multiproof, ce qui coûte des coûts similaires à ceux des branches ZK-SNARKS de Merkle mais c'est nettement moins cher à prouver.
Du point de vue de la mise en œuvre, il est probablement préférable de demander à des groupeurs d'agréger les preuves inter-chaînes via la norme d'abstraction de comptes ERC-4337. L'ERC-4337 dispose déjà d'un mécanisme permettant aux créateurs d'agréger certaines parties de UserOperations de manière personnalisée. Il existe même une implémentation < a href= " https://hackmd.io/@voltrevo/BJ0QBY3ZI " > pour l'agrégation de signatures BLS, ce qui pourrait réduire les coûts de gaz sur la couche L2 de 1,5 à 3 fois selon les autres formes de compression incluses.
Schéma tiré d'un article < a href= " https://hackmd.io/@voltrevo/bj0qby3zi " > sur la mise en œuvre du portefeuille BLS montrant le flux de travail des signatures agrégées BLS dans une version antérieure de l'ERC-4337. Le flux de travail d'agrégation des preuves inter-chaînes sera probablement très similaire.
Une dernière possibilité, utilisable uniquement pour la L2 lisant la L1 (et non la L1 lisant L2), est de modifier les L2 pour leur permettre de passer des appels statiques aux contrats de L1 directement.
Cela peut être fait à l'aide d'un opcode ou d'une précompilation, qui autorise les appels en L1 où vous fournissez l'adresse de destination, le gaz et les données d'appel, et qui renvoie le résultat, mais comme ces appels sont statiques, ils ne peuvent pas réellement modifier l'état L1. Les L2 doivent déjà connaître la L1 pour traiter les dépôts. Rien de fondamental n'empêche donc la mise en œuvre d'une telle solution ; il s'agit principalement d'un défi de mise en œuvre technique (voir : cet appel d'offres d'Optimism visant à prendre en charge les appels statiques vers la L1).
Notez que si le keystore est en L1 et que les L2 intègrent la fonctionnalité d'appel statique L1, aucune preuve n'est requise ! Cependant, si les L2 n'intègrent pas les appels statiques L1, ou si le keystore est en L2 (ce qui devra peut-être être le cas, une fois que la L1 deviendra trop chère pour que les utilisateurs puissent l'utiliser ne serait-ce qu'un tout petit peu), alors des preuves seront requises.
Tous les schémas ci-dessus nécessitent que la L2 accède soit à la racine de l'état L1 récent, soit à l'intégralité de l'état L1 récent. Heureusement, toutes les L2 disposent déjà de certaines fonctionnalités qui leur permettent d'accéder à l'état récent de la L1. C'est parce qu'ils ont besoin d'une telle fonctionnalité pour traiter les messages provenant de la L1 vers la L2, notamment les dépôts.
Et en effet, si une L2 possède une fonction de dépôt, vous pouvez utiliser cette L2 telle quelle pour transférer les racines de l'État L1 dans un contrat sur la L2 : il suffit de créer un contrat en L1, d'appeler l'opcode BLOCKHASH, et de le transmettre à L2 sous forme de message de dépôt. L'en-tête du bloc complet peut être reçu, et sa racine d'état extraite, côté L2. Cependant, il serait préférable que chaque L2 dispose d'un moyen explicite d'accéder directement à l'état L1 complet récent ou aux racines récentes de l'État L1.
Le principal défi lié à l'optimisation de la façon dont les L2 reçoivent les racines d'état L1 récentes est de garantir à la fois la sécurité et une faible latence :
De plus, dans la direction opposée (L1 lit L2) :
Certaines de ces vitesses pour les opérations inter-chaînes en toute confiance sont trop lentes pour de nombreux cas d'utilisation de DeFi ; dans ces cas, vous avez besoin de ponts plus rapides avec des modèles de sécurité plus imparfaits. Pour ce qui est de la mise à jour des clés de portefeuille, des délais plus longs sont toutefois plus acceptables : vous ne retardez pas les transactions de plusieurs heures, vous retardez les changements de clés. Vous n'aurez qu'à conserver les anciennes clés plus longtemps. Si vous changez de clé parce que des clés sont volées, c'est que vous êtes exposée à une période de vulnérabilité importante, mais cela peut être atténué, par exemple en installant une fonction de blocage des portefeuilles.
En fin de compte, la meilleure solution pour minimiser la latence est que les L2 implémentent la lecture directe des racines d'état L1 de manière optimale, où chaque bloc L2 (ou le journal de calcul de la racine d'état) contient un pointeur vers le bloc L1 le plus récent. Ainsi, si la L1 revient, la L2 peut également revenir en arrière. Les contrats Keystore doivent être passés soit sur le réseau principal, soit sur des L2 qui sont des ZK-Rollups, ce qui permet de passer rapidement en L1.
Les blocs de la chaîne L2 peuvent dépendre non seulement des blocs L2 précédents, mais aussi d'un bloc L1. Si la L1 revient après un tel lien, la L2 revient également. Il convient de noter que c'est également ainsi qu'une version antérieure (antérieure à Dank) du sharding était envisagée ; voir le code ici.
Étonnamment, pas tant que ça. En fait, il n'est même pas nécessaire que ce soit un cumul : s'il s'agit d'un L3 ou d'un Validium, vous pouvez y conserver des portefeuilles, à condition que vous déteniez des keystores en L1 ou en ZK. Ce dont vous avez besoin, c'est que la chaîne ait un accès direct aux racines de l'État d'Ethereum et qu'elle s'engage techniquement et socialement à être prête à se réorganiser si Ethereum se réorganise, et à hard fork si Ethereum fait des hard forks.
Un problème de recherche intéressant est de déterminer dans quelle mesure il est possible pour une chaîne d'avoir cette forme de connexion avec plusieurs autres chaînes (par ex. Ethereum et Zcash). Le faire naïvement est possible : votre chaîne pourrait accepter de se réorganiser en cas de réorganisation d'Ethereum ou de Zcash (et de hard fork s'il s'agit d'Ethereum ou de Zcash), mais les opérateurs de vos nœuds et votre communauté en général ont deux fois plus de dépendances techniques et politiques. Une telle technique pourrait donc être utilisée pour se connecter à quelques autres chaînes, mais à un coût plus élevé. Les schémas basés sur les ponts ZK ont des propriétés techniques intéressantes, mais leur principal point faible est qu'ils ne résistent pas aux attaques à 51 % ou aux hard forks. Il existe peut-être des solutions plus intelligentes.
Idéalement, nous voulons également préserver la confidentialité. Si vous avez de nombreux portefeuilles gérés par le même keystore, nous voulons nous assurer que :
Cela pose quelques problèmes :
Avec les SNARKs, les solutions sont simples sur le plan conceptuel : les preuves masquent des informations par défaut, et l'agrégateur doit produire un SNARK récursif pour prouver les SNARK.
Le principal défi de cette approche aujourd'hui est que l'agrégateur doit créer un SNARK récursif, ce qui est assez lent actuellement.
Avec KZG, nous pouvons utiliser < a href= " https://notes.ethereum.org/@vbuterin/non_index_revealing_proof " > pour ce travailler sur des preuves KZG non révélatrices d'index (voir aussi : une version plus formalisée de ce travail dans l'article de Caulk) comme point de départ . L'agrégation de preuves en aveugle est toutefois un problème ouvert qui nécessite plus d'attention.
La lecture directe de la L1 depuis la L2 ne préserve malheureusement pas la confidentialité, même si la mise en œuvre d'une fonctionnalité de lecture directe reste très utile, à la fois pour minimiser la latence et pour d'autres applications.
Merci tout particulièrement à Yoav Weiss, Dan Finlay, Martin Koppelmann et aux équipes d'Arbitrum, Optimism, Polygon, Scroll et SoulWallet pour leurs commentaires.
Dans ce billet sur les trois transitions, j'ai expliqué certaines des principales raisons pour lesquelles il est utile de commencer à penser explicitement à la prise en charge L1 + Cross-L2, à la sécurité des portefeuilles et à la confidentialité en tant que fonctionnalités de base nécessaires de l'écosystème, plutôt que de créer chacun de ces éléments sous forme d'addons pouvant être conçus séparément pour chaque portefeuille.
Ce billet se concentrera plus directement sur les aspects techniques d'un sous-problème spécifique : comment faciliter la lecture de la L1 de la L2, de la L2 de la L1 ou d'une L2 d'une autre L2. La résolution de ce problème est cruciale pour mettre en œuvre une architecture de séparation des actifs et des clés, mais elle présente également de précieux cas d'utilisation dans d'autres domaines, notamment l'optimisation de la fiabilité des appels inter-L2, y compris des cas d'utilisation tels que le transfert d'actifs entre les niveaux 1 et 2.
Une fois que les L2 seront plus courantes, les utilisateurs disposeront d'actifs répartis sur plusieurs L2, et peut-être aussi sur la L1. Une fois que les portefeuilles à contrats intelligents (multisig, social recovery ou autre) seront courants, les clés nécessaires pour accéder à certains comptes changeront au fil du temps, et les anciennes clés devront ne plus être valides. Une fois que ces deux choses se seront produites, l'utilisateur devra trouver un moyen de modifier les clés qui lui permettent d'accéder à de nombreux comptes situés dans de nombreux endroits différents, sans effectuer un très grand nombre de transactions.
En particulier, nous avons besoin d'un moyen de gérer les adresses contrefactuelles : des adresses qui n'ont pas encore été « enregistrées » sur la chaîne, mais qui doivent néanmoins recevoir et détenir des fonds en toute sécurité. Nous dépendons tous d'adresses contrefactuelles : lorsque vous utilisez Ethereum pour la première fois, vous pouvez générer une adresse ETH que quelqu'un peut utiliser pour vous payer, sans « enregistrer » l'adresse en chaîne (ce qui impliquerait de payer des taxes, et donc de détenir déjà des ETH).
Avec les EOA, toutes les adresses commencent par être des adresses contrefactuelles. Avec les portefeuilles à contrats intelligents, les adresses contrefactuelles sont toujours possibles, en grande partie grâce à CREATE2, qui vous permet d'avoir une adresse ETH qui ne peut être renseignée que par un contrat intelligent dont le code correspond à un hachage particulier.
Algorithme de calcul d'adresse EIP-1014 (CREATE2).
Cependant, les portefeuilles à contrats intelligents présentent un nouveau défi : la possibilité de changer les clés d'accès. L'adresse, qui est un hash du code d'initialisation, ne peut contenir que la clé de vérification initiale du portefeuille. La clé de vérification actuelle serait stockée dans la mémoire du portefeuille, mais cet enregistrement ne se propage pas comme par magie aux autres L2.
Si un utilisateur possède de nombreuses adresses sur de nombreuses L2, y compris des adresses que le L2 auquel il se trouve (parce que c'est contrefactuel) ne connaît pas, alors il semble qu'il n'y a qu'un seul moyen de permettre aux utilisateurs de modifier leurs clés : l'architecture de séparation des actifs et des magasins de clés. Chaque utilisateur possède (i) un « contrat de stockage de clés » (sur la L1 ou sur une L2 en particulier), qui stocke la clé de vérification de tous les portefeuilles ainsi que les règles relatives au changement de clé, et (ii) des « contrats de portefeuille » sur la L1 et sur de nombreux L2, qui lisent les chaînes pour obtenir la clé de vérification.
Il existe deux manières de le mettre en œuvre :
Pour en montrer toute la complexité, nous allons explorer le cas le plus difficile : celui où le keystore se trouve sur une L2 et le portefeuille sur une autre L2. Si le keystore ou le portefeuille se trouvent sur L1, seule la moitié de ce design est nécessaire.
Supposons que le keystore se trouve sur Linea et que le portefeuille se trouve sur Kakarot. Une preuve complète des clés du portefeuille comprend :
Il y a deux principales questions de mise en œuvre ici :
Il existe cinq options principales :
En termes de travaux d'infrastructure requis et de coûts pour les utilisateurs, je les classe à peu près comme suit :
Le terme « agrégation » fait référence à l'idée de regrouper toutes les preuves fournies par les utilisateurs au sein de chaque bloc dans une grande méta-preuve qui les combine toutes. C'est possible pour SNArks et pour KZG, mais pas pour les agences Merkle (vous pouvez légèrement combiner les branches Merkle, mais cela vous permet d'économiser du log (txs par bloc) /log (nombre total de keystores), peut-être 15 à 30 % en pratique, donc ça ne vaut probablement pas le coût).
L'agrégation ne vaut la peine que lorsque le système compte un nombre important d'utilisateurs. En réalité, il est normal pour une implémentation en version 1 de laisser de côté l'agrégation et de l'implémenter pour la version 2.
Celui-ci est simple : suivez directement le schéma de la section précédente. Plus précisément, chaque « preuve » (en supposant que le cas de difficulté maximale consiste à prouver une L2 contre une autre) contiendrait :
Malheureusement, les preuves d'état d'Ethereum sont compliquées, mais il existe des bibliothèques pour les vérifier, et si vous utilisez ces bibliothèques, ce mécanisme n'est pas trop compliqué à mettre en œuvre.
Le plus gros problème, c'est le coût. Les preuves de Merkle sont longues, et les arbres Patricia le sont malheureusement environ 3,9 fois plus que nécessaire (précisément : une preuve de Merkle idéale pour un arbre contenant N objets fait 32 log2 (N) octets, et comme les arbres Patricia d'Ethereum ont 16 feuilles par enfant, les preuves de ces arbres font 32 15 log16 (N) ~= 125 log2 (N) octets). Dans un État comptant environ 250 millions (~2 m²) de comptes, chaque preuve fait 125 * 28 = 3 500 octets, soit environ 56 000 de gaz, plus les frais supplémentaires liés au décodage et à la vérification des hachages.
Deux preuves réunies coûteraient entre 100 000 et 150 000 gaz (sans compter la vérification de la signature si elle est utilisée par transaction), soit bien plus que les 21 000 gaz de base actuels par transaction. Mais la disparité ne fait qu'empirer si les preuves sont vérifiées en L2. Les calculs au sein d'une L2 ne coûtent pas cher, car ils se font hors chaîne et dans un écosystème comportant beaucoup moins de nœuds que la L1. Les données, quant à elles, doivent être publiées en L1. La comparaison n'est donc pas de 21 000 gaz contre 150 000 gaz ; il s'agit de 21 000 L2 contre 100 000 gaz L1.
Nous pouvons calculer ce que cela signifie en comparant les coûts du gaz L1 et les coûts du gaz L2 :
La L1 coûte actuellement 15 à 25 fois plus chère que la L2 pour les envois simples, et 20 à 50 fois plus chère pour les échanges de jetons. Les envois simples sont relativement gourmands en données, mais les swaps sont beaucoup plus gourmands en termes de calcul. Les swaps constituent donc une meilleure référence pour déterminer le coût approximatif du calcul L1 par rapport au calcul L2. Compte tenu de tout cela, si nous supposons un ratio de coûts multiplié par 30 entre les coûts de calcul L1 et les coûts de calcul L2, cela signifie que le fait d'ajouter une preuve Merkle à L2 coûtera l'équivalent de cinquante transactions régulières.
Bien sûr, l'utilisation d'un arbre de Merkle binaire peut réduire les coûts d'environ 4 fois, mais malgré tout, le coût sera trop élevé dans la plupart des cas. Et si nous sommes prêtes à faire le sacrifice de ne plus être compatibles avec l'arbre d'états hexaires actuel d'Ethereum, autant rechercher des options encore meilleures.
D'un point de vue conceptuel, l'utilisation des zk-SNARK est également facile à comprendre : il vous suffit de remplacer les preuves de Merkle dans le schéma ci-dessus par un ZK-SNARK prouvant que ces preuves de Merkle existent. Un ZK-SNARK coûte environ 400 000 gaz de calcul, et environ 400 octets (à comparer : 21 000 gaz et 100 octets pour une transaction de base, à l'avenir, réductible à environ 25 octets avec compression). Du point de vue informatique, un ZK-SNARK coûte 19 fois le coût d'une transaction de base aujourd'hui, et du point de vue des données, un ZK-SNARK coûte 4 fois plus cher qu'une transaction de base aujourd'hui, et 16 fois ce qu'une transaction de base pourrait coûter à l'avenir.
Ces chiffres constituent une nette amélioration par rapport aux preuves de Merkle, mais ils restent assez chers. Il existe deux manières d'améliorer cela : (i) des preuves KZG spécialisées, ou (ii) l'agrégation, similaire à l'agrégation ERC-4337 mais en utilisant des mathématiques plus sophistiquées. Nous pouvons examiner les deux.
Attention, cette section est beaucoup plus mathématique que les autres. C'est parce que nous allons au-delà des outils à usage général et que nous créons quelque chose de spécial pour être moins cher. Nous devons donc passer beaucoup plus de temps à « passer sous le capot ». Si vous n'aimez pas les mathématiques approfondies, passez directement à la section suivante.
Tout d'abord, un résumé du fonctionnement des engagements du KZG :
Certaines propriétés clés qu'il est important de comprendre sont les suivantes :
Nous avons donc une structure qui nous permet de continuer à ajouter des valeurs à la fin d'une liste qui ne cesse de s'allonger, mais avec une certaine limite de taille (en réalité, des centaines de millions pourraient être viables). Nous l'utilisons ensuite comme structure de données pour gérer (i) un engagement envers la liste des clés de chaque L2, stockée sur cette L2 et mise en miroir sur la L1, et (ii) un engagement envers la liste des engagements clés L2, stockée sur l'Ethereum L1 et reflétée sur chaque L2.
La mise à jour des engagements pourrait soit faire partie de la logique principale de la L2, soit être mise en œuvre sans modification du protocole principal de la L2 par le biais de passerelles de dépôt et de retrait.
Pour obtenir une preuve complète, il faudrait donc :
Il est en fait possible de fusionner les deux preuves KZG en une seule, donc nous obtenons une taille totale de 100 octets seulement.
Notez une subtilité : comme la liste des touches est une liste, et non une carte clé/valeur comme c'est le cas pour l'État, la liste des touches devra attribuer les positions de manière séquentielle. Le contrat d'engagement des clés contiendrait son propre registre interne associant chaque keystore à un identifiant, et pour chaque clé, il stockerait le hash (clé, adresse du keystore) au lieu d'une simple clé, afin de communiquer sans ambiguïté aux autres L2 de quel keystore parle une entrée en particulier.
L'avantage de cette technique, c'est qu'elle fonctionne très bien en L2. Les données font 100 octets, soit environ 4 fois plus courtes qu'un ZK-SNARK et bien plus courtes qu'une preuve Merkle. Le coût de calcul correspond en grande partie à un chèque d'appariement de taille 2, soit environ 119 000 essence. En L1, les données sont moins importantes que les calculs. Malheureusement, le KZG coûte un peu plus cher que les preuves de Merkle.
Les arbres Verkle consistent essentiellement à empiler les engagements KZG (ou les engagements IPA, qui peuvent être plus efficaces et utiliser une cryptographie plus simple) les uns sur les autres : pour stocker 2 valeurs, vous pouvez prendre un engagement KZG sur une liste de 2 valeurs, chacune d'entre elles constituant en elle-même un engagement KZG à 2 valeurs. Les arbres Verkle sont fortement < a href= " https://notes.ethereum.org/@vbuterin/verkle_tree_eip " > considéré pour l'arbre d'état d'Ethereum, parce que les arbres de Verkle peuvent être utilisés pour contenir des cartes de valeurs clés et pas seulement des listes (en gros, vous pouvez créer un arbre de 2 m² mais le démarrer vide, en ne remplissant que des parties spécifiques de l'arbre une fois que vous en avez réellement besoin).
À quoi ressemble un arbre Verkle. En pratique, vous pouvez donner à chaque nœud une largeur de 256 == 2 pour les arbres basés sur l'IPA, ou de 2 ² pour les arbres basés sur le KzG.
Les preuves dans les arbres Verkle sont légèrement plus longues qu'en KZG ; elles peuvent faire quelques centaines d'octets. Ils sont également difficiles à vérifier, surtout si vous essayez de regrouper de nombreuses preuves en une seule.
En réalité, les arbres Verkle devraient être considérés comme des arbres Merkle, mais ils sont plus viables sans SNARking (en raison de la baisse des coûts de données) et moins chers avec SnarKing (grâce à la baisse des coûts des prouvateurs).
Le principal avantage des arbres de Verkle est la possibilité d'harmoniser les structures de données : les preuves de Verkle peuvent être utilisées directement sur l'état L1 ou L2, sans structures de superposition, et en utilisant exactement le même mécanisme pour L1 et L2. Une fois que les ordinateurs quantiques poseront un problème, ou une fois que les branches de Merkle seront suffisamment efficaces, les arbres de Verkle pourraient être remplacés sur place par un arbre de hachage binaire doté d'une fonction de hachage adaptée à Snark.
Si N utilisateurs effectuent N transactions (ou, de façon plus réaliste, N ERC-4337 UserOperations) qui doivent prouver N réclamations inter-chaînes, nous pouvons économiser beaucoup de gaz en agrégeant ces preuves : le constructeur qui combinerait ces transactions dans un bloc ou un ensemble pouvant être intégré à un bloc peut créer une preuve unique prouvant toutes ces affirmations simultanément.
Cela peut vouloir dire :
Dans les trois cas, les preuves ne coûteraient que quelques centaines de milliers d'essence chacune. Le créateur devrait en créer un sur chaque L2 pour les utilisateurs de cette L2 ; par conséquent, pour que cela soit utile, le schéma dans son ensemble doit être suffisamment utilisé pour qu'il y ait très souvent au moins quelques transactions dans le même bloc sur plusieurs L2 majeurs.
Si des zk-SNARK sont utilisés, le principal coût marginal est simplement la « logique commerciale » qui consiste à transmettre des chiffres entre les contrats, soit peut-être quelques milliers de L2 par utilisateur. Si des appareils KZG sont utilisés, le testeur devra ajouter 48 litres de gaz pour chaque unité de stockage de gaz utilisée dans ce bloc. Ainsi, le coût marginal du système par utilisateur ajouterait environ 800 litres de gaz supplémentaires par litre (et non par utilisateur) en plus. Mais ces coûts sont bien inférieurs à ceux liés à l'absence d'agrégation, qui implique inévitablement plus de 10 000 litres de gaz L1 et des centaines de milliers de gaz L2 par utilisateur. Pour les arbres Verkle, vous pouvez soit utiliser directement les multi-preuves Verkle, en ajoutant environ 100 à 200 octets par utilisateur, soit créer un ZK-SNARK à partir d'un Verkle multiproof, ce qui coûte des coûts similaires à ceux des branches ZK-SNARKS de Merkle mais c'est nettement moins cher à prouver.
Du point de vue de la mise en œuvre, il est probablement préférable de demander à des groupeurs d'agréger les preuves inter-chaînes via la norme d'abstraction de comptes ERC-4337. L'ERC-4337 dispose déjà d'un mécanisme permettant aux créateurs d'agréger certaines parties de UserOperations de manière personnalisée. Il existe même une implémentation < a href= " https://hackmd.io/@voltrevo/BJ0QBY3ZI " > pour l'agrégation de signatures BLS, ce qui pourrait réduire les coûts de gaz sur la couche L2 de 1,5 à 3 fois selon les autres formes de compression incluses.
Schéma tiré d'un article < a href= " https://hackmd.io/@voltrevo/bj0qby3zi " > sur la mise en œuvre du portefeuille BLS montrant le flux de travail des signatures agrégées BLS dans une version antérieure de l'ERC-4337. Le flux de travail d'agrégation des preuves inter-chaînes sera probablement très similaire.
Une dernière possibilité, utilisable uniquement pour la L2 lisant la L1 (et non la L1 lisant L2), est de modifier les L2 pour leur permettre de passer des appels statiques aux contrats de L1 directement.
Cela peut être fait à l'aide d'un opcode ou d'une précompilation, qui autorise les appels en L1 où vous fournissez l'adresse de destination, le gaz et les données d'appel, et qui renvoie le résultat, mais comme ces appels sont statiques, ils ne peuvent pas réellement modifier l'état L1. Les L2 doivent déjà connaître la L1 pour traiter les dépôts. Rien de fondamental n'empêche donc la mise en œuvre d'une telle solution ; il s'agit principalement d'un défi de mise en œuvre technique (voir : cet appel d'offres d'Optimism visant à prendre en charge les appels statiques vers la L1).
Notez que si le keystore est en L1 et que les L2 intègrent la fonctionnalité d'appel statique L1, aucune preuve n'est requise ! Cependant, si les L2 n'intègrent pas les appels statiques L1, ou si le keystore est en L2 (ce qui devra peut-être être le cas, une fois que la L1 deviendra trop chère pour que les utilisateurs puissent l'utiliser ne serait-ce qu'un tout petit peu), alors des preuves seront requises.
Tous les schémas ci-dessus nécessitent que la L2 accède soit à la racine de l'état L1 récent, soit à l'intégralité de l'état L1 récent. Heureusement, toutes les L2 disposent déjà de certaines fonctionnalités qui leur permettent d'accéder à l'état récent de la L1. C'est parce qu'ils ont besoin d'une telle fonctionnalité pour traiter les messages provenant de la L1 vers la L2, notamment les dépôts.
Et en effet, si une L2 possède une fonction de dépôt, vous pouvez utiliser cette L2 telle quelle pour transférer les racines de l'État L1 dans un contrat sur la L2 : il suffit de créer un contrat en L1, d'appeler l'opcode BLOCKHASH, et de le transmettre à L2 sous forme de message de dépôt. L'en-tête du bloc complet peut être reçu, et sa racine d'état extraite, côté L2. Cependant, il serait préférable que chaque L2 dispose d'un moyen explicite d'accéder directement à l'état L1 complet récent ou aux racines récentes de l'État L1.
Le principal défi lié à l'optimisation de la façon dont les L2 reçoivent les racines d'état L1 récentes est de garantir à la fois la sécurité et une faible latence :
De plus, dans la direction opposée (L1 lit L2) :
Certaines de ces vitesses pour les opérations inter-chaînes en toute confiance sont trop lentes pour de nombreux cas d'utilisation de DeFi ; dans ces cas, vous avez besoin de ponts plus rapides avec des modèles de sécurité plus imparfaits. Pour ce qui est de la mise à jour des clés de portefeuille, des délais plus longs sont toutefois plus acceptables : vous ne retardez pas les transactions de plusieurs heures, vous retardez les changements de clés. Vous n'aurez qu'à conserver les anciennes clés plus longtemps. Si vous changez de clé parce que des clés sont volées, c'est que vous êtes exposée à une période de vulnérabilité importante, mais cela peut être atténué, par exemple en installant une fonction de blocage des portefeuilles.
En fin de compte, la meilleure solution pour minimiser la latence est que les L2 implémentent la lecture directe des racines d'état L1 de manière optimale, où chaque bloc L2 (ou le journal de calcul de la racine d'état) contient un pointeur vers le bloc L1 le plus récent. Ainsi, si la L1 revient, la L2 peut également revenir en arrière. Les contrats Keystore doivent être passés soit sur le réseau principal, soit sur des L2 qui sont des ZK-Rollups, ce qui permet de passer rapidement en L1.
Les blocs de la chaîne L2 peuvent dépendre non seulement des blocs L2 précédents, mais aussi d'un bloc L1. Si la L1 revient après un tel lien, la L2 revient également. Il convient de noter que c'est également ainsi qu'une version antérieure (antérieure à Dank) du sharding était envisagée ; voir le code ici.
Étonnamment, pas tant que ça. En fait, il n'est même pas nécessaire que ce soit un cumul : s'il s'agit d'un L3 ou d'un Validium, vous pouvez y conserver des portefeuilles, à condition que vous déteniez des keystores en L1 ou en ZK. Ce dont vous avez besoin, c'est que la chaîne ait un accès direct aux racines de l'État d'Ethereum et qu'elle s'engage techniquement et socialement à être prête à se réorganiser si Ethereum se réorganise, et à hard fork si Ethereum fait des hard forks.
Un problème de recherche intéressant est de déterminer dans quelle mesure il est possible pour une chaîne d'avoir cette forme de connexion avec plusieurs autres chaînes (par ex. Ethereum et Zcash). Le faire naïvement est possible : votre chaîne pourrait accepter de se réorganiser en cas de réorganisation d'Ethereum ou de Zcash (et de hard fork s'il s'agit d'Ethereum ou de Zcash), mais les opérateurs de vos nœuds et votre communauté en général ont deux fois plus de dépendances techniques et politiques. Une telle technique pourrait donc être utilisée pour se connecter à quelques autres chaînes, mais à un coût plus élevé. Les schémas basés sur les ponts ZK ont des propriétés techniques intéressantes, mais leur principal point faible est qu'ils ne résistent pas aux attaques à 51 % ou aux hard forks. Il existe peut-être des solutions plus intelligentes.
Idéalement, nous voulons également préserver la confidentialité. Si vous avez de nombreux portefeuilles gérés par le même keystore, nous voulons nous assurer que :
Cela pose quelques problèmes :
Avec les SNARKs, les solutions sont simples sur le plan conceptuel : les preuves masquent des informations par défaut, et l'agrégateur doit produire un SNARK récursif pour prouver les SNARK.
Le principal défi de cette approche aujourd'hui est que l'agrégateur doit créer un SNARK récursif, ce qui est assez lent actuellement.
Avec KZG, nous pouvons utiliser < a href= " https://notes.ethereum.org/@vbuterin/non_index_revealing_proof " > pour ce travailler sur des preuves KZG non révélatrices d'index (voir aussi : une version plus formalisée de ce travail dans l'article de Caulk) comme point de départ . L'agrégation de preuves en aveugle est toutefois un problème ouvert qui nécessite plus d'attention.
La lecture directe de la L1 depuis la L2 ne préserve malheureusement pas la confidentialité, même si la mise en œuvre d'une fonctionnalité de lecture directe reste très utile, à la fois pour minimiser la latence et pour d'autres applications.