ZK-verifier: привязка доказательства к действию
Успешная проверка ещё не выдаёт право
Участник подготовил доказательство членства. Теперь библиотека должна принять его и выдать право на выпуск 2 курса 17. Частая ошибка — увидеть true от проверяющей функции и сразу связать результат с текущим пользователем браузера. Но proof подтверждает определённое публичное утверждение. Если оно не связано с получателем права, копию может отправить другой человек.
Введём отдельный AccessClaimRegistry. Его задача — проверить допустимый контекст, доказательство и однократность, затем выполнить конкретный эффект. Он не меняет базовый CourseCatalog и не считает членство оплатой. Источником группы служит управляемый реестр участников, а библиотека задаёт правила принятия корней и сообщения.
Проверяющий соответствует конкретной схеме
Verifier связан с системой доказательства, параметрами и версией отношения. Любой контракт с методом verify не подходит к любому proof. При интеграции сохраняются версия протокола, артефакты схемы, способ кодирования публичных данных и адрес проверяющего в конкретной сети. Исходная схема и сгенерированный verifier — части одного профиля.
В этой главе опираемся на понятия Semaphore V4. Пакет не установлен, проверяющий не развёрнут, доказательство не создано. Описание circuit помогает понять связку членства, nullifier и сообщения. Собственный безопасный криптографический протокол из сокращённого примера не выводим.
Если приложение обновляет verifier, оно меняет возможные доказуемые утверждения. Такая операция требует ограниченной роли и понятной политики старых прав. Переключение адреса через открытый setter позволило бы заменить проверяющий контракт на функцию, всегда возвращающую true. Поэтому правильная математика не освобождает от управления конфигурацией.
Привязываем получателя
В библиотечном сообщении фиксируем курс, выпуск, тип права, адрес получателя, целевую сеть и адрес приложения. Версия кодирования предотвращает смешение форматов. Для собственной EVM-модели можно рассмотреть следующее образование значения message:
// Фрагмент спецификации адаптера, не полный контракт доступа.
uint256 expectedMessage = uint256(keccak256(abi.encode(
"LibraryClaim-v1",
block.chainid,
address(this),
courseId,
edition,
beneficiary,
claimType
)));
Здесь требуется согласованно определить типы courseId, edition и claimType и использовать тот же порядок на стороне подготовки доказательства. abi.encode задаёт структурированное кодирование, поэтому поля не объединяются простым склеиванием строк. Это отдельное сообщение действия. ContentHash опубликованного выпуска по-прежнему равен SHA-256 точных байтов манифеста; разные задачи не требуют одного алгоритма во всех местах.
Адаптер сравнивает переданный message с ожидаемым и не принимает произвольное сообщение пользователя. В реальной интеграции формат этого uint256 должен соответствовать пути обработки выбранного протокола. Нельзя обрезать случайный digest до размера поля самостоятельно и считать, что все стороны теперь согласованы.
Копия из публичного пула
Пусть Анна подготовила proof для beneficiary A. Борис увидел транзакцию и отправил её раньше. Если эффект всегда выдаётся указанному в message получателю A, копирование не передаёт право Борису. Отправитель транзакции и получатель права могут различаться: это позволяет оплачивать доставку через помощника, сохраняя привязку результата.
Если же контракт выдаёт право msg.sender, а message не включает получателя, Борис выигрывает гонку. Проверка членства всё равно может быть корректной. Ошибка находится между доказанным утверждением и эффектом. Поэтому сценарий копирования разбираем до написания красивого интерфейса отправки.
Изменим beneficiary в вызове с A на B, сохранив proof. Ожидаем несовпадение message и отказ. Изменим target chain или адрес приложения: контекст тоже меняется. Такой договор ограничивает перенос proof в другой экземпляр библиотеки. Он не делает доказательство секретным — оно остаётся публичным, но его копия не должна менять адресата.
Проверка и потребление различаются
В текущем исходнике Semaphore verifyProof проверяет доказательство и допустимый корень, а validateProof дополнительно записывает использованный nullifier. Эти операции нельзя бездумно взаимозаменять.
Для нашего адаптера выбираем концептуальный маршрут: проверять членство через совместимую view-проверку, а однократность бизнес-права вести в собственном журнале AccessClaimRegistry. Это решение объясняется в следующей главе. Внешний публичный validateProof может быть вызван отдельным участником до библиотечного вызова; простой wrapper вокруг него не устраняет возможность раннего потребления.
Другой архитектурный вариант мог бы обрабатывать события принятой проверки и выдавать связанный результат через специально разработанный процесс. Но он требует своего договора и согласования эффекта. Здесь не смешиваем варианты: библиотечный nullifier-журнал и проверка через view имеют собственную область и не зависят от чужой отметки потребления в Semaphore.
Порядок атомарного действия
Сначала проверяем версию сообщения и допустимый контекст. Затем выбираем доверенную группу, проверяем proof и собственный журнал использованных nullifier. После успешного решения отмечаем право использованным и выдаём результат в одной согласованной операции. Если транзакция целиком откатилась, отметка и выдача не должны расходиться.
Внешние вызовы во время выдачи добавляют возможность повторного входа. Для первой модели ограничиваем эффект простой записью права в реестр без передачи средств и произвольных callback. Последующее получение файла — отдельное действие веб-приложения. Оно читает запись, а не пытается завершить сетевую загрузку внутри проверки доказательства.
Старые корни допускаются только по объявленной политике группы. Удаление участника может не действовать мгновенно, если существует окно старых корней. Интерфейс показывает этот смысл явно: текущий состав группы и допустимость подготовленного proof не обязательно обновляются одновременно.
Ожидаемые случаи приёмки
Первое доказательство Анны для курса 17 и выпуска 2 выдаёт право A. Его копия либо завершает тот же эффект первой, либо встречает использованный nullifier; право B не появляется. Доказательство для посторонней группы отклоняется. Правильный proof с другим claimType не разрешает исходное действие. Замена verifier доступна только определённой роли и фиксируется в журнале конфигурации.
Все случаи описывают ожидаемое поведение проектируемого адаптера. Они не подтверждены исполнением. До выбора точного пакета и артефактов proof необходимо сохранить совместимость форматов, параметры сети и границы управления. Следующая глава уточняет последний элемент: как однократность сохраняется при смене корня, повторной доставке и создании новой области права.