Доказательства с нулевым разглашением - важный инструмент для нас в децентрализации. Как мы обеспечиваем конфиденциальность, когда наша платформа хранит данные, которые прозрачны в глобальном масштабе? А как насчет решений для масштабирования, которые работают «вне сети», что затем необходимо подтверждать доказательствами в цепочке? Доказательства с нулевым разглашением играют свою роль в решении подобных проблем.

Когда дело доходит до написания кода для схем ZK, у нас есть несколько библиотек на выбор. Я обнаружил circom и snarkjs от команды identify3. Это хороший выбор для новичков в этой области: схемы легко кодировать, и они работают с простыми командами, не требующими экзотических сред.

Учебное пособие для изучения circom и snarkjs действительно полезно. Для меня было очень приятно и познавательно наблюдать, как процесс разворачивается перед вами. Эта статья призвана помочь вам выйти за рамки вводного руководства и изучить несколько дополнительных аспектов circom. Он основан на простой модифицированной версии покера, в которой важную роль играет доказательство с нулевым разглашением.

ZK Poker

Правила ZK Poker немного проще, чем в других разновидностях игры.

  • Игрокам раздается 5 карт,
  • Обмен карты не допускается.
  • При ранжировании рук учитываются только пары. Стриты, флеши, фулл-хаусы и т. Д. Игнорируются.
  • Варианты во время хода игрока - сбросить карты, посмотреть карты или поднять ставку.
  • Игроки не должны делать ставки, если в их руке нет пары.

Это последнее правило - не блефовать - - это то, которое должно соблюдаться доказательством ZK. Участник торгов может избежать разглашения своих карт, одновременно доказывая другим игрокам, что они соблюдают правило не блефовать. Мы проигнорируем механику игры (оценку победителя, оценку ставок и т. Д.) И просто сконцентрируемся на схеме, которая создаст ZKP.

Схема

Примерный план схемы:

  • Собирайте входные данные: руку и вариант ставки игрока.
  • Ищите хотя бы одну пару в руке.
  • Оцените вид ставки (сбросить или сыграть)
  • Установите ограничение, чтобы убедиться, что выбор сделан
  • Установите ограничение, чтобы проверить, что выбор действителен при наличии или отсутствии пары.

Давайте пройдемся по коду схемы по несколько строк за раз и посмотрим, что мы можем узнать в каждой точке.

Включает в себя

Он начинается с некоторых спецификаций include:

include “../node_modules/circomlib/circuits/gates.circom”;
include “../node_modules/circomlib/circuits/comparators.circom”;

Я должен отметить, что моя структура папок:

корень проекта› \ poker

Таким образом, указанный мной путь включения работает для этой структуры. Вам потребуется установить circomlib, что является дополнительным требованием к вводному руководству circom / snarkjs. Используйте эту команду из корневой папки проекта:

npm install --save circomlib

Шаблон схемы

template Poker() {
   ... circuit body ...
}
component main = Poker();

Структура во многом такая же, как и во вводном руководстве. Это один класс, и точка входа объявлена. Красиво и просто.

Объявление входов, выходов и промежуточных результатов

signal private input cards[5]; // Each 2..14
 signal input isSee; // 1 or 0
 signal input isFold; // 1 or 0
 signal input raise; // int
 signal output out; // 1 or 0
 
 // Intermediate results
 signal isBid;
 signal isRaise;
 signal hasChosen;

Карточная комбинация объявляется массивом: cards [5]. Карты просто представлены их номиналом. Костюмы игнорируются. Туз = 14; Король = 13; Королева = 12; Джек = 11; и так далее, вплоть до 2. Рука - это личный ввод, что означает, что он должен оставаться в секрете.

Выбор ставок, конечно же, является публичным, поскольку он будет открыто заявлен другим игрокам. Варианты выбора сворачивания и просмотра представлены как логические. Обратите внимание, что это не объявляется (и не может быть) явным образом. Circom основан на javascript, поэтому типы переменных определяются на основе данных. (Я должен был предупредить о срабатывании триггера для поклонников надежного набора данных.) Ввод raise будет целым числом, представляющим фактическую сумму прибавки.

Единственным выходом является out, который принимает значение 1 для действительного или 0 для недействительного.

Некоторые промежуточные значения объявлены как сигналы . Мы увидим их в действии позже.

Подсчет пар

Этот блок кода определяет, есть ли в руке пара. Обратите внимание, что код очень похож на простой javascript. Он не содержит деклараций ограничений или манипуляций с сигналами.

Здесь есть понимание того, как работают схемы. Реальная схема может включать некоторую предварительную обработку или промежуточную оценку, но будьте осторожны: такой код не является частью формального доказательства. Только ограничения вводятся во время настройки и подтверждаются с помощью цикла проверки / проверки. Нечестный доказывающий может опровергнуть промежуточные результаты, чтобы сфальсифицировать свидетельство. Мне действительно следует обновить код, чтобы ограничения были встроены в оценку пары.

В его нынешнем виде этап предварительной обработки приводит к единственной переменной numPairs. Это будет использоваться на следующих этапах.

Логика здесь представляет собой вложенный цикл, который рассматривает каждую карту по очереди и сравнивает каждую последующую карту, чтобы увидеть, есть ли у нас пара. Как только пара найдена, мы добавляем его к numPairs и выходим. Нас не интересует, действительно ли это часть тройки или четверки, и есть ли вторая пара. Все эти возможности считаются парой для ограничения, которое мы будем оценивать. Также обратите внимание, что break, похоже, не реализован в этой урезанной версии javascript. Этот код просто запускает выход, устанавливая итераторы на высокий уровень.

 // Count pairs
 var numPairs = 0;
 for (var i=0; i<4; i++) {
    for (var j=i+1; j<5; j++) {
       if (cards[i] == cards[j]) {
          numPairs++;
          // break doesn’t work. Just force j and i to exit
          j = 5;
          i = 5;
       }
    }
 }

Ограничения

Вводное руководство охватывает операции с сигналами: <--, -->, <==, ==> и ===. Что нового в этом блоке кода, так это компоненты. Компонент - это экземпляр одного раза из классов, импортированных, в данном случае, из библиотеки circomlib. Обратите внимание на способ их вызова. Входные сигналы компоненту (с именами a, b, in и т. Д.) Назначаются с помощью операторов сигналов. Аналогичным образом можно использовать выходной сигнал компонента (часто называемый out).

Здесь мы используем компоненты для логических операторов. Ознакомьтесь с кодом circomlib, чтобы узнать, что доступно. Имеется обширная библиотека функций, таких как операции хеширования, операции с эллиптической кривой и многое другое.

Итак, наши ограничения определены. Есть ограничение, чтобы проверить, был ли сделан выбор (сбросить карты, увидеть или повысить). Есть еще один, чтобы проверить наличие пары или выбор фолда. И, наконец, сигнал out устанавливается в 1.

 // isRaise = (raise != 0)
 isRaise <-- (raise > 0);
 isBid <-- (isRaise || isSee);
 // Constraint: Must be either bid or fold: isBid XOR isFold = 1
 hasChosen <-- isBid + isFold — 2*isBid*isFold;
 hasChosen === 1;
 // Constraint: numPairs must be > 0 if isBid = 1
 var hasPairs = (numPairs > 0);
 component not3 = NOT();
 not3.in <-- isBid;
 component or2 = OR();
 or2.a <-- hasPairs;
 or2.b <-- not3.out;
 or2.out === 1;
 out <-- or2.out;

Запуск доказательства

Первые шаги будут вам знакомы из вводного руководства:

circom poker.circom -o poker.json
snarkjs setup -o poker.json

Следующий шаг требует предоставления входных данных. Давайте посмотрим на образец входного файла (input.json):

{“cards”: [8, 7, 4, 7, 13], “isFold”: 0, “isSee”: 0, “raise”: 10 }

В руке есть пара, поэтому доступны все варианты ставок. Игрок решил сделать рейз на 10. Мы ожидаем, что этот ввод будет принят как действительный. Рука остается частной, в то время как другие входные данные являются общедоступными. Я не упомянул возможность того, что игрок ошибается. Возможно, это можно было бы обработать путем хеширования руки, сделав хеш общедоступным, что позволило бы подтвердить в конце раздачи. В реальном доказательстве с нулевым разглашением все такие аспекты процедуры, включая код схемы и настройку, должны быть согласованы заранее как проверяющей, так и проверяющей. Проверяющий будет настаивать на исключении любой возможности, которая позволила бы проверяющему подделать свою руку.

Давайте рассмотрим следующие шаги, чтобы получить доказательство:

snarkjs calculatewitness -c poker.json
snarkjs proof

Производительность создания доказательства будет выполняться за время O (n), где n = количество ограничений. В этом примере слишком мало ограничений, чтобы увидеть, как это происходит. См. Здесь для теста со многими другими ограничениями.

А теперь проверка, выполняемая верификатором:

PS C:\dev\snarks\poker> snarkjs verify
OK

… И это работает!

С хорошо продуманным ZK-SNARK цепочка доверия возвращается к настройке. Чтобы получить результат «ОК» на этапе проверки, проверяющий может сделать только одно - создать свидетеля, удовлетворяющего всем ограничениям.

Я надеюсь, вы найдете это полезным.

Полная схема

include “../node_modules/circomlib/circuits/gates.circom”;
include “../node_modules/circomlib/circuits/comparators.circom”;
template Poker() {
 signal private input cards[5]; // Each 2..14
 signal input isSee; // 1 or 0
 signal input raise; // int
 signal input isFold; // 1 or 0
 signal output out; // 1 or 0
 // Intermediate results
 signal isBid;
 signal isRaise;
 signal hasChosen;
 // Count pairs
 var numPairs = 0;
 for (var i=0; i<4; i++) {
    for (var j=i+1; j<5; j++) {
       if (cards[i] == cards[j]) {
          numPairs++;
          // break doesn’t work. Just force j and i to exit
          j = 5;
          i = 5;
       }
    }
 }
 // isRaise = (raise != 0)
 isRaise <--(raise > 0);
 isBid <--(isRaise || isSee);
// Constraint: Must be either bid or fold: isBid XOR isFold = 1
 hasChosen <--isBid + isFold — 2*isBid*isFold;
 hasChosen === 1;
 // Constraint: numPairs must be > 0 if isBid = 1
 var hasPairs = (numPairs > 0);
 component not3 = NOT();
 not3.in <-- isBid;
 component or2 = OR();
 or2.a <-- hasPairs;
 or2.b <-- not3.out;
 or2.out === 1;
 out <-- or2.out;
}
component main = Poker();

Вы можете найти код в github

Получайте лучшие предложения по программному обеспечению прямо в свой почтовый ящик