Особая благодарность Йоаву Вайсу, Дэну Финлею, Мартину Коппельману, а также командам 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, а кошелек - на другом L2. Если либо хранилище ключей, либо кошелек находятся на L1, то потребуется только половина этой конструкции.
Предположим, что хранилище ключей находится на Linea, а кошелек - на Kakarot. Полное доказательство ключей от кошелька состоит из:
Здесь есть два основных непростых вопроса, связанных с реализацией:
Существует пять основных вариантов:
С точки зрения требуемой инфраструктуры и стоимости для пользователей, я оцениваю их примерно следующим образом:
"Агрегация" относится к идее объединения всех доказательств, предоставленных пользователями в рамках каждого блока, в большое мета-доказательство, которое объединяет их все. Это возможно для SNARK и для KZG, но не для ветвей Меркла (Вы можете немного объединить ветви Меркла, но это сэкономит Вам только log(txs per block) / log(total number of keystores), возможно, 15-30% на практике, так что это, вероятно, не стоит затрат).
Агрегирование становится целесообразным только тогда, когда у схемы появляется значительное количество пользователей, так что в реальности для реализации версии 1 вполне можно отказаться от агрегирования и реализовать его в версии 2.
Здесь все просто: следуйте схеме, приведенной в предыдущем разделе. Точнее, каждое "доказательство" (предполагая максимально сложный случай доказательства одного 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 стоит ~400,000 газа вычислений и около 400 байт (сравните: 21,000 газа и 100 байт для базовой транзакции, в будущем сокращаемой до ~25 байт при сжатии). Таким образом, с точки зрения вычислений, ZK-SNARK стоит в 19 раз больше, чем базовая транзакция сегодня, а с точки зрения данных, ZK-SNARK стоит в 4 раза больше, чем базовая транзакция сегодня, и в 16 раз больше, чем базовая транзакция может стоить в будущем.
Эти цифры значительно превосходят доказательства Меркла, но они все еще довольно дороги. Есть два способа улучшить эту ситуацию: (i) специальные KZG-доказательства или (ii) агрегирование, похожее на агрегирование ERC-4337, но с использованием более причудливой математики. Мы можем рассмотреть оба варианта.
Внимание, в этом разделе гораздо больше математики, чем в других разделах. Это происходит потому, что мы выходим за рамки инструментов общего назначения и создаем нечто специализированное, чтобы быть дешевле, поэтому нам приходится гораздо чаще заглядывать "под капот". Если Вам не нравится глубокая математика, переходите сразу к следующему разделу.
Сначала напомним, как работают обязательства KZG:
Некоторые ключевые свойства, которые важно понимать, таковы:
Таким образом, у нас есть структура, в которой мы можем просто продолжать добавлять значения в конец постоянно растущего списка, хотя и с определенным ограничением по размеру (в реальности, сотни миллионов могут быть жизнеспособными). Затем мы используем эту структуру данных для управления (i) обязательствами по списку ключей на каждом L2, которые хранятся на этом L2 и зеркально отражаются на L1, и (ii) обязательствами по списку обязательств по ключам L2, которые хранятся на Ethereum L1 и зеркально отражаются на каждом L2.
Обновление обязательств может стать частью логики ядра L2, либо может быть реализовано без изменений протокола ядра L2 с помощью мостов ввода и вывода средств.
Таким образом, для полного доказательства потребуется:
На самом деле можно объединить два доказательства 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 обращался либо к корню последнего состояния L1, либо ко всему последнему состоянию L1. К счастью, все L2 уже имеют некоторую функциональность для доступа к недавнему состоянию L1. Это связано с тем, что такая функциональность необходима им для обработки сообщений, поступающих из L1 в L2, в частности, депозитов.
И действительно, если L2 имеет функцию депозита, то Вы можете использовать этот L2 как есть для перемещения корней состояний L1 в контракт на L2: просто вызовите в контракте на L1 опкод BLOCKHASH и передайте его на L2 в качестве сообщения о депозите. Полный заголовок блока может быть получен, а его корень состояния извлечен на стороне L2. Однако было бы гораздо лучше, если бы каждый L2 имел явный способ прямого доступа либо к полному состоянию L1, либо к корням состояния L1.
Основная проблема, связанная с оптимизацией того, как L2 получают свежие корни состояния L1, заключается в одновременном достижении безопасности и низкой задержки:
Кроме того, в обратном направлении (L1 читают L2):
Некоторые из этих скоростей для бездоверительных межцепочечных операций являются неприемлемо медленными для многих случаев использования Defi; для этих случаев Вам действительно нужны более быстрые мосты с более несовершенными моделями безопасности. Однако для случая обновления ключей кошелька более длительные задержки более приемлемы: Вы не задерживаете транзакции на часы, Вы задерживаете смену ключей. Вам просто придется дольше хранить старые ключи. Если Вы меняете ключи из-за того, что их крадут, то у Вас действительно есть значительный период уязвимости, но это можно смягчить, например, с помощью кошельков с функцией замораживания.
В конечном итоге, наилучшим решением, позволяющим сократить время ожидания, является оптимальная реализация L2 прямого чтения корней состояний L1, когда каждый блок L2 (или журнал вычислений корня состояний) содержит указатель на самый последний блок L1, так что если L1 вернется, L2 тоже сможет вернуться. Контракты Keystore должны быть размещены либо в mainnet, либо на L2, которые являются ZK-роликами и поэтому могут быстро перевестись на L1.
Блоки цепочки L2 могут иметь зависимости не только от предыдущих блоков L2, но и от блока L1. Если L1 возвращается, минуя такую связь, L2 тоже возвращается. Стоит отметить, что именно так должна была работать и ранняя (до Dank) версия шардинга; код см. здесь.
На удивление, не так уж и много. На самом деле, это даже не обязательно должен быть роллап: если это L3 или валидиум, то там можно держать кошельки, при условии, что Вы держите хранилища ключей либо на L1, либо на ZK роллапе. Главное, что Вам нужно, - это чтобы цепочка имела прямой доступ к корням состояний Ethereum, а также техническая и социальная готовность к перестройке, если Ethereum перестроится, и к хард-форку, если Ethereum перестроится.
Одна из интересных исследовательских проблем заключается в определении того, насколько возможно, чтобы цепочка имела такую форму связи с множеством других цепочек (например. Ethereum и Zcash). Наивный подход возможен: Ваша цепочка может согласиться на реорганизацию, если реорганизуется Ethereum или Zcash (и на хард форк, если хард форкнется Ethereum или Zcash), но тогда операторы узлов и сообщество в целом окажутся в двойной зависимости от технических и политических факторов. Таким образом, подобная техника может быть использована для подключения к нескольким другим цепочкам, но с большими затратами. Схемы, основанные на мостах ZK, обладают привлекательными техническими свойствами, но у них есть ключевой недостаток - они не устойчивы к атакам 51% или жестким вилкам. Возможно, есть и более разумные решения.
В идеале мы также хотим сохранить конфиденциальность. Если у Вас много кошельков, управляемых одним и тем же хранилищем ключей, то мы хотим убедиться в этом:
Это создает несколько проблем:
В случае с SNARKs решения концептуально просты: доказательства по умолчанию скрывают информацию, и агрегатору нужно создать рекурсивный SNARK, чтобы доказать SNARKs.
Основная проблема этого подхода на сегодняшний день заключается в том, что агрегация требует от агрегатора создания рекурсивного SNARK, что в настоящее время происходит довольно медленно.
С помощью KZG мы можем использовать <a href="https://notes.ethereum.org/@vbuterin/non_index_revealing_proof"> this Работа по неиндексным доказательствам KZG (см. также: более формализованная версия этой работы в статье Caulk) в качестве отправной точки. Однако агрегирование слепых доказательств - это открытая проблема, которая требует большего внимания.
Прямое чтение L1 изнутри 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, а кошелек - на другом L2. Если либо хранилище ключей, либо кошелек находятся на L1, то потребуется только половина этой конструкции.
Предположим, что хранилище ключей находится на Linea, а кошелек - на Kakarot. Полное доказательство ключей от кошелька состоит из:
Здесь есть два основных непростых вопроса, связанных с реализацией:
Существует пять основных вариантов:
С точки зрения требуемой инфраструктуры и стоимости для пользователей, я оцениваю их примерно следующим образом:
"Агрегация" относится к идее объединения всех доказательств, предоставленных пользователями в рамках каждого блока, в большое мета-доказательство, которое объединяет их все. Это возможно для SNARK и для KZG, но не для ветвей Меркла (Вы можете немного объединить ветви Меркла, но это сэкономит Вам только log(txs per block) / log(total number of keystores), возможно, 15-30% на практике, так что это, вероятно, не стоит затрат).
Агрегирование становится целесообразным только тогда, когда у схемы появляется значительное количество пользователей, так что в реальности для реализации версии 1 вполне можно отказаться от агрегирования и реализовать его в версии 2.
Здесь все просто: следуйте схеме, приведенной в предыдущем разделе. Точнее, каждое "доказательство" (предполагая максимально сложный случай доказательства одного 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 стоит ~400,000 газа вычислений и около 400 байт (сравните: 21,000 газа и 100 байт для базовой транзакции, в будущем сокращаемой до ~25 байт при сжатии). Таким образом, с точки зрения вычислений, ZK-SNARK стоит в 19 раз больше, чем базовая транзакция сегодня, а с точки зрения данных, ZK-SNARK стоит в 4 раза больше, чем базовая транзакция сегодня, и в 16 раз больше, чем базовая транзакция может стоить в будущем.
Эти цифры значительно превосходят доказательства Меркла, но они все еще довольно дороги. Есть два способа улучшить эту ситуацию: (i) специальные KZG-доказательства или (ii) агрегирование, похожее на агрегирование ERC-4337, но с использованием более причудливой математики. Мы можем рассмотреть оба варианта.
Внимание, в этом разделе гораздо больше математики, чем в других разделах. Это происходит потому, что мы выходим за рамки инструментов общего назначения и создаем нечто специализированное, чтобы быть дешевле, поэтому нам приходится гораздо чаще заглядывать "под капот". Если Вам не нравится глубокая математика, переходите сразу к следующему разделу.
Сначала напомним, как работают обязательства KZG:
Некоторые ключевые свойства, которые важно понимать, таковы:
Таким образом, у нас есть структура, в которой мы можем просто продолжать добавлять значения в конец постоянно растущего списка, хотя и с определенным ограничением по размеру (в реальности, сотни миллионов могут быть жизнеспособными). Затем мы используем эту структуру данных для управления (i) обязательствами по списку ключей на каждом L2, которые хранятся на этом L2 и зеркально отражаются на L1, и (ii) обязательствами по списку обязательств по ключам L2, которые хранятся на Ethereum L1 и зеркально отражаются на каждом L2.
Обновление обязательств может стать частью логики ядра L2, либо может быть реализовано без изменений протокола ядра L2 с помощью мостов ввода и вывода средств.
Таким образом, для полного доказательства потребуется:
На самом деле можно объединить два доказательства 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 обращался либо к корню последнего состояния L1, либо ко всему последнему состоянию L1. К счастью, все L2 уже имеют некоторую функциональность для доступа к недавнему состоянию L1. Это связано с тем, что такая функциональность необходима им для обработки сообщений, поступающих из L1 в L2, в частности, депозитов.
И действительно, если L2 имеет функцию депозита, то Вы можете использовать этот L2 как есть для перемещения корней состояний L1 в контракт на L2: просто вызовите в контракте на L1 опкод BLOCKHASH и передайте его на L2 в качестве сообщения о депозите. Полный заголовок блока может быть получен, а его корень состояния извлечен на стороне L2. Однако было бы гораздо лучше, если бы каждый L2 имел явный способ прямого доступа либо к полному состоянию L1, либо к корням состояния L1.
Основная проблема, связанная с оптимизацией того, как L2 получают свежие корни состояния L1, заключается в одновременном достижении безопасности и низкой задержки:
Кроме того, в обратном направлении (L1 читают L2):
Некоторые из этих скоростей для бездоверительных межцепочечных операций являются неприемлемо медленными для многих случаев использования Defi; для этих случаев Вам действительно нужны более быстрые мосты с более несовершенными моделями безопасности. Однако для случая обновления ключей кошелька более длительные задержки более приемлемы: Вы не задерживаете транзакции на часы, Вы задерживаете смену ключей. Вам просто придется дольше хранить старые ключи. Если Вы меняете ключи из-за того, что их крадут, то у Вас действительно есть значительный период уязвимости, но это можно смягчить, например, с помощью кошельков с функцией замораживания.
В конечном итоге, наилучшим решением, позволяющим сократить время ожидания, является оптимальная реализация L2 прямого чтения корней состояний L1, когда каждый блок L2 (или журнал вычислений корня состояний) содержит указатель на самый последний блок L1, так что если L1 вернется, L2 тоже сможет вернуться. Контракты Keystore должны быть размещены либо в mainnet, либо на L2, которые являются ZK-роликами и поэтому могут быстро перевестись на L1.
Блоки цепочки L2 могут иметь зависимости не только от предыдущих блоков L2, но и от блока L1. Если L1 возвращается, минуя такую связь, L2 тоже возвращается. Стоит отметить, что именно так должна была работать и ранняя (до Dank) версия шардинга; код см. здесь.
На удивление, не так уж и много. На самом деле, это даже не обязательно должен быть роллап: если это L3 или валидиум, то там можно держать кошельки, при условии, что Вы держите хранилища ключей либо на L1, либо на ZK роллапе. Главное, что Вам нужно, - это чтобы цепочка имела прямой доступ к корням состояний Ethereum, а также техническая и социальная готовность к перестройке, если Ethereum перестроится, и к хард-форку, если Ethereum перестроится.
Одна из интересных исследовательских проблем заключается в определении того, насколько возможно, чтобы цепочка имела такую форму связи с множеством других цепочек (например. Ethereum и Zcash). Наивный подход возможен: Ваша цепочка может согласиться на реорганизацию, если реорганизуется Ethereum или Zcash (и на хард форк, если хард форкнется Ethereum или Zcash), но тогда операторы узлов и сообщество в целом окажутся в двойной зависимости от технических и политических факторов. Таким образом, подобная техника может быть использована для подключения к нескольким другим цепочкам, но с большими затратами. Схемы, основанные на мостах ZK, обладают привлекательными техническими свойствами, но у них есть ключевой недостаток - они не устойчивы к атакам 51% или жестким вилкам. Возможно, есть и более разумные решения.
В идеале мы также хотим сохранить конфиденциальность. Если у Вас много кошельков, управляемых одним и тем же хранилищем ключей, то мы хотим убедиться в этом:
Это создает несколько проблем:
В случае с SNARKs решения концептуально просты: доказательства по умолчанию скрывают информацию, и агрегатору нужно создать рекурсивный SNARK, чтобы доказать SNARKs.
Основная проблема этого подхода на сегодняшний день заключается в том, что агрегация требует от агрегатора создания рекурсивного SNARK, что в настоящее время происходит довольно медленно.
С помощью KZG мы можем использовать <a href="https://notes.ethereum.org/@vbuterin/non_index_revealing_proof"> this Работа по неиндексным доказательствам KZG (см. также: более формализованная версия этой работы в статье Caulk) в качестве отправной точки. Однако агрегирование слепых доказательств - это открытая проблема, которая требует большего внимания.
Прямое чтение L1 изнутри L2, к сожалению, не сохраняет конфиденциальность, хотя реализация функции прямого чтения все равно очень полезна, как для минимизации задержки, так и из-за ее полезности для других приложений.