Avertissement
Cette caisse a été archivée. Le développement a été déplacé vers le référentiel zksync-crypto. Veuillez l'utiliser à la place.
zkSync Era est un cumul de couche 2 qui utilise des preuves sans connaissance pour faire évoluer Ethereum sans compromettre la sécurité ou la décentralisation. Puisqu'il est compatible EVM (Solidity/Vyper), 99 % des projets Ethereum peuvent se redéployer sans refactoriser ni ré-auditer une seule ligne de code. zkSync Era utilise également un compilateur basé sur LLVM qui permettra à terme aux développeurs d'écrire des contrats intelligents en C++, Rust et d'autres langages populaires.
Le but de cette bibliothèque est de travailler avec une arithmétique très spécifique avec des hypothèses supplémentaires sur la taille du champ. En gros, on s'attend à avoir un corps F
avec |F| ~ 64 bits (la taille d'un mot machine) (l'hypothèse sur la taille du champ n'est pas importante pour la stratégie d'arithmétisation et de placement des portes, mais elle est affirmée dans les implémentations de gadgets pour des fonctions particulières qui reposent sur une taille de champ spécifique).
Le système possède une hiérarchie de fonctions logiques (gadgets) - portes (entités pouvant s'inscrire dans la trace) - et d'évaluateurs (relations entre polynômes). Les évaluateurs sont écrits sous la forme d'un trait qui nous permet ultérieurement de composer automatiquement des fonctions pour vérifier la satisfiabilité et calculer des preuves, ainsi que de synthétiser des vérificateurs simples et récursifs. Les portes sont dotées d'outils supplémentaires qui leur permettent de suivre la logique de l'endroit où elles doivent être placées dans la trace. Notez que nous nous appuyons sur les contraintes de copie de Plonk et travaillons sur les entités logiques copiables des « variables » pour composer une instruction finale prouvable. Le système n'est pas destiné à l'arithmétisation AIR et ne permet pas d'exprimer des contraintes qui s'étendent sur plusieurs lignes de la trace.
En général, la trace contient peu de variétés de colonnes. La séparation principale se situe entre :
De plus, la trace vous permet d'ajouter un argument de recherche, qui peut également utiliser des colonnes spécialisées pour héberger les entrées des tables de recherche, ou simplement utiliser des colonnes à usage général. Les tableaux sont pour l'instant codés comme un seul ensemble de polynômes, la longueur totale de la trace doit donc être supérieure au nombre total d'entrées dans les tableaux.
Notez que chaque "porte" (en tant que type Rust) est unique, et donc une porte ne peut être placée que dans des colonnes spécialisées ou à usage général, mais pas dans les deux. Si l'on a besoin d'une telle fonctionnalité, il est alors possible de créer un nouveau type de wrapper.
Des fonctions logiques de niveau supérieur (comme les décompositions booléennes, les contrôles de plage, les contrôles de zéro, etc.) sont utilisées pour qu'un circuit s'inscrive en interne de différentes manières selon que certaines portes sont autorisées ou non dans l'instance particulière du CS. Les instances du CS avec différents ensembles de portes sont considérées comme un type différent du point de vue de Rust, et nous nous appuyons sur certains travaux d'accessoires/compilateurs inline/constants pour réduire les branchements vers des sauts statiques.
|F|
et nous devons donc répéter les arguments), mais cela sera modifié pour que nous passions au champ d'extension comme le plus vite possible après s'être engagé auprès du témoin pour éviter une chance assez "grande" d'obtenir des zéros au dénominateur. L'effet sur les dépenses de calcul lors de la preuve est tout à fait négligeable Nous utilisons un argument de recherche appliqué via les relations sum_i selector(x_i) / (witness(x_i) + beta) == sum_i multiplicity(x_i) / (table(x_i) + beta)
où une recherche sur selector(x_i)
n'est qu'une identité. Nous ne codons pas non plus les tableaux en tant que polynôme de degré inférieur pour éliminer les vérifications supplémentaires liées aux degrés, mais les complétons avec des zéros. Notez que les entrées de table ne contiennent jamais d'élément de (0,0,...,0)
car nous utilisons des colonnes d'ID distinctes pour les types de tables en cas de tables multiples (même dans le cas d'une seule table utilisée) et un ID comme ça commence par 1.
Une caractéristique intéressante d'un argument de recherche comme celui-ci est que, en raison de sa nature additive, si nous effectuons des recherches sur plusieurs polynômes witness
dans la même table, au lieu de répéter l'argument pour chaque (tuple de) polynôme(s) (ce qui nécessiterait une colonne de multiplicité séparée, ainsi que quelques polynômes intermédiaires plus tard), nous pouvons "additionner" les multiplicités et transformer l'argument en quelque chose comme sum_i selector_0(x_i) / (witness_0(x_i) + beta) + sum_i selector_1(x_i) / (witness_1(x_i) + beta) == sum_i total_multiplicity(x_i) / (table(x_i) + beta)
, de sorte que le coût total d'une recherche est juste 1 colonne de multiplicités, et 2 (liés aux témoins) + 1 (liés aux tables) intermédiaires polynômes pour coder les relations lhs et rhs sur les racines de l'unité.
La justesse de cet argument est claire. Pour des raisons de solidité, nous utilisons l'argument original comme dans l'article "Quotients mis en cache pour des recherches rapides", Lemme 2.4. Nous devons montrer qu'il suffit de s'engager sur la total_multiplicity
plutôt que sur les multiplicités de witness_0
et witness_1
séparément.
Supposons que l'équation sum_i selector_0(x_i) / (witness_0(x_i) + X) + sum_i selector_1(x_i) / (witness_1(x_i) + X) == sum_i total_multiplicity(x_i) / (table(x_i) + X)
tient. Nous devons montrer que witness_0
et witness_1
sont contenus dans le tableau t
. Soit f = (witness_0 | witness_1)
, une concaténation des valeurs. L'équation ci-dessus implique sum_i selector_i / (f_i + X) == sum_i total_multiplicity_i / (t_i + X)
(notez que la longueur de l'intervalle de i
sur le LHS est le double de celle ci-dessus). D'après le lemme 2.4, nous obtenons f subset t
: "sous-ensemble" dans le sens où chaque coordonnée du vecteur f
est une coordonnée de t
. En particulier, witness_0, witness_1 subset f subset t
.
Notez que l'argument est également valable pour plusieurs witness_i
. Le reste de l’argument de solidité, pour un beta
choisi, suit directement comme dans le travail ci-dessus.
2^-40
chances d'obtenir 0
aux dénominateurs. Il existe des références pour 8 Ko de SHA256 utilisant ce que nous pensons être une configuration quelque peu optimale de portes + tables pour le circuit SHA256. Notez que même si le prouveur est plutôt rapide, nous n'avons pas correctement optimisé la FFT et utilisons toujours Poséidon (pas Poséidon2) pour les configurations où nous nous attendons à ce que la preuve soit utilisée à des fins de récursivité. Deux scripts sha256_bench_recursive.sh
et sha256_bench_non_recursive.sh
vous permettent d'exécuter les tests correspondants (que la preuve soit censée être utilisée en récursion ou non), et vous devez chercher une ligne Proving is done, taken ...
pour voir le temps de preuve , car le vérificateur qui s'exécute après est assez verbeux. Ces benchmarks utilisent un facteur LDE de 8, même si toutes nos contraintes sont de degré 4 ou moins - cependant, c'est un paramètre qui est utilisé dans certains autres benchmarks publics. Nous n'utilisons pas non plus PoW dans ces preuves car PoW pour 20 bits est négligeable (30 ms), et nous ne prenons pas encore en charge PoW sur les hachages algébriques (cependant, ceux-ci ne sont qu'environ 2 fois plus lents, donc également négligeables). Le niveau de sécurité est d'environ 100
bits, mais la solidité du FRI peut être améliorée en augmentant le nombre de requêtes, et une augmentation du nombre de requêtes n'augmente pas le temps de preuve (à ne pas confondre avec la modification du taux FRI). La longueur de la trace est 2^16
et utilise 60 colonnes à usage général et 8 arguments de recherche de largeur 4.
Remarque : les benchmarks essaient simplement de compiler en arch natif et seul l'arc AArch64 (lire Apple M1) est normalement testé de bout en bout pour le moment. La validité des implémentations arithmétiques x86-64 a été testée, mais pas de bout en bout dans des preuves complètes. Notez que les performances maximales x86-64 nécessitent des indicateurs de fonctionnalités supplémentaires du compilateur en plus de cpu = native
(l'ensemble AVX512 n'est pas utilisé par le compilateur Rust, même sur les processeurs natifs)
Le prouveur Boojum est distribué selon les termes de l'un ou l'autre
à votre choix.
zkSync Era a subi de nombreux tests et audits. Bien qu'il soit en ligne, il est toujours à l'état alpha et fera l'objet de davantage d'audits et de programmes de bug bounties. Nous serions ravis d’entendre les réflexions et suggestions de notre communauté à ce sujet ! Il est important de préciser que le faire maintenant peut potentiellement entraîner la perte de mises à jour de sécurité importantes, de fonctionnalités critiques et d’améliorations des performances.
Ce logiciel inclut des composants provenant de tiers. Pour une liste complète de ces composants et de leurs licences, consultez le fichier AVIS AUX TIERS.