НОВОЕ: Helius приобретает Light Protocol
Solana Builders — ZK Compression
Блог/Разработка

Solana Builders: ZK Compression

Инженер-программистdubbelosix в X
Чтение: 17 мин

После анонса проекта Zero-Knowledge (ZK) Compression на Solana от Helius и Light Protocol развернулось активное обсуждение ZK Compression. Значительная часть дискуссии посвящена терминологии. Это ZK-роллап? L2? Или нечто совершенно иное?

Почему это важно? Некоторые участники экосистемы Solana считают споры о терминологии излишними. Я отчасти согласен, что название не так важно, как функциональность. Но оно всё же имеет значение, поскольку такие названия обозначают конструкции с определёнными свойствами и объединяют их в категории. Поэтому название XYZ может многое сказать о свойствах и предположениях о доверии — а это действительно важно!

Свойства

Прежде чем оценивать ZK-Compression, перечислим интересующие нас свойства Solana:

  1. Синхронная атомарная компонуемость
  2. Параллельное выполнение
  3. Безопасность
  4. Живучесть
  5. Устойчивость к цензуре

Исходные предположения о доверии

Термин «не требующий доверия» будет обозначать предположения о безопасности полной ноды. Это наша отправная точка. Всё, что полная нода не может сделать самостоятельно, требует дополнительных предположений о доверии.

Немного контекста

Я постараюсь кратко изложить необходимые сведения о Solana, которые помогут понять ZK-Compression. 

  • Состояние Solana хранится на дисках полных нод в «AccountsDB»
  • Единица хранения называется «аккаунтом»
  • У аккаунтов есть адреса (по 32 байта)
  • Объём данных, который может хранить аккаунт, варьируется от 0 до 10 МБ (максимум)
  • Хранение 10 МБ в Solana стоит примерно 70 SOL, которые платит создатель аккаунта. Стоимость зависит от объёма хранилища, а не от количества аккаунтов: это может быть один аккаунт на 10 МБ или 1000 аккаунтов по 10 КБ.
  • Текущий размер всех аккаунтов в Solana составляет 76 ГБ в сжатом виде
  • Каждая транзакция Solana должна указывать все аккаунты, из которых она читает и в которые записывает данные
  • Сейчас размер транзакции Solana ограничен 1232 байтами (существует предложение увеличить лимит)
  • Каждая транзакция Solana должна содержать определённые данные
    • Подписи (по 64 байта)
    • Аккаунты (по 32 байта)
    • Данные инструкций (произвольная длина)
    • Недавний хеш блока (32 байта)
    • Адреса программ (по 32 байта) (CPI: межпрограммные вызовы)
  • Транзакции Solana содержат 32-байтовый «недавний хеш блока» и должны попасть в один из последних 150 блоков. В противном случае они считаются недействительными, и их нужно заново подписать и отправить

Жизненный цикл обычной транзакции

Обычно жизненный цикл выполнения транзакции выглядит так:

  1. Сначала для транзакции проверяются срок действия (действительны только недавние транзакции), отсутствие дубликатов, структура, комиссии (газ) и подписи
  2. Байт-код программы загружается по адресу программы, после чего создаётся экземпляр Solana Virtual Machine (SVM)
  3. Все указанные в транзакции аккаунты проверяются, загружаются из хранилища в память и передаются SVM
  4. Выполняется байт-код программы
  5. Все изменённые аккаунты синхронизируются с хранилищем в обновлённом состоянии

Основные причины использовать ZK-Compression:

  • Хранение состояния ончейн обходится дорого. Например, тысяча аккаунтов стоит 70 SOL, поэтому такие продукты, как Drip Haus, быстро становятся дорогостоящими
  • Даже без полной мерклизации состояния большее количество аккаунтов на диске означает более крупные снимки, индексы и так далее
  • Не ко всем аккаунтам часто обращаются, поэтому постоянно тратить на них ресурсы необязательно

Как добиться сжатия наиболее простым способом?

Вместо хранения аккаунтов на диске и их чтения при необходимости (шаг 3 жизненного цикла выполнения транзакции) можно передавать данные аккаунта в составе полезной нагрузки транзакции. Это снижает затраты на ончейн-хранилище. Но возникает новая проблема: как гарантировать, что пользователи не лгут о состоянии?

Допустим, офчейн-значение аккаунта, в котором хранится баланс токенов, равно 1200, а в поле владельца указано «BYixJwV32DjeuyRww72PwZMyKcaedN533GBrv7CDh4n9».

Если отправить транзакцию с этими данными в блокчейн, как он поймёт, что вы не солгали о количестве токенов на адресе «BYixJwV32DjeuyRww72PwZMyKcaedN533GBrv7CDh4n9»?

Ведь полная нода, обрабатывающая транзакцию, не имеет доступа к офчейн-данным — она ожидает, что вы передадите их вместе с транзакцией.

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

Есть ли здесь проблемы?

Проблема в том, что доказательства Меркла могут быть большими. Если дерево содержит 100 000 аккаунтов, размер доказательства для одного аккаунта равен 17 * 32 = 544 байтам. Если нужно предоставить доказательства для нескольких аккаунтов, в худшем случае размер умножается на их количество. Поэтому для десяти аккаунтов он составит 10 * 544 = 5440 байт. Эта проблема особенно актуальна для Solana, поскольку сейчас размер транзакции Solana ограничен 1232 байтами, тогда как в других блокчейнах ограничения обычно мягче. Выше перечислены размеры каждого компонента: программ, подписей, недавнего хеша блока и так далее. Поэтому даже в лучшем случае одно лишь доказательство Меркла занимает половину транзакции. 

Здесь возникает несколько вопросов.

Если размер транзакции составляет 1232 байта, как передать в ней все данные?

Отличный вопрос. ZK Compression подходит для очень большого количества аккаунтов, если каждый из них содержит мало данных. Балансы токенов (8 байт на токен), небольшие объёмы метаданных NFT и так далее — 100 байт данных легко помещаются в транзакцию. Разместить 1000 байт уже сложно, поскольку в транзакции есть и другие данные. Если аккаунты должны хранить больше данных, этот метод (и ZK Compression) не подойдёт.

Есть один нюанс: логику ZK Compression можно применять к отдельным частям состояния аккаунта. Поэтому, даже если все данные аккаунта невозможно поместить в полезную нагрузку одной транзакции, проблему можно обойти — зафиксировать частичные данные и предоставить для них доказательство.

Доказательства Меркла — единственный способ сделать это?

Нет. Доказательство Меркла — лишь один из видов векторного обязательства. Размер обязательства составляет 32 байта, а размер доказательства — Log2(N) * 32 байта, где N — размер фиксируемого вектора. Именно поэтому выше получилось 17: Log2(100000) равно 17. Однако существуют обязательства с постоянным размером доказательства (KZG, Pedersen). ZK-Compression использует один из таких методов! 

Вы говорите, что данные аккаунта, которые обычно хранятся на полной ноде как часть состояния, нужно передавать вместе с транзакцией. Где хранятся эти данные?

Очень хороший вопрос, влияющий на предположения о доверии и свойства системы. Ответ — где угодно! Эти данные могут хранить специализированные RPC-серверы. Они могут находиться в Filecoin или IPFS, либо пользователь может хранить их на своём компьютере. Важно лишь, чтобы все элементы вектора где-то хранились: тогда доказательства можно вычислять на лету. Последствия выбора места хранения мы рассмотрим в разделе о свойствах. 

Могут ли другие блокчейны делать то же самое?

Для ZK-Compression принципиально важно, чтобы проверка zk-SNARK была дешёвой. Поэтому технология хорошо подходит для Solana, где вычисления дешевле хранения. Обобщённую концепцию векторных обязательств и доказательств для данных, передаваемых в каждой транзакции, можно использовать и в других блокчейнах. Однако высокая стоимость вычислений делает этот компромисс менее выгодным, чем в Solana. Более того, в блокчейнах на базе EVM постоянные затраты газа на проверку в итоге превышают стоимость однократной операции SSTORE.

Что такое ZK Compression?

  • Если вы не знаете, что такое ZK Compression, этот раздел всё объяснит
  • Если у вас есть лишь общее представление, я всё равно рекомендую прочитать этот раздел, поскольку существует несколько распространённых заблуждений
  • Если вы точно знаете, что это такое, всё равно прочитайте раздел и поправьте меня, если я где-то ошибся :) 

Если вы поняли предыдущий раздел, то уже понимаете 90% сути ZK Compression. Основной проблемой был размер доказательства Меркла. ZK Compression решает её с помощью схемы доказательства вычисления.

Если вы не знаете, что такое ZK, это не так важно. Достаточно понимать, что это способ доказать «правильность» выполненного вычисления. Простой пример: вы хотите доказать, что перемножили два числа и получили третье, то есть 4*3 = 12.

Чтобы доказать это «способом ZK», нужна следующая функция:

f(x,y) = x*y

Если создать схему для приведённого выше кода, доказывающая сторона сформирует доказательство правильности вычисления. Сама схема служит обязательством, поэтому все знают, какое именно «вычисление» вы выполняете. Но преимущество в том, что им не нужно знать входные данные. После выполнения f(3,4) функция возвращает 12 и «доказательство». Теперь любой может взять 12, «доказательство» и убедиться, что вы перемножили КАКИЕ-ТО два числа и получили 12. Никто не узнает, использовали вы 4,3, 6,2 или даже 12,1. Возможность скрыть эти числа, сохранив проверяемость результата, и даёт «нулевое разглашение».

Зачем я это объясняю?

Эта обобщённая концепция даёт невероятно мощный способ доказать, что вы выполнили определённое вычисление и получили конкретный результат. Имея результат и доказательство, любой может проверить правильность, не выполняя вычисление на самом деле. Это применимо к ЛЮБЫМ произвольным вычислениям. Я просто перемножил два числа, но тот же подход позволяет заявить: «Я проверил эти десять подписей, и все они действительны». Это второе преимущество и одна из главных причин использовать доказательства с нулевым разглашением, даже когда ничего не нужно «скрывать». Задача, требующая 1000 шагов вычисления (или даже миллиона), сводится к проверке одного доказательства, подтверждающего правильность вычисления. Но есть нюанс: создание доказательства занимает некоторое время.

ZK Compression использует ту же технологию для выполнения логики проверки принадлежности дереву Меркла. Схема принимает данные аккаунта и доказательство (128 байт), а затем проверяет, что данные действительно входят в ончейн-«обязательство». (Фактически доказательство занимает 256 байт, но у эллиптических кривых и точек есть удобное свойство: если кривая известна, для получения второй точки достаточно одной точки.)

Главная цель — сократить размер доказательства до постоянных 128 байт. Тогда в транзакции остаётся относительно много места для данных небольших аккаунтов. Размер обычного доказательства Меркла равен Log2(N), а в ZK Compression он всегда постоянен. Поэтому одно обязательство может охватывать очень большое количество аккаунтов. (Для сравнения: доказательство Меркла для 100 000 аккаунтов занимало бы около 550 байт — половину полезной нагрузки транзакции.) 

Доказательство можно создать офчейн, но проверять его необходимо ончейн. Прежде чем разрешить выполнение, программа должна убедиться, что предоставлены правильные данные аккаунта. Для этого требуется базовый механизм проверки ZK-доказательств. Система доказательства, используемая ZK-Compression, называется Groth16. Она, в свою очередь, опирается на системный вызов alt_bn128, который сейчас доступен в mainnet только за флагом функции и проходит тестирование.

Особенно интересно, что механизм ZK-Compression позволяет проверять произвольные вычисления, а не только утверждение «принадлежит ли этот лист дереву с данным корнем?».

Одно из главных преимуществ ZK Compression — готовая инфраструктура, благодаря которой разработчику вообще не приходится работать с частью «ZK». С точки зрения разработчика это обычный аккаунт с теми же полями и другими атрибутами, поэтому внутри программы с ним можно обращаться как с обычным аккаунтом. Абстрагирование большей части «магии ZK» полезно: разработчикам не приходится разбираться с ней самостоятельно.

ZK-роллапы

Не углубляясь в детали, ZK-роллапы в основном используют те же концепции, что и ZK-Compression. Главное сходство состоит в том, что всё состояние роллапа представлено в базовом слое (Ethereum) одним корнем. Поэтому некоторые называют ZK-Compression роллапом. Но между ними есть принципиальные различия.

Рассмотрим 100 транзакций роллапа.

Весь ZK-роллап рассматривается как одна схема, подобная программе умножения из нашего примера. Все 100 транзакций проверяются — подписи, логика контрактов, отсутствие дубликатов и так далее, — после чего создаётся единое доказательство утверждения

«После применения 100 транзакций корень состояния изменяется с A на B». После проверки доказательства смарт-контракт обновляет корень состояния с A на B.

В ZK-Compression каждая из 100 транзакций содержит одно доказательство, которое лишь подтверждает, что данные аккаунта верны. При этом переходы состояния, создаваемые транзакциями, фактически выполняются ончейн самой SVM. После проверки доказательства аккаунт обрабатывается как обычный. Это критически важно для свойства компонуемости, которое мы рассмотрим далее.

Повторный анализ свойств

Теперь перейдём к самому интересному. Какие свойства Solana сохраняет ZK-Compression?

Синхронная атомарная компонуемость

Если транзакция ссылается на 2 аккаунта, сжатых с помощью ZK, и 10 «обычных» аккаунтов, компонуемость не нарушается. Инструкция, ссылающаяся на сжатый с помощью ZK аккаунт, может вызвать другую инструкцию или программу, которая обращается к «обычному» несжатому аккаунту. Это свойство полностью сохраняется, даже если два аккаунта сжаты в разных деревьях. При сбое одной инструкции вся транзакция откатывается (атомарность), а изменения из инструкции, вызванной в строке 1, видны в строке 2 (синхронность).

Для роллапов это неверно: ZK-роллапы не могут вызывать друг друга ни синхронно, ни атомарно, если только не используют глобальные блокировки и не поддерживают откат между роллапами.

Параллелизм

Эта функция влияет на параллелизм, поэтому стоит рассмотреть каждый случай:

Запись в несколько сжатых аккаунтов одного дерева

Каждое дерево само по себе поддерживает параллельную работу. Если пользователи читают данные из двух сжатых аккаунтов с одним корнем состояния или записывают в них данные, операции могут выполняться параллельно, а корень состояния — обновляться параллельно. Здесь применяется та же логика, которую Solana использует для параллельного обновления деревьев Меркла в cNFT.

Запись в один и тот же сжатый аккаунт

Один сжатый аккаунт не поддерживает параллельную запись. Если два пользователя попытаются записать данные в один сжатый аккаунт, одна из транзакций завершится с ошибкой независимо от порядка. При обычном выполнении изменения аккаунта, внесённые предыдущей инструкцией, доступны следующей инструкции. Но для аккаунтов, сжатых с помощью ZK, доказательство данных аккаунта станет недействительным, поскольку оно относится к предыдущему состоянию.

Также важно учитывать, что высокое потребление вычислительных единиц (CU) при сжатии снижает максимальный параллелизм для каждого дерева. Из-за лимита CU аккаунта каждый аккаунт может использовать не более 12 млн вычислительных единиц на блок.

Предположения о доверии

Любой может хранить все исходные данные, необходимые для создания доказательств и отправки транзакций. Однако это дополнительное предположение о доверии, которое влияет на живучесть сжатого состояния. Если данные по какой-либо причине «утеряны» или поступают с задержкой, вы не сможете отправить транзакцию, если не храните данные самостоятельно. К счастью, это так называемая задача f+1, а не 3f+1, для которой требуется византийская отказоустойчивость. Для задачи f+1 достаточно одной честной ноды, предоставляющей данные. Поскольку доказательства «самопроверяемы», проблема не затрагивает «безопасность». В основном она касается «живучести» и создаёт вектор цензуры.

И обычные роллапы, и ZK-Compression требуют предоставления доказательства валидности. Но роллапы кодируют в нём полную функцию перехода состояния, тогда как ZK-Compression кодирует только вопрос «Верны ли данные аккаунта?». Поэтому предположения о доверии немного различаются. В случае сжатия они в основном относятся к доступу к состоянию, а переход состояния выполняется полностью. В случае роллапа они относятся ко всей функции перехода состояния с точки зрения базового слоя. Предположения о безопасности, касающиеся количества битов или сложности и вычислительной неразрешимости базовой задачи, одинаковы (предположение о билинейной задаче Диффи — Хеллмана). Различается то, для чего вы полагаетесь на эту модель безопасности: для доступа к состоянию или выполнения. Я упоминаю это лишь потому, что важно понимать, где добавляются предположения о доверии, а где нет.

Сейчас программу, проверяющую сжатые с помощью ZK аккаунты, можно обновлять. В будущем её можно сделать неизменяемой или заморозить, поскольку она выполняет только узкоспециализированную операцию — открытие доказательств Меркла, — которая не требует постоянных обновлений.

Кроме того, сжатия состояния можно добиться ещё двумя способами.

  1. Сейчас разрабатывается предложение по увеличению размера транзакции (канал proj-3x-tx в Discord Solana). После его запуска можно будет использовать обычные доказательства Меркла, если позволяет размер
  2. После запуска системного вызова alt_bn128 его также можно будет использовать для обычного векторного обязательства с постоянным размером доказательства (KZG работает с любой кривой, поддерживающей спаривание, включая alt_bn128). Для этого не нужна схема ZK-доказательства

Как это называть?

К сожалению, такие термины, как роллап, L2 и Validium, применяются настолько свободно, что некоторые роллапы фактически даже не являются роллапами. Они не наследуют живучесть, безопасность и устойчивость базового слоя к цензуре. Helius обвиняли в использовании «маркетингового термина», но те же люди по той же причине — ради маркетинга — очень широко применяли слово «роллап» к проектам, в которые инвестировали. Более того, не являющимся роллапами проектам присваивают разные стадии лишь для того, чтобы они могли и дальше называть себя роллапами.

Так поступают не все. Некоторые предельно честно выступают за точную терминологию и месяцами спорят с теми, кто вводит пользователей в заблуждение неточными терминами. Отдельное спасибо Toghrul, который последовательно требует точной терминологии от *всех*.

Поскольку свойства и предположения о доверии отличаются от роллапа, такое название может запутать пользователей. Validium — тоже слишком широкое понятие. Оно не учитывает, что синхронная атомарная компонуемость и параллелизм сохраняются, доступность данных (DA) обеспечивается ончейн, а сама функция перехода состояния не требует доверия, поскольку полные ноды полностью выполняют реальные программы, а не просто проверяют доказательство валидности выполнения. Кто-то может утверждать, что ZK-доказательства не требуют доверия, но это не так. Они значительно минимизируют доверие, однако с математической точки зрения предположения о безопасности различаются. Возможно, их достаточно для 99% сценариев, но по сравнению с полной нодой возникает дополнительное предположение о доверии — например, предположение о билинейной задаче Диффи — Хеллмана для zk-SNARK на основе кривой со спариванием. Разумеется, поскольку ZK-Compression использует snark для проверки валидности самого аккаунта, справедливо сказать, что ZK-Compression требует доверия: для сжатых с помощью ZK аккаунтов есть предположения о доверии, которых нет у «обычных» аккаунтов. Таким образом, технология находится где-то между системой без необходимости доверия и полноценным ZK-роллапом.

Если по названию должны быть понятны свойства, то термин «роллап» не отражает ни эти свойства, ни предположения о доверии. Может быть, придумать новое название? ZK-Compression вполне подходит, если предположения о доверии сформулированы ясно.

Подпишитесь на Helius

Следите за новостями разработки на Solana и получайте уведомления о наших публикациях