Более глубокое погружение в кросс-L2 чтение для кошельков и других вариантов использования

ПродвинутыйFeb 29, 2024
В этой статье Виталик непосредственно рассматривает конкретный технический аспект подпроблемы: как легче читать с L2 на L1, с L1 на L2 или с одного L2 на другой L2. Решение этой проблемы очень важно для создания архитектуры разделения активов/ключей, но оно также имеет ценные примеры использования в других областях, в частности, для оптимизации надежных кросс-L2 вызовов, включая такие случаи использования, как перемещение активов между L1 и L2.
Более глубокое погружение в кросс-L2 чтение для кошельков и других вариантов использования

Особая благодарность Йоаву Вайсу, Дэну Финлею, Мартину Коппельману, а также командам Arbitrum, Optimism, Polygon, Scroll и SoulWallet за отзывы и рецензии.

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

Эта заметка будет посвящена непосредственно техническим аспектам одной конкретной подпроблемы: как облегчить чтение L1 с L2, L2 с L1 или L2 с другого L2. Решение этой проблемы очень важно для реализации архитектуры разделения активов и хранилищ ключей, но оно также имеет ценное применение в других областях, в частности, для оптимизации надежных кросс-L2 вызовов, включая такие случаи, как перемещение активов между L1 и L2.

Рекомендуемое предварительное чтение

Оглавление

Какова цель?

Когда языки L2 станут более популярными, пользователи будут иметь активы на нескольких языках L2, а возможно, и на L1. Как только кошельки смарт-контрактов (мультисигма, социальное восстановление или другие) станут общепринятыми, ключи, необходимые для доступа к какому-либо аккаунту, со временем будут меняться, и старые ключи должны будут перестать быть действительными. Когда произойдут оба этих события, пользователю понадобится способ сменить ключи, имеющие право доступа ко многим учетным записям, которые находятся в разных местах, не совершая при этом чрезвычайно большого количества транзакций.

В частности, нам нужен способ работы с контрфактическими адресами: адресами, которые еще никак не были "зарегистрированы" в цепи, но которым, тем не менее, необходимо получать и надежно хранить средства. Мы все зависим от контрфактических адресов: когда Вы впервые используете Ethereum, Вы можете сгенерировать ETH-адрес, который кто-то может использовать, чтобы заплатить Вам, без "регистрации" адреса на цепочке (для этого нужно заплатить txfees, а значит, уже иметь некоторое количество ETH).

При использовании EOA все адреса начинаются как контрфактические адреса. В кошельках со смарт-контрактами контрфактические адреса все еще возможны, во многом благодаря CREATE2, который позволяет Вам иметь ETH-адрес, который может быть заполнен только смарт-контрактом с кодом, соответствующим определенному хэшу.

Алгоритм расчета адреса EIP-1014 (CREATE2).

Однако кошельки смарт-контрактов создают новую проблему: возможность смены ключей доступа. Адрес, который является хэшем иниткода, может содержать только начальный ключ проверки кошелька. Текущий ключ проверки будет храниться в хранилище кошелька, но эта запись не будет волшебным образом распространяться на другие L2.

Если у пользователя много адресов на многих L2, включая адреса, о которых (поскольку они контрфактические) L2, на котором он находится, не знает, то кажется, что есть только один способ позволить пользователям менять свои ключи: архитектура разделения активов и хранилищ ключей. У каждого пользователя есть (i) "контракт keystore" (на L1 или на одном конкретном L2), который хранит ключ проверки для всех кошельков вместе с правилами изменения ключа, и (ii) "контракты кошельков" на L1 и многих L2, которые считывают межцепочечные данные, чтобы получить ключ проверки.

Есть два способа реализовать это:

  • Облегченная версия (проверка только для обновления ключей): каждый кошелек хранит ключ проверки локально и содержит функцию, которая может быть вызвана для проверки межцепочечного доказательства текущего состояния хранилища ключей и обновления своего локально хранящегося ключа проверки до соответствия. Когда кошелек используется в первый раз на определенном L2, вызов этой функции для получения текущего ключа проверки из хранилища ключей является обязательным.
    • Положительные стороны: экономно использует доказательства кросс-цепочек, так что это не страшно, если доказательства кросс-цепочек стоят дорого. Все средства можно потратить только с помощью текущих ключей, так что это по-прежнему безопасно.
    • Недостатки: Чтобы изменить ключ проверки, необходимо произвести смену ключа на цепочке как в хранилище ключей, так и в каждом уже инициализированном кошельке (хотя и не в контрфактическом). Это может стоить много бензина.
  • Тяжелая версия (проверка каждого tx): для каждой транзакции необходимо межцепочечное доказательство, показывающее ключ, который в данный момент находится в хранилище ключей.
    • Плюсы: меньше системных сложностей, а обновление хранилища ключей обходится недорого.
    • Недостатки: дороговизна в пересчете на один канал, поэтому требуется гораздо больше инженерных решений, чтобы сделать доказательства кросс-цепочек приемлемо дешевыми. Также нелегко совместить с ERC-4337, который в настоящее время не поддерживает кросс-контрактное чтение мутабельных объектов во время валидации.

Как выглядит доказательство кросс-цепочки?

Чтобы показать всю сложность, мы рассмотрим самый сложный случай: когда хранилище ключей находится на одном L2, а кошелек - на другом L2. Если либо хранилище ключей, либо кошелек находятся на L1, то потребуется только половина этой конструкции.

Предположим, что хранилище ключей находится на Linea, а кошелек - на Kakarot. Полное доказательство ключей от кошелька состоит из:

  • Доказательство, доказывающее текущий корень состояния Linea, учитывая текущий корень состояния Ethereum, о котором знает Kakarot
  • Доказательство, подтверждающее текущие ключи в хранилище ключей, учитывая текущее состояние корня Linea

Здесь есть два основных непростых вопроса, связанных с реализацией:

  1. Какие доказательства мы используем? (Это доказательства Меркла? что-то еще?)
  2. Как L2 вообще узнает недавний корень состояния L1 (Ethereum) (или, как мы увидим, потенциально полное состояние L1)? И в качестве альтернативы, как L1 узнает государственный корень L2?
    • В обоих случаях, насколько велика задержка между тем, как что-то происходит на одной стороне, и тем, как это можно доказать другой стороне?

Какие схемы доказательства мы можем использовать?

Существует пять основных вариантов:

  • Доказательства Меркла
  • ZK-SNARK общего назначения
  • Доказательства специального назначения (например. с KZG)
  • Доказательства Веркля, которые находятся где-то между KZG и ZK-SNARK как по нагрузке на инфраструктуру, так и по стоимости.
  • Нет доказательств и полагайтесь на прямое чтение состояния

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

"Агрегация" относится к идее объединения всех доказательств, предоставленных пользователями в рамках каждого блока, в большое мета-доказательство, которое объединяет их все. Это возможно для SNARK и для KZG, но не для ветвей Меркла (Вы можете немного объединить ветви Меркла, но это сэкономит Вам только log(txs per block) / log(total number of keystores), возможно, 15-30% на практике, так что это, вероятно, не стоит затрат).

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

Как работают доказательства Меркла?

Здесь все просто: следуйте схеме, приведенной в предыдущем разделе. Точнее, каждое "доказательство" (предполагая максимально сложный случай доказательства одного L2 в другой L2) будет содержать:

  • Ветвь Меркле, доказывающая корень состояния хранилища ключей L2, учитывая самый последний корень состояния Ethereum, о котором известно L2. Корень состояния хранилища ключей L2 хранится в известном слоте памяти по известному адресу (контракт на L1, представляющий L2), и поэтому путь по дереву может быть жестко закодирован.
  • Ветвь Меркла, доказывающая текущие ключи проверки, учитывая корень состояния хранилища ключей L2. Здесь снова ключ проверки хранится в известном слоте памяти по известному адресу, поэтому путь может быть закодирован.

К сожалению, доказательства состояния в Ethereum сложны, но существуют библиотеки для их проверки, и если Вы используете эти библиотеки, то реализовать этот механизм не так уж сложно.

Более серьезная проблема - это стоимость. Доказательства Меркла длинные, а деревья Patricia, к сожалению, в ~3,9 раза длиннее, чем нужно (точно: идеальное доказательство Меркла для дерева, содержащего N объектов, имеет длину 32 log2(N) байта, а поскольку деревья Patricia в Ethereum имеют 16 листьев на каждого ребенка, доказательства для этих деревьев имеют длину 32 15 log16(N) ~= 125 log2(N) байт). В государстве с примерно 250 миллионами (~2²⁸) учетных записей, это делает каждое доказательство 125 * 28 = 3500 байт, или около 56,000 газа, плюс дополнительные затраты на декодирование и проверку хэшей.

Два доказательства вместе будут стоить около 100 000-150 000 газовых единиц (не считая проверки подписи, если она используется для каждой транзакции) - значительно больше, чем текущие базовые 21 000 газовых единиц за транзакцию. Но разница становится еще больше, если доказательство проверяется на L2. Вычисления внутри L2 дешевы, потому что вычисления выполняются вне цепи и в экосистеме с гораздо меньшим количеством узлов, чем в L1. Данные, с другой стороны, должны быть размещены на L1. Таким образом, сравнение не 21000 газа против 150 000 газа; это 21 000 газа L2 против 100 000 газа L1.

Мы можем рассчитать, что это значит, если сравним расходы на газ в L1 и L2:

В настоящее время L1 примерно в 15-25 раз дороже L2 при простой отправке и в 20-50 раз дороже при обмене маркерами. Простая отправка требует относительно много данных, но обмен требует гораздо больше вычислений. Таким образом, свопы являются лучшим эталоном для приблизительной оценки стоимости вычислений L1 по сравнению с вычислениями L2. Учитывая все это, если мы предположим 30-кратное соотношение между стоимостью вычислений L1 и стоимостью вычислений L2, то это означает, что создание доказательства Меркла на L2 будет стоить эквивалентно, возможно, пятидесяти обычным транзакциям.

Конечно, использование двоичного дерева Меркла может сократить затраты в ~4 раза, но даже в этом случае в большинстве случаев затраты будут слишком высокими - и если мы готовы пожертвовать тем, что больше не будем совместимы с текущим гексарическим деревом состояний Ethereum, мы можем поискать еще лучшие варианты.

Как работают доказательства ZK-SNARK?

Концептуально использование ZK-SNARK также легко понять: Вы просто заменяете доказательства Меркле на диаграмме выше на ZK-SNARK, доказывающий, что эти доказательства Меркле существуют. ZK-SNARK стоит ~400,000 газа вычислений и около 400 байт (сравните: 21,000 газа и 100 байт для базовой транзакции, в будущем сокращаемой до ~25 байт при сжатии). Таким образом, с точки зрения вычислений, ZK-SNARK стоит в 19 раз больше, чем базовая транзакция сегодня, а с точки зрения данных, ZK-SNARK стоит в 4 раза больше, чем базовая транзакция сегодня, и в 16 раз больше, чем базовая транзакция может стоить в будущем.

Эти цифры значительно превосходят доказательства Меркла, но они все еще довольно дороги. Есть два способа улучшить эту ситуацию: (i) специальные KZG-доказательства или (ii) агрегирование, похожее на агрегирование ERC-4337, но с использованием более причудливой математики. Мы можем рассмотреть оба варианта.

Как будут работать специальные доказательства KZG?

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

Сначала напомним, как работают обязательства KZG:

  • Мы можем представить набор данных [D_1 ... D_n] с помощью KZG-доказательства многочлена, полученного из этих данных: а именно, многочлена P, где P(w) = D_1, P(w²) = D_2 ... P(wⁿ) = D_n. Здесь w - это "корень из единства", значение, при котором wᴺ = 1 для некоторого размера области оценки N (все это делается в конечном поле).
  • Чтобы "зафиксироваться" на P, мы создаем точку эллиптической кривой com(P) = P₀ G + P₁ S₁ + ... + Pₖ * Sₖ. Здесь:
    • G - точка генератора кривой
    • Pᵢ - это коэффициент i-й степени полинома P
    • Sᵢ - i-ая точка в доверенной установке.
  • Чтобы доказать P(z) = a, мы создадим многочлен Q = (P - a) / (X - z), и создадим обязательство com(Q) для него. Создать такой многочлен можно только в том случае, если P(z) действительно равно a.
  • Чтобы проверить доказательство, мы проверяем уравнение Q * (X - z) = P - a, выполняя проверку эллиптической кривой для доказательства com(Q) и полиномиального обязательства com(P): мы проверяем e(com(Q), com(X - z)) ?= e(com(P) - com(a), com(1))

Некоторые ключевые свойства, которые важно понимать, таковы:

  • Доказательство - это просто значение com(Q), которое составляет 48 байт.
  • com(P₁) + com(P₂) = com(P₁ + P₂)
  • Это также означает, что Вы можете "редактировать" значение в существующем обязательстве. Предположим, что мы знаем, что D_i в настоящее время a, мы хотим установить его на b, и существующее обязательство D - com(P). Если Вы хотите выполнить "P, но с P(wⁱ) = b, и никакие другие оценки не изменились", то мы зададим com(new_P) = com(P) + (b-a) * com(Lᵢ), где Lᵢ - это "полином Лагранжа", который равен 1 в точке wⁱ и 0 в других точках wʲ.
  • Чтобы эффективно выполнять эти обновления, все N обязательств по полиномам Лагранжа (com(Lᵢ)) могут быть предварительно вычислены и сохранены каждым клиентом. Внутри контракта на цепочке может оказаться слишком накладно хранить все N обязательств, поэтому вместо этого Вы можете сделать KZG-обязательство на набор значений com(L_i) (или hash(com(L_i)), так что всякий раз, когда кому-то нужно обновить дерево на цепочке, он может просто предоставить соответствующее com(L_i) с доказательством его корректности.

Таким образом, у нас есть структура, в которой мы можем просто продолжать добавлять значения в конец постоянно растущего списка, хотя и с определенным ограничением по размеру (в реальности, сотни миллионов могут быть жизнеспособными). Затем мы используем эту структуру данных для управления (i) обязательствами по списку ключей на каждом L2, которые хранятся на этом L2 и зеркально отражаются на L1, и (ii) обязательствами по списку обязательств по ключам L2, которые хранятся на Ethereum L1 и зеркально отражаются на каждом L2.

Обновление обязательств может стать частью логики ядра L2, либо может быть реализовано без изменений протокола ядра L2 с помощью мостов ввода и вывода средств.

Таким образом, для полного доказательства потребуется:

  • Последний com (список ключей) на хранилище ключей L2 (48 байт)
  • KZG-доказательство того, что com(key list) является значением внутри com(mirror_list), обязательство по списку всех комментариев к списку ключей (48 байт)
  • KZG доказательство Вашего ключа в com(key list) (48 байт, плюс 4 байта для индекса)

На самом деле можно объединить два доказательства KZG в одно, и тогда общий размер доказательства составит всего 100 байт.

Обратите внимание на одну тонкость: поскольку список ключей - это список, а не карта ключ/значение, как в случае с состоянием, список ключей должен присваивать позиции последовательно. Контракт на предоставление ключей будет содержать свой собственный внутренний реестр, сопоставляющий каждое хранилище ключей с идентификатором, и для каждого ключа он будет хранить не просто ключ, а хэш(ключ, адрес хранилища), чтобы однозначно сообщить другим L2, о каком хранилище ключей идет речь в конкретной записи.

Плюсом этой техники является то, что она очень хорошо работает на L2. Объем данных составляет 100 байт, что в ~4 раза короче, чем ZK-SNARK, и в разы короче, чем доказательство Меркла. Затраты на вычисления составляют в основном один парный чек размера 2, или около 119 000 газов. На L1 данные менее важны, чем вычисления, и поэтому, к сожалению, KZG несколько дороже, чем доказательства Меркла.

Как работают деревья Веркле?

Деревья Веркле, по сути, представляют собой наложение друг на друга обязательств KZG (или обязательств IPA, которые могут быть более эффективными и использовать более простую криптографию): чтобы хранить 2⁴⁸ значений, Вы можете взять на себя обязательства KZG по списку из 2²⁴ значений, каждое из которых само является обязательством KZG по 2²⁴ значениям. Деревья веркле <a href="https://notes.ethereum.org/@vbuterin/verkle_tree_eip"> сильно Рассматривается для дерева состояний Ethereum, потому что деревья Веркле можно использовать для хранения карт ключей-значений, а не только списков (в принципе, Вы можете создать дерево размером 2²⁵⁶, но начать его пустым, заполняя определенные части дерева только тогда, когда Вам действительно нужно их заполнить).

Как выглядит дерево Веркле. На практике Вы можете задать каждому узлу ширину 256 == 2⁸ для деревьев на основе IPA или 2²⁴ для деревьев на основе KZG.

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

В реальности деревья Веркле следует рассматривать как деревья Меркле, но они более жизнеспособны без SNARKing (из-за меньшей стоимости данных) и дешевле с SNARKing (из-за меньшей стоимости провера).

Самое большое преимущество деревьев Веркле - это возможность унификации структур данных: Доказательства Веркле можно использовать непосредственно над состоянием L1 или L2, без оверлейных структур и с использованием совершенно одинакового механизма для L1 и L2. Когда квантовые компьютеры станут проблемой, или когда доказательство ветвей Меркле станет достаточно эффективным, деревья Веркле можно будет заменить двоичным хэш-деревом с подходящей хэш-функцией, удобной для SNARK.

Агрегация

Если N пользователей совершают N транзакций (или, что более реалистично, N ERC-4337 UserOperations), которые должны доказать N межцепочечных утверждений, мы можем сэкономить много газа, агрегируя эти доказательства: сборщик, который будет объединять эти транзакции в блок или пакет, входящий в блок, может создать одно доказательство, которое докажет все эти утверждения одновременно.

Это может означать:

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

Если используются ZK-SNARK, то основные предельные затраты - это просто "бизнес-логика" передачи чисел между контрактами, так что, возможно, несколько тысяч L2 газа на пользователя. Если используются мультизащиты KZG, проверяющему придется добавить 48 газа на каждый L2, хранящий ключи, который используется в данном блоке, поэтому предельные затраты схемы на одного пользователя добавят еще ~800 газа L1 на каждый L2 (не на одного пользователя). Но эти затраты гораздо ниже, чем затраты на отказ от агрегации, которые неизбежно влекут за собой более 10 000 L1-газов и сотни тысяч L2-газов на одного пользователя. Для деревьев Веркле Вы можете либо использовать мультидоказательства Веркле напрямую, что добавит около 100-200 байт на пользователя, либо сделать ZK-SNARK мультидоказательства Веркле, которое имеет такие же затраты, как и ZK-SNARK ветвей Меркле, но доказывать его значительно дешевле.

С точки зрения реализации, вероятно, лучше всего, чтобы бандлеры агрегировали межцепочечные доказательства через стандарт абстракции счетов ERC-4337. В ERC-4337 уже есть механизм для разработчиков, позволяющий объединять части UserOperations пользовательскими способами. Существует даже <a href="https://hackmd.io/@voltrevo/BJ0QBy3zi"> реализация этого для агрегации подписей BLS, которая может снизить расходы на газ для L2 в 1,5-3 раза в зависимости от того, какие другие формы сжатия включены.

Диаграмма из поста <a href="https://hackmd.io/@voltrevo/BJ0QBy3zi"> BLS wallet implementation post, показывающая процесс работы с агрегатными подписями BLS в рамках ранней версии ERC-4337. Рабочий процесс объединения межцепочечных доказательств, скорее всего, будет выглядеть очень похоже.

Прямое считывание состояния

Последняя возможность, которая подходит только для L2, читающего L1 (а не для L1, читающего L2), - это модифицировать L2, чтобы позволить им делать статические вызовы контрактов на L1 напрямую.

Это можно сделать с помощью опкода или прекомпиляции, которая позволяет вызывать L1, где Вы указываете адрес назначения, газ и calldata, и она возвращает результат, хотя, поскольку эти вызовы являются статическими, они не могут фактически изменить какое-либо состояние L1. L2 уже должны знать о L1, чтобы обрабатывать депозиты, поэтому нет ничего принципиального, что мешало бы реализовать такую вещь; это в основном техническая проблема реализации (см.: этот RFP от Optimism для поддержки статических вызовов в L1).

Обратите внимание, что если хранилище ключей находится на L1, а L2 интегрируют функциональность статических вызовов L1, то доказательств не требуется вообще! Однако если L2 не интегрируют статические вызовы L1, или если хранилище ключей находится на L2 (что, возможно, в конечном итоге придется сделать, когда L1 станет слишком дорогим для пользователей, чтобы использовать его хоть немного), то потребуются доказательства.

Как L2 узнает недавний корень состояния Ethereum?

Все описанные выше схемы требуют, чтобы L2 обращался либо к корню последнего состояния L1, либо ко всему последнему состоянию L1. К счастью, все L2 уже имеют некоторую функциональность для доступа к недавнему состоянию L1. Это связано с тем, что такая функциональность необходима им для обработки сообщений, поступающих из L1 в L2, в частности, депозитов.

И действительно, если L2 имеет функцию депозита, то Вы можете использовать этот L2 как есть для перемещения корней состояний L1 в контракт на L2: просто вызовите в контракте на L1 опкод BLOCKHASH и передайте его на L2 в качестве сообщения о депозите. Полный заголовок блока может быть получен, а его корень состояния извлечен на стороне L2. Однако было бы гораздо лучше, если бы каждый L2 имел явный способ прямого доступа либо к полному состоянию L1, либо к корням состояния L1.

Основная проблема, связанная с оптимизацией того, как L2 получают свежие корни состояния L1, заключается в одновременном достижении безопасности и низкой задержки:

  • Если L2 реализуют функцию "прямого чтения L1" ленивым способом, читая только финализированные корни состояния L1, то задержка обычно составляет 15 минут, но в экстремальном случае утечек неактивности (с которыми Вам придется мириться) задержка может составлять несколько недель.
  • L2 абсолютно точно могут быть спроектированы для чтения гораздо более поздних корней состояний L1, но поскольку L1 может возвращаться (даже при однослотовом окончании возвраты могут происходить во время утечек неактивности), L2 также должен быть способен возвращаться. Это технически сложно с точки зрения программной инженерии, но, по крайней мере, Optimism уже имеет такую возможность.
  • Если Вы используете мост депозита для перевода корней состояния L1 в состояние L2, то простая экономическая целесообразность может потребовать длительного времени между обновлениями депозита: если полная стоимость депозита составляет 100 000 газа, и мы предполагаем, что ETH стоит $1800, а комиссии составляют 200 гвеев, и корни L1 переводятся в L2 раз в день, то это будет стоить $36 на L2 в день, или $13148 на L2 в год для поддержания системы. При задержке в один час эта сумма возрастает до $315 569 на L2 в год. В лучшем случае, постоянный приток нетерпеливых богатых пользователей покрывает расходы на обновление и поддерживает систему в актуальном состоянии для всех остальных. В худшем случае, какому-нибудь альтруистичному деятелю придется заплатить за это самому.
  • "Оракулы" (по крайней мере, те технологии, которые некоторые дефи-люди называют "оракулами") не являются приемлемым решением в данном случае: управление ключами кошелька - это очень важная для безопасности низкоуровневая функциональность, и поэтому она должна зависеть максимум от нескольких частей очень простой, криптографически недоверчивой низкоуровневой инфраструктуры.

Кроме того, в обратном направлении (L1 читают L2):

  • При оптимистичном сворачивании корни штата достигают L1 за одну неделю из-за задержки доказательства мошенничества. Пока что на сворачивание ZK уходит несколько часов из-за сочетания времени на проверку и экономических ограничений, хотя будущие технологии позволят сократить это время.
  • Предварительное подтверждение (от секвенсоров, аттестователей и т.д.) не является приемлемым решением для чтения L1 на L2. Управление кошельком - это очень важная для безопасности низкоуровневая функциональность, поэтому уровень безопасности связи L2 -> L1 должен быть абсолютным: не должно быть даже возможности ввести ложный корень состояния L1, захватив набор валидаторов L2. Единственные корни состояний, которым должен доверять L1, - это корни состояний, которые были приняты как окончательные в рамках контракта L2 с L1 на удержание корней состояний.

Некоторые из этих скоростей для бездоверительных межцепочечных операций являются неприемлемо медленными для многих случаев использования Defi; для этих случаев Вам действительно нужны более быстрые мосты с более несовершенными моделями безопасности. Однако для случая обновления ключей кошелька более длительные задержки более приемлемы: Вы не задерживаете транзакции на часы, Вы задерживаете смену ключей. Вам просто придется дольше хранить старые ключи. Если Вы меняете ключи из-за того, что их крадут, то у Вас действительно есть значительный период уязвимости, но это можно смягчить, например, с помощью кошельков с функцией замораживания.

В конечном итоге, наилучшим решением, позволяющим сократить время ожидания, является оптимальная реализация L2 прямого чтения корней состояний L1, когда каждый блок L2 (или журнал вычислений корня состояний) содержит указатель на самый последний блок L1, так что если L1 вернется, L2 тоже сможет вернуться. Контракты Keystore должны быть размещены либо в mainnet, либо на L2, которые являются ZK-роликами и поэтому могут быстро перевестись на L1.

Блоки цепочки L2 могут иметь зависимости не только от предыдущих блоков L2, но и от блока L1. Если L1 возвращается, минуя такую связь, L2 тоже возвращается. Стоит отметить, что именно так должна была работать и ранняя (до Dank) версия шардинга; код см. здесь.

Насколько большая связь с Ethereum нужна другой цепочке, чтобы хранить кошельки, чьи хранилища ключей находятся на Ethereum или L2?

На удивление, не так уж и много. На самом деле, это даже не обязательно должен быть роллап: если это L3 или валидиум, то там можно держать кошельки, при условии, что Вы держите хранилища ключей либо на L1, либо на ZK роллапе. Главное, что Вам нужно, - это чтобы цепочка имела прямой доступ к корням состояний Ethereum, а также техническая и социальная готовность к перестройке, если Ethereum перестроится, и к хард-форку, если Ethereum перестроится.

Одна из интересных исследовательских проблем заключается в определении того, насколько возможно, чтобы цепочка имела такую форму связи с множеством других цепочек (например. Ethereum и Zcash). Наивный подход возможен: Ваша цепочка может согласиться на реорганизацию, если реорганизуется Ethereum или Zcash (и на хард форк, если хард форкнется Ethereum или Zcash), но тогда операторы узлов и сообщество в целом окажутся в двойной зависимости от технических и политических факторов. Таким образом, подобная техника может быть использована для подключения к нескольким другим цепочкам, но с большими затратами. Схемы, основанные на мостах ZK, обладают привлекательными техническими свойствами, но у них есть ключевой недостаток - они не устойчивы к атакам 51% или жестким вилкам. Возможно, есть и более разумные решения.

Сохранение конфиденциальности

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

  • Неизвестно, что все эти кошельки связаны друг с другом.
  • Опекуны социального восстановления не узнают, что это за адреса, которые они охраняют.

Это создает несколько проблем:

  • Мы не можем использовать доказательства Меркла напрямую, потому что они не сохраняют конфиденциальность.
  • Если мы используем KZG или SNARK, то доказательство должно предоставлять "слепую" версию ключа проверки, не раскрывая местоположение ключа проверки.
  • Если мы используем агрегацию, то агрегатор не должен узнавать местоположение открытым текстом; скорее, агрегатор должен получать доказательства вслепую и иметь возможность их агрегировать.
  • Мы не можем использовать "облегченную версию" (использовать межцепочечные доказательства только для обновления ключей), потому что это создает утечку конфиденциальности: если многие кошельки обновляются одновременно благодаря процедуре обновления, время утечки информации о том, что эти кошельки, вероятно, связаны между собой. Поэтому мы должны использовать "тяжелую версию" (межцепочечные доказательства для каждой транзакции).

В случае с SNARKs решения концептуально просты: доказательства по умолчанию скрывают информацию, и агрегатору нужно создать рекурсивный SNARK, чтобы доказать SNARKs.

Основная проблема этого подхода на сегодняшний день заключается в том, что агрегация требует от агрегатора создания рекурсивного SNARK, что в настоящее время происходит довольно медленно.

С помощью KZG мы можем использовать <a href="https://notes.ethereum.org/@vbuterin/non_index_revealing_proof"> this Работа по неиндексным доказательствам KZG (см. также: более формализованная версия этой работы в статье Caulk) в качестве отправной точки. Однако агрегирование слепых доказательств - это открытая проблема, которая требует большего внимания.

Прямое чтение L1 изнутри L2, к сожалению, не сохраняет конфиденциальность, хотя реализация функции прямого чтения все равно очень полезна, как для минимизации задержки, так и из-за ее полезности для других приложений.

Краткая информация

  • Чтобы иметь кошельки для социального восстановления, наиболее реалистичный рабочий процесс - это кошелек, который хранит хранилище ключей в одном месте, и кошельки во многих местах, где кошельки считывают хранилище ключей либо (i) для обновления своего локального представления ключа проверки, либо (ii) в процессе проверки каждой транзакции.
  • Ключевым компонентом, позволяющим сделать это возможным, являются доказательства кросс-цепочек. Нам нужно сильно оптимизировать эти доказательства. Либо ZK-SNARK, либо ожидание доказательств Веркля, либо построенное на заказ решение KZG кажутся наилучшими вариантами.
  • В долгосрочной перспективе для минимизации затрат потребуются протоколы агрегирования, в которых агрегирующие устройства генерируют агрегированные доказательства в процессе создания пакета всех UserOperations, которые были представлены пользователями. Вероятно, это следует интегрировать в экосистему ERC-4337, хотя, скорее всего, потребуется внести изменения в ERC-4337.
  • L2 должны быть оптимизированы так, чтобы минимизировать задержку чтения состояния L1 (или, по крайней мере, корня состояния) изнутри L2. L2, напрямую читающие состояние L1, идеальны и могут сэкономить место для доказательства.
  • Кошельки могут находиться не только на L2; Вы также можете разместить кошельки на системах с более низким уровнем связи с Ethereum (L3 или даже отдельные цепочки, которые согласны включать только корни состояний Ethereum и реорганизовывать или делать хардфорк, когда Ethereum реорганизует или делает хардфорк).
  • Однако хранилища ключей должны находиться либо на L1, либо на ZK-ролике L2 с высокой степенью защиты. Нахождение на L1 экономит много сложностей, хотя в долгосрочной перспективе даже это может оказаться слишком дорогим, отсюда необходимость в хранилищах ключей на L2.
  • Сохранение конфиденциальности потребует дополнительной работы и усложнит некоторые варианты. Однако, вероятно, нам все равно следует двигаться в сторону решений, сохраняющих конфиденциальность, и, по крайней мере, убедиться, что все, что мы предложим, будет совместимо с сохранением конфиденциальности.

Отказ от ответственности:

  1. Эта статья перепечатана с сайта[vitalik], Все авторские права принадлежат автору оригинала[Виталик Бутерин]. Если у Вас есть возражения против этой перепечатки, пожалуйста, свяжитесь с командой Gate Learn, и они незамедлительно рассмотрят их.
  2. Отказ от ответственности: Мнения и взгляды, выраженные в этой статье, принадлежат исключительно автору и не являются инвестиционным советом.
  3. Перевод статьи на другие языки осуществляется командой Gate Learn. Если не указано, копирование, распространение или плагиат переведенных статей запрещены.

Более глубокое погружение в кросс-L2 чтение для кошельков и других вариантов использования

ПродвинутыйFeb 29, 2024
В этой статье Виталик непосредственно рассматривает конкретный технический аспект подпроблемы: как легче читать с L2 на L1, с L1 на L2 или с одного L2 на другой L2. Решение этой проблемы очень важно для создания архитектуры разделения активов/ключей, но оно также имеет ценные примеры использования в других областях, в частности, для оптимизации надежных кросс-L2 вызовов, включая такие случаи использования, как перемещение активов между L1 и L2.
Более глубокое погружение в кросс-L2 чтение для кошельков и других вариантов использования

Особая благодарность Йоаву Вайсу, Дэну Финлею, Мартину Коппельману, а также командам Arbitrum, Optimism, Polygon, Scroll и SoulWallet за отзывы и рецензии.

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

Эта заметка будет посвящена непосредственно техническим аспектам одной конкретной подпроблемы: как облегчить чтение L1 с L2, L2 с L1 или L2 с другого L2. Решение этой проблемы очень важно для реализации архитектуры разделения активов и хранилищ ключей, но оно также имеет ценное применение в других областях, в частности, для оптимизации надежных кросс-L2 вызовов, включая такие случаи, как перемещение активов между L1 и L2.

Рекомендуемое предварительное чтение

Оглавление

Какова цель?

Когда языки L2 станут более популярными, пользователи будут иметь активы на нескольких языках L2, а возможно, и на L1. Как только кошельки смарт-контрактов (мультисигма, социальное восстановление или другие) станут общепринятыми, ключи, необходимые для доступа к какому-либо аккаунту, со временем будут меняться, и старые ключи должны будут перестать быть действительными. Когда произойдут оба этих события, пользователю понадобится способ сменить ключи, имеющие право доступа ко многим учетным записям, которые находятся в разных местах, не совершая при этом чрезвычайно большого количества транзакций.

В частности, нам нужен способ работы с контрфактическими адресами: адресами, которые еще никак не были "зарегистрированы" в цепи, но которым, тем не менее, необходимо получать и надежно хранить средства. Мы все зависим от контрфактических адресов: когда Вы впервые используете Ethereum, Вы можете сгенерировать ETH-адрес, который кто-то может использовать, чтобы заплатить Вам, без "регистрации" адреса на цепочке (для этого нужно заплатить txfees, а значит, уже иметь некоторое количество ETH).

При использовании EOA все адреса начинаются как контрфактические адреса. В кошельках со смарт-контрактами контрфактические адреса все еще возможны, во многом благодаря CREATE2, который позволяет Вам иметь ETH-адрес, который может быть заполнен только смарт-контрактом с кодом, соответствующим определенному хэшу.

Алгоритм расчета адреса EIP-1014 (CREATE2).

Однако кошельки смарт-контрактов создают новую проблему: возможность смены ключей доступа. Адрес, который является хэшем иниткода, может содержать только начальный ключ проверки кошелька. Текущий ключ проверки будет храниться в хранилище кошелька, но эта запись не будет волшебным образом распространяться на другие L2.

Если у пользователя много адресов на многих L2, включая адреса, о которых (поскольку они контрфактические) L2, на котором он находится, не знает, то кажется, что есть только один способ позволить пользователям менять свои ключи: архитектура разделения активов и хранилищ ключей. У каждого пользователя есть (i) "контракт keystore" (на L1 или на одном конкретном L2), который хранит ключ проверки для всех кошельков вместе с правилами изменения ключа, и (ii) "контракты кошельков" на L1 и многих L2, которые считывают межцепочечные данные, чтобы получить ключ проверки.

Есть два способа реализовать это:

  • Облегченная версия (проверка только для обновления ключей): каждый кошелек хранит ключ проверки локально и содержит функцию, которая может быть вызвана для проверки межцепочечного доказательства текущего состояния хранилища ключей и обновления своего локально хранящегося ключа проверки до соответствия. Когда кошелек используется в первый раз на определенном L2, вызов этой функции для получения текущего ключа проверки из хранилища ключей является обязательным.
    • Положительные стороны: экономно использует доказательства кросс-цепочек, так что это не страшно, если доказательства кросс-цепочек стоят дорого. Все средства можно потратить только с помощью текущих ключей, так что это по-прежнему безопасно.
    • Недостатки: Чтобы изменить ключ проверки, необходимо произвести смену ключа на цепочке как в хранилище ключей, так и в каждом уже инициализированном кошельке (хотя и не в контрфактическом). Это может стоить много бензина.
  • Тяжелая версия (проверка каждого tx): для каждой транзакции необходимо межцепочечное доказательство, показывающее ключ, который в данный момент находится в хранилище ключей.
    • Плюсы: меньше системных сложностей, а обновление хранилища ключей обходится недорого.
    • Недостатки: дороговизна в пересчете на один канал, поэтому требуется гораздо больше инженерных решений, чтобы сделать доказательства кросс-цепочек приемлемо дешевыми. Также нелегко совместить с ERC-4337, который в настоящее время не поддерживает кросс-контрактное чтение мутабельных объектов во время валидации.

Как выглядит доказательство кросс-цепочки?

Чтобы показать всю сложность, мы рассмотрим самый сложный случай: когда хранилище ключей находится на одном L2, а кошелек - на другом L2. Если либо хранилище ключей, либо кошелек находятся на L1, то потребуется только половина этой конструкции.

Предположим, что хранилище ключей находится на Linea, а кошелек - на Kakarot. Полное доказательство ключей от кошелька состоит из:

  • Доказательство, доказывающее текущий корень состояния Linea, учитывая текущий корень состояния Ethereum, о котором знает Kakarot
  • Доказательство, подтверждающее текущие ключи в хранилище ключей, учитывая текущее состояние корня Linea

Здесь есть два основных непростых вопроса, связанных с реализацией:

  1. Какие доказательства мы используем? (Это доказательства Меркла? что-то еще?)
  2. Как L2 вообще узнает недавний корень состояния L1 (Ethereum) (или, как мы увидим, потенциально полное состояние L1)? И в качестве альтернативы, как L1 узнает государственный корень L2?
    • В обоих случаях, насколько велика задержка между тем, как что-то происходит на одной стороне, и тем, как это можно доказать другой стороне?

Какие схемы доказательства мы можем использовать?

Существует пять основных вариантов:

  • Доказательства Меркла
  • ZK-SNARK общего назначения
  • Доказательства специального назначения (например. с KZG)
  • Доказательства Веркля, которые находятся где-то между KZG и ZK-SNARK как по нагрузке на инфраструктуру, так и по стоимости.
  • Нет доказательств и полагайтесь на прямое чтение состояния

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

"Агрегация" относится к идее объединения всех доказательств, предоставленных пользователями в рамках каждого блока, в большое мета-доказательство, которое объединяет их все. Это возможно для SNARK и для KZG, но не для ветвей Меркла (Вы можете немного объединить ветви Меркла, но это сэкономит Вам только log(txs per block) / log(total number of keystores), возможно, 15-30% на практике, так что это, вероятно, не стоит затрат).

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

Как работают доказательства Меркла?

Здесь все просто: следуйте схеме, приведенной в предыдущем разделе. Точнее, каждое "доказательство" (предполагая максимально сложный случай доказательства одного L2 в другой L2) будет содержать:

  • Ветвь Меркле, доказывающая корень состояния хранилища ключей L2, учитывая самый последний корень состояния Ethereum, о котором известно L2. Корень состояния хранилища ключей L2 хранится в известном слоте памяти по известному адресу (контракт на L1, представляющий L2), и поэтому путь по дереву может быть жестко закодирован.
  • Ветвь Меркла, доказывающая текущие ключи проверки, учитывая корень состояния хранилища ключей L2. Здесь снова ключ проверки хранится в известном слоте памяти по известному адресу, поэтому путь может быть закодирован.

К сожалению, доказательства состояния в Ethereum сложны, но существуют библиотеки для их проверки, и если Вы используете эти библиотеки, то реализовать этот механизм не так уж сложно.

Более серьезная проблема - это стоимость. Доказательства Меркла длинные, а деревья Patricia, к сожалению, в ~3,9 раза длиннее, чем нужно (точно: идеальное доказательство Меркла для дерева, содержащего N объектов, имеет длину 32 log2(N) байта, а поскольку деревья Patricia в Ethereum имеют 16 листьев на каждого ребенка, доказательства для этих деревьев имеют длину 32 15 log16(N) ~= 125 log2(N) байт). В государстве с примерно 250 миллионами (~2²⁸) учетных записей, это делает каждое доказательство 125 * 28 = 3500 байт, или около 56,000 газа, плюс дополнительные затраты на декодирование и проверку хэшей.

Два доказательства вместе будут стоить около 100 000-150 000 газовых единиц (не считая проверки подписи, если она используется для каждой транзакции) - значительно больше, чем текущие базовые 21 000 газовых единиц за транзакцию. Но разница становится еще больше, если доказательство проверяется на L2. Вычисления внутри L2 дешевы, потому что вычисления выполняются вне цепи и в экосистеме с гораздо меньшим количеством узлов, чем в L1. Данные, с другой стороны, должны быть размещены на L1. Таким образом, сравнение не 21000 газа против 150 000 газа; это 21 000 газа L2 против 100 000 газа L1.

Мы можем рассчитать, что это значит, если сравним расходы на газ в L1 и L2:

В настоящее время L1 примерно в 15-25 раз дороже L2 при простой отправке и в 20-50 раз дороже при обмене маркерами. Простая отправка требует относительно много данных, но обмен требует гораздо больше вычислений. Таким образом, свопы являются лучшим эталоном для приблизительной оценки стоимости вычислений L1 по сравнению с вычислениями L2. Учитывая все это, если мы предположим 30-кратное соотношение между стоимостью вычислений L1 и стоимостью вычислений L2, то это означает, что создание доказательства Меркла на L2 будет стоить эквивалентно, возможно, пятидесяти обычным транзакциям.

Конечно, использование двоичного дерева Меркла может сократить затраты в ~4 раза, но даже в этом случае в большинстве случаев затраты будут слишком высокими - и если мы готовы пожертвовать тем, что больше не будем совместимы с текущим гексарическим деревом состояний Ethereum, мы можем поискать еще лучшие варианты.

Как работают доказательства ZK-SNARK?

Концептуально использование ZK-SNARK также легко понять: Вы просто заменяете доказательства Меркле на диаграмме выше на ZK-SNARK, доказывающий, что эти доказательства Меркле существуют. ZK-SNARK стоит ~400,000 газа вычислений и около 400 байт (сравните: 21,000 газа и 100 байт для базовой транзакции, в будущем сокращаемой до ~25 байт при сжатии). Таким образом, с точки зрения вычислений, ZK-SNARK стоит в 19 раз больше, чем базовая транзакция сегодня, а с точки зрения данных, ZK-SNARK стоит в 4 раза больше, чем базовая транзакция сегодня, и в 16 раз больше, чем базовая транзакция может стоить в будущем.

Эти цифры значительно превосходят доказательства Меркла, но они все еще довольно дороги. Есть два способа улучшить эту ситуацию: (i) специальные KZG-доказательства или (ii) агрегирование, похожее на агрегирование ERC-4337, но с использованием более причудливой математики. Мы можем рассмотреть оба варианта.

Как будут работать специальные доказательства KZG?

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

Сначала напомним, как работают обязательства KZG:

  • Мы можем представить набор данных [D_1 ... D_n] с помощью KZG-доказательства многочлена, полученного из этих данных: а именно, многочлена P, где P(w) = D_1, P(w²) = D_2 ... P(wⁿ) = D_n. Здесь w - это "корень из единства", значение, при котором wᴺ = 1 для некоторого размера области оценки N (все это делается в конечном поле).
  • Чтобы "зафиксироваться" на P, мы создаем точку эллиптической кривой com(P) = P₀ G + P₁ S₁ + ... + Pₖ * Sₖ. Здесь:
    • G - точка генератора кривой
    • Pᵢ - это коэффициент i-й степени полинома P
    • Sᵢ - i-ая точка в доверенной установке.
  • Чтобы доказать P(z) = a, мы создадим многочлен Q = (P - a) / (X - z), и создадим обязательство com(Q) для него. Создать такой многочлен можно только в том случае, если P(z) действительно равно a.
  • Чтобы проверить доказательство, мы проверяем уравнение Q * (X - z) = P - a, выполняя проверку эллиптической кривой для доказательства com(Q) и полиномиального обязательства com(P): мы проверяем e(com(Q), com(X - z)) ?= e(com(P) - com(a), com(1))

Некоторые ключевые свойства, которые важно понимать, таковы:

  • Доказательство - это просто значение com(Q), которое составляет 48 байт.
  • com(P₁) + com(P₂) = com(P₁ + P₂)
  • Это также означает, что Вы можете "редактировать" значение в существующем обязательстве. Предположим, что мы знаем, что D_i в настоящее время a, мы хотим установить его на b, и существующее обязательство D - com(P). Если Вы хотите выполнить "P, но с P(wⁱ) = b, и никакие другие оценки не изменились", то мы зададим com(new_P) = com(P) + (b-a) * com(Lᵢ), где Lᵢ - это "полином Лагранжа", который равен 1 в точке wⁱ и 0 в других точках wʲ.
  • Чтобы эффективно выполнять эти обновления, все N обязательств по полиномам Лагранжа (com(Lᵢ)) могут быть предварительно вычислены и сохранены каждым клиентом. Внутри контракта на цепочке может оказаться слишком накладно хранить все N обязательств, поэтому вместо этого Вы можете сделать KZG-обязательство на набор значений com(L_i) (или hash(com(L_i)), так что всякий раз, когда кому-то нужно обновить дерево на цепочке, он может просто предоставить соответствующее com(L_i) с доказательством его корректности.

Таким образом, у нас есть структура, в которой мы можем просто продолжать добавлять значения в конец постоянно растущего списка, хотя и с определенным ограничением по размеру (в реальности, сотни миллионов могут быть жизнеспособными). Затем мы используем эту структуру данных для управления (i) обязательствами по списку ключей на каждом L2, которые хранятся на этом L2 и зеркально отражаются на L1, и (ii) обязательствами по списку обязательств по ключам L2, которые хранятся на Ethereum L1 и зеркально отражаются на каждом L2.

Обновление обязательств может стать частью логики ядра L2, либо может быть реализовано без изменений протокола ядра L2 с помощью мостов ввода и вывода средств.

Таким образом, для полного доказательства потребуется:

  • Последний com (список ключей) на хранилище ключей L2 (48 байт)
  • KZG-доказательство того, что com(key list) является значением внутри com(mirror_list), обязательство по списку всех комментариев к списку ключей (48 байт)
  • KZG доказательство Вашего ключа в com(key list) (48 байт, плюс 4 байта для индекса)

На самом деле можно объединить два доказательства KZG в одно, и тогда общий размер доказательства составит всего 100 байт.

Обратите внимание на одну тонкость: поскольку список ключей - это список, а не карта ключ/значение, как в случае с состоянием, список ключей должен присваивать позиции последовательно. Контракт на предоставление ключей будет содержать свой собственный внутренний реестр, сопоставляющий каждое хранилище ключей с идентификатором, и для каждого ключа он будет хранить не просто ключ, а хэш(ключ, адрес хранилища), чтобы однозначно сообщить другим L2, о каком хранилище ключей идет речь в конкретной записи.

Плюсом этой техники является то, что она очень хорошо работает на L2. Объем данных составляет 100 байт, что в ~4 раза короче, чем ZK-SNARK, и в разы короче, чем доказательство Меркла. Затраты на вычисления составляют в основном один парный чек размера 2, или около 119 000 газов. На L1 данные менее важны, чем вычисления, и поэтому, к сожалению, KZG несколько дороже, чем доказательства Меркла.

Как работают деревья Веркле?

Деревья Веркле, по сути, представляют собой наложение друг на друга обязательств KZG (или обязательств IPA, которые могут быть более эффективными и использовать более простую криптографию): чтобы хранить 2⁴⁸ значений, Вы можете взять на себя обязательства KZG по списку из 2²⁴ значений, каждое из которых само является обязательством KZG по 2²⁴ значениям. Деревья веркле <a href="https://notes.ethereum.org/@vbuterin/verkle_tree_eip"> сильно Рассматривается для дерева состояний Ethereum, потому что деревья Веркле можно использовать для хранения карт ключей-значений, а не только списков (в принципе, Вы можете создать дерево размером 2²⁵⁶, но начать его пустым, заполняя определенные части дерева только тогда, когда Вам действительно нужно их заполнить).

Как выглядит дерево Веркле. На практике Вы можете задать каждому узлу ширину 256 == 2⁸ для деревьев на основе IPA или 2²⁴ для деревьев на основе KZG.

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

В реальности деревья Веркле следует рассматривать как деревья Меркле, но они более жизнеспособны без SNARKing (из-за меньшей стоимости данных) и дешевле с SNARKing (из-за меньшей стоимости провера).

Самое большое преимущество деревьев Веркле - это возможность унификации структур данных: Доказательства Веркле можно использовать непосредственно над состоянием L1 или L2, без оверлейных структур и с использованием совершенно одинакового механизма для L1 и L2. Когда квантовые компьютеры станут проблемой, или когда доказательство ветвей Меркле станет достаточно эффективным, деревья Веркле можно будет заменить двоичным хэш-деревом с подходящей хэш-функцией, удобной для SNARK.

Агрегация

Если N пользователей совершают N транзакций (или, что более реалистично, N ERC-4337 UserOperations), которые должны доказать N межцепочечных утверждений, мы можем сэкономить много газа, агрегируя эти доказательства: сборщик, который будет объединять эти транзакции в блок или пакет, входящий в блок, может создать одно доказательство, которое докажет все эти утверждения одновременно.

Это может означать:

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

Если используются ZK-SNARK, то основные предельные затраты - это просто "бизнес-логика" передачи чисел между контрактами, так что, возможно, несколько тысяч L2 газа на пользователя. Если используются мультизащиты KZG, проверяющему придется добавить 48 газа на каждый L2, хранящий ключи, который используется в данном блоке, поэтому предельные затраты схемы на одного пользователя добавят еще ~800 газа L1 на каждый L2 (не на одного пользователя). Но эти затраты гораздо ниже, чем затраты на отказ от агрегации, которые неизбежно влекут за собой более 10 000 L1-газов и сотни тысяч L2-газов на одного пользователя. Для деревьев Веркле Вы можете либо использовать мультидоказательства Веркле напрямую, что добавит около 100-200 байт на пользователя, либо сделать ZK-SNARK мультидоказательства Веркле, которое имеет такие же затраты, как и ZK-SNARK ветвей Меркле, но доказывать его значительно дешевле.

С точки зрения реализации, вероятно, лучше всего, чтобы бандлеры агрегировали межцепочечные доказательства через стандарт абстракции счетов ERC-4337. В ERC-4337 уже есть механизм для разработчиков, позволяющий объединять части UserOperations пользовательскими способами. Существует даже <a href="https://hackmd.io/@voltrevo/BJ0QBy3zi"> реализация этого для агрегации подписей BLS, которая может снизить расходы на газ для L2 в 1,5-3 раза в зависимости от того, какие другие формы сжатия включены.

Диаграмма из поста <a href="https://hackmd.io/@voltrevo/BJ0QBy3zi"> BLS wallet implementation post, показывающая процесс работы с агрегатными подписями BLS в рамках ранней версии ERC-4337. Рабочий процесс объединения межцепочечных доказательств, скорее всего, будет выглядеть очень похоже.

Прямое считывание состояния

Последняя возможность, которая подходит только для L2, читающего L1 (а не для L1, читающего L2), - это модифицировать L2, чтобы позволить им делать статические вызовы контрактов на L1 напрямую.

Это можно сделать с помощью опкода или прекомпиляции, которая позволяет вызывать L1, где Вы указываете адрес назначения, газ и calldata, и она возвращает результат, хотя, поскольку эти вызовы являются статическими, они не могут фактически изменить какое-либо состояние L1. L2 уже должны знать о L1, чтобы обрабатывать депозиты, поэтому нет ничего принципиального, что мешало бы реализовать такую вещь; это в основном техническая проблема реализации (см.: этот RFP от Optimism для поддержки статических вызовов в L1).

Обратите внимание, что если хранилище ключей находится на L1, а L2 интегрируют функциональность статических вызовов L1, то доказательств не требуется вообще! Однако если L2 не интегрируют статические вызовы L1, или если хранилище ключей находится на L2 (что, возможно, в конечном итоге придется сделать, когда L1 станет слишком дорогим для пользователей, чтобы использовать его хоть немного), то потребуются доказательства.

Как L2 узнает недавний корень состояния Ethereum?

Все описанные выше схемы требуют, чтобы L2 обращался либо к корню последнего состояния L1, либо ко всему последнему состоянию L1. К счастью, все L2 уже имеют некоторую функциональность для доступа к недавнему состоянию L1. Это связано с тем, что такая функциональность необходима им для обработки сообщений, поступающих из L1 в L2, в частности, депозитов.

И действительно, если L2 имеет функцию депозита, то Вы можете использовать этот L2 как есть для перемещения корней состояний L1 в контракт на L2: просто вызовите в контракте на L1 опкод BLOCKHASH и передайте его на L2 в качестве сообщения о депозите. Полный заголовок блока может быть получен, а его корень состояния извлечен на стороне L2. Однако было бы гораздо лучше, если бы каждый L2 имел явный способ прямого доступа либо к полному состоянию L1, либо к корням состояния L1.

Основная проблема, связанная с оптимизацией того, как L2 получают свежие корни состояния L1, заключается в одновременном достижении безопасности и низкой задержки:

  • Если L2 реализуют функцию "прямого чтения L1" ленивым способом, читая только финализированные корни состояния L1, то задержка обычно составляет 15 минут, но в экстремальном случае утечек неактивности (с которыми Вам придется мириться) задержка может составлять несколько недель.
  • L2 абсолютно точно могут быть спроектированы для чтения гораздо более поздних корней состояний L1, но поскольку L1 может возвращаться (даже при однослотовом окончании возвраты могут происходить во время утечек неактивности), L2 также должен быть способен возвращаться. Это технически сложно с точки зрения программной инженерии, но, по крайней мере, Optimism уже имеет такую возможность.
  • Если Вы используете мост депозита для перевода корней состояния L1 в состояние L2, то простая экономическая целесообразность может потребовать длительного времени между обновлениями депозита: если полная стоимость депозита составляет 100 000 газа, и мы предполагаем, что ETH стоит $1800, а комиссии составляют 200 гвеев, и корни L1 переводятся в L2 раз в день, то это будет стоить $36 на L2 в день, или $13148 на L2 в год для поддержания системы. При задержке в один час эта сумма возрастает до $315 569 на L2 в год. В лучшем случае, постоянный приток нетерпеливых богатых пользователей покрывает расходы на обновление и поддерживает систему в актуальном состоянии для всех остальных. В худшем случае, какому-нибудь альтруистичному деятелю придется заплатить за это самому.
  • "Оракулы" (по крайней мере, те технологии, которые некоторые дефи-люди называют "оракулами") не являются приемлемым решением в данном случае: управление ключами кошелька - это очень важная для безопасности низкоуровневая функциональность, и поэтому она должна зависеть максимум от нескольких частей очень простой, криптографически недоверчивой низкоуровневой инфраструктуры.

Кроме того, в обратном направлении (L1 читают L2):

  • При оптимистичном сворачивании корни штата достигают L1 за одну неделю из-за задержки доказательства мошенничества. Пока что на сворачивание ZK уходит несколько часов из-за сочетания времени на проверку и экономических ограничений, хотя будущие технологии позволят сократить это время.
  • Предварительное подтверждение (от секвенсоров, аттестователей и т.д.) не является приемлемым решением для чтения L1 на L2. Управление кошельком - это очень важная для безопасности низкоуровневая функциональность, поэтому уровень безопасности связи L2 -> L1 должен быть абсолютным: не должно быть даже возможности ввести ложный корень состояния L1, захватив набор валидаторов L2. Единственные корни состояний, которым должен доверять L1, - это корни состояний, которые были приняты как окончательные в рамках контракта L2 с L1 на удержание корней состояний.

Некоторые из этих скоростей для бездоверительных межцепочечных операций являются неприемлемо медленными для многих случаев использования Defi; для этих случаев Вам действительно нужны более быстрые мосты с более несовершенными моделями безопасности. Однако для случая обновления ключей кошелька более длительные задержки более приемлемы: Вы не задерживаете транзакции на часы, Вы задерживаете смену ключей. Вам просто придется дольше хранить старые ключи. Если Вы меняете ключи из-за того, что их крадут, то у Вас действительно есть значительный период уязвимости, но это можно смягчить, например, с помощью кошельков с функцией замораживания.

В конечном итоге, наилучшим решением, позволяющим сократить время ожидания, является оптимальная реализация L2 прямого чтения корней состояний L1, когда каждый блок L2 (или журнал вычислений корня состояний) содержит указатель на самый последний блок L1, так что если L1 вернется, L2 тоже сможет вернуться. Контракты Keystore должны быть размещены либо в mainnet, либо на L2, которые являются ZK-роликами и поэтому могут быстро перевестись на L1.

Блоки цепочки L2 могут иметь зависимости не только от предыдущих блоков L2, но и от блока L1. Если L1 возвращается, минуя такую связь, L2 тоже возвращается. Стоит отметить, что именно так должна была работать и ранняя (до Dank) версия шардинга; код см. здесь.

Насколько большая связь с Ethereum нужна другой цепочке, чтобы хранить кошельки, чьи хранилища ключей находятся на Ethereum или L2?

На удивление, не так уж и много. На самом деле, это даже не обязательно должен быть роллап: если это L3 или валидиум, то там можно держать кошельки, при условии, что Вы держите хранилища ключей либо на L1, либо на ZK роллапе. Главное, что Вам нужно, - это чтобы цепочка имела прямой доступ к корням состояний Ethereum, а также техническая и социальная готовность к перестройке, если Ethereum перестроится, и к хард-форку, если Ethereum перестроится.

Одна из интересных исследовательских проблем заключается в определении того, насколько возможно, чтобы цепочка имела такую форму связи с множеством других цепочек (например. Ethereum и Zcash). Наивный подход возможен: Ваша цепочка может согласиться на реорганизацию, если реорганизуется Ethereum или Zcash (и на хард форк, если хард форкнется Ethereum или Zcash), но тогда операторы узлов и сообщество в целом окажутся в двойной зависимости от технических и политических факторов. Таким образом, подобная техника может быть использована для подключения к нескольким другим цепочкам, но с большими затратами. Схемы, основанные на мостах ZK, обладают привлекательными техническими свойствами, но у них есть ключевой недостаток - они не устойчивы к атакам 51% или жестким вилкам. Возможно, есть и более разумные решения.

Сохранение конфиденциальности

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

  • Неизвестно, что все эти кошельки связаны друг с другом.
  • Опекуны социального восстановления не узнают, что это за адреса, которые они охраняют.

Это создает несколько проблем:

  • Мы не можем использовать доказательства Меркла напрямую, потому что они не сохраняют конфиденциальность.
  • Если мы используем KZG или SNARK, то доказательство должно предоставлять "слепую" версию ключа проверки, не раскрывая местоположение ключа проверки.
  • Если мы используем агрегацию, то агрегатор не должен узнавать местоположение открытым текстом; скорее, агрегатор должен получать доказательства вслепую и иметь возможность их агрегировать.
  • Мы не можем использовать "облегченную версию" (использовать межцепочечные доказательства только для обновления ключей), потому что это создает утечку конфиденциальности: если многие кошельки обновляются одновременно благодаря процедуре обновления, время утечки информации о том, что эти кошельки, вероятно, связаны между собой. Поэтому мы должны использовать "тяжелую версию" (межцепочечные доказательства для каждой транзакции).

В случае с SNARKs решения концептуально просты: доказательства по умолчанию скрывают информацию, и агрегатору нужно создать рекурсивный SNARK, чтобы доказать SNARKs.

Основная проблема этого подхода на сегодняшний день заключается в том, что агрегация требует от агрегатора создания рекурсивного SNARK, что в настоящее время происходит довольно медленно.

С помощью KZG мы можем использовать <a href="https://notes.ethereum.org/@vbuterin/non_index_revealing_proof"> this Работа по неиндексным доказательствам KZG (см. также: более формализованная версия этой работы в статье Caulk) в качестве отправной точки. Однако агрегирование слепых доказательств - это открытая проблема, которая требует большего внимания.

Прямое чтение L1 изнутри L2, к сожалению, не сохраняет конфиденциальность, хотя реализация функции прямого чтения все равно очень полезна, как для минимизации задержки, так и из-за ее полезности для других приложений.

Краткая информация

  • Чтобы иметь кошельки для социального восстановления, наиболее реалистичный рабочий процесс - это кошелек, который хранит хранилище ключей в одном месте, и кошельки во многих местах, где кошельки считывают хранилище ключей либо (i) для обновления своего локального представления ключа проверки, либо (ii) в процессе проверки каждой транзакции.
  • Ключевым компонентом, позволяющим сделать это возможным, являются доказательства кросс-цепочек. Нам нужно сильно оптимизировать эти доказательства. Либо ZK-SNARK, либо ожидание доказательств Веркля, либо построенное на заказ решение KZG кажутся наилучшими вариантами.
  • В долгосрочной перспективе для минимизации затрат потребуются протоколы агрегирования, в которых агрегирующие устройства генерируют агрегированные доказательства в процессе создания пакета всех UserOperations, которые были представлены пользователями. Вероятно, это следует интегрировать в экосистему ERC-4337, хотя, скорее всего, потребуется внести изменения в ERC-4337.
  • L2 должны быть оптимизированы так, чтобы минимизировать задержку чтения состояния L1 (или, по крайней мере, корня состояния) изнутри L2. L2, напрямую читающие состояние L1, идеальны и могут сэкономить место для доказательства.
  • Кошельки могут находиться не только на L2; Вы также можете разместить кошельки на системах с более низким уровнем связи с Ethereum (L3 или даже отдельные цепочки, которые согласны включать только корни состояний Ethereum и реорганизовывать или делать хардфорк, когда Ethereum реорганизует или делает хардфорк).
  • Однако хранилища ключей должны находиться либо на L1, либо на ZK-ролике L2 с высокой степенью защиты. Нахождение на L1 экономит много сложностей, хотя в долгосрочной перспективе даже это может оказаться слишком дорогим, отсюда необходимость в хранилищах ключей на L2.
  • Сохранение конфиденциальности потребует дополнительной работы и усложнит некоторые варианты. Однако, вероятно, нам все равно следует двигаться в сторону решений, сохраняющих конфиденциальность, и, по крайней мере, убедиться, что все, что мы предложим, будет совместимо с сохранением конфиденциальности.

Отказ от ответственности:

  1. Эта статья перепечатана с сайта[vitalik], Все авторские права принадлежат автору оригинала[Виталик Бутерин]. Если у Вас есть возражения против этой перепечатки, пожалуйста, свяжитесь с командой Gate Learn, и они незамедлительно рассмотрят их.
  2. Отказ от ответственности: Мнения и взгляды, выраженные в этой статье, принадлежат исключительно автору и не являются инвестиционным советом.
  3. Перевод статьи на другие языки осуществляется командой Gate Learn. Если не указано, копирование, распространение или плагиат переведенных статей запрещены.
Начните торговать сейчас
Зарегистрируйтесь сейчас и получите ваучер на
$100
!