Profundización en la lectura cruzada de L2 para billeteras y otros casos de uso

AvanzadoFeb 29, 2024
En este artículo, Vitalik aborda directamente un aspecto técnico específico de un subproblema: cómo leer más fácilmente de L2 a L1, de L1 a L2, o de una L2 a otra L2. Resolver este problema es crucial para lograr una arquitectura de separación de activos/claves, pero también tiene casos de uso valiosos en otras áreas, sobre todo en la optimización de llamadas confiables entre L2, incluidos casos de uso como mover activos entre L1 y L2.
Profundización en la lectura cruzada de L2 para billeteras y otros casos de uso

Un agradecimiento especial a Yoav Weiss, Dan Finlay, Martin Koppelmann y a los equipos de Arbitrum, Optimism, Polygon, Scroll y SoulWallet por sus comentarios y reseñas.

En esta publicación sobre las Tres Transiciones, describí algunas razones clave por las que es valioso comenzar a pensar explícitamente en el soporte L1 + Cross-L2, la seguridad de la billetera y la privacidad como características básicas necesarias de la pila del ecosistema, en lugar de construir cada una de estas cosas como complementos que pueden ser diseñados por separado por billeteras individuales.

Esta publicación se centrará más directamente en los aspectos técnicos de un subproblema específico: cómo facilitar la lectura de L1 de L2, L2 de L1 o L2 de otra L2. Resolver este problema es crucial para implementar una arquitectura de separación de activos / almacén de claves, pero también tiene casos de uso valiosos en otras áreas, sobre todo la optimización de llamadas confiables entre L2, incluidos casos de uso como mover activos entre L1 y L2.

Lecturas previas recomendadas

Tabla de contenidos

¿Cuál es el objetivo?

Una vez que las L2 se generalicen, los usuarios tendrán activos en varias L2 y, posiblemente, también en L1. Una vez que las billeteras de contratos inteligentes (multifirma, recuperación social o de otro tipo) se conviertan en la corriente principal, las claves necesarias para acceder a alguna cuenta cambiarán con el tiempo, y las claves antiguas ya no tendrían que ser válidas. Una vez que sucedan ambas cosas, un usuario deberá tener una forma de cambiar las claves que tienen autoridad para acceder a muchas cuentas que viven en muchos lugares diferentes, sin realizar un número extremadamente alto de transacciones.

En particular, necesitamos una forma de manejar las direcciones contrafácticas: direcciones que aún no han sido "registradas" de ninguna manera en la cadena, pero que, sin embargo, necesitan recibir y mantener fondos de forma segura. Todos dependemos de las direcciones contrafácticas: cuando usas Ethereum por primera vez, puedes generar una dirección ETH que alguien puede usar para pagarte, sin "registrar" la dirección en la cadena (lo que requeriría pagar txfees y, por lo tanto, ya tener algo de ETH).

Con las EOA, todas las direcciones comienzan como direcciones contrafactuales. Con las billeteras de contratos inteligentes, las direcciones contrafácticas aún son posibles, en gran parte gracias a CREATE2, que le permite tener una dirección ETH que solo puede ser completada por un contrato inteligente que tiene un código que coincide con un hash en particular.

Algoritmo de cálculo de direcciones EIP-1014 (CREATE2).

Sin embargo, las billeteras de contratos inteligentes introducen un nuevo desafío: la posibilidad de cambiar las claves de acceso. La dirección, que es un hash del código de inicio, solo puede contener la clave de verificación inicial de la billetera. La clave de verificación actual se almacenaría en el almacenamiento de la billetera, pero ese registro de almacenamiento no se propaga mágicamente a otras L2.

Si un usuario tiene muchas direcciones en muchas L2, incluidas direcciones que (porque son contrafácticas) la L2 en la que se encuentra no conoce, parece que solo hay una forma de permitir que los usuarios cambien sus claves: arquitectura de separación de activos/almacén de claves. Cada usuario tiene (i) un "contrato de almacén de claves" (en L1 o en un L2 en particular), que almacena la clave de verificación para todas las billeteras junto con las reglas para cambiar la clave, y (ii) "contratos de billetera" en L1 y muchos L2, que leen la cadena cruzada para obtener la clave de verificación.

Hay dos formas de implementar esto:

  • Versión ligera (marque solo para actualizar las claves): cada billetera almacena la clave de verificación localmente y contiene una función que se puede llamar para verificar una prueba entre cadenas del estado actual del almacén de claves y actualizar su clave de verificación almacenada localmente para que coincida. Cuando se usa una billetera por primera vez en una L2 en particular, es obligatorio llamar a esa función para obtener la clave de verificación actual del almacén de claves.
    • Ventaja: usa las pruebas de cadena cruzada con moderación, por lo que está bien si las pruebas de cadena cruzada son caras. Todos los fondos solo se pueden gastar con las claves actuales, por lo que sigue siendo seguro.
    • Desventaja: Para cambiar la clave de verificación, debe realizar un cambio de clave en la cadena tanto en el almacén de claves como en cada billetera que ya esté inicializada (aunque no contrafactual). Esto podría costar mucha gasolina.
  • Versión pesada (comprobar para cada tx): es necesaria una prueba de cadena cruzada que muestre la clave actualmente en el almacén de claves para cada transacción.
    • Ventaja: menos complejidad sistémica y la actualización del almacén de claves es barata.
    • Desventaja: caro por tx, por lo que requiere mucha más ingeniería para hacer que las pruebas de cadena cruzada sean aceptablemente baratas. Tampoco es fácilmente compatible con ERC-4337, que actualmente no admite la lectura de contratos cruzados de objetos mutables durante la validación.

¿Cómo es una prueba de cadena cruzada?

Para mostrar toda la complejidad, exploraremos el caso más difícil: donde el almacén de claves está en una L2 y la billetera está en una L2 diferente. Si el almacén de claves o la billetera están en L1, entonces solo se necesita la mitad de este diseño.

Supongamos que el almacén de claves está en Linea y la billetera está en Kakarot. Una prueba completa de las claves de la billetera consiste en:

  • Una prueba que demuestra la raíz actual del estado de Linea, dada la raíz actual del estado de Ethereum que Kakarot conoce
  • Una prueba que demuestre las claves actuales en el almacén de claves, dada la raíz de estado actual de Linea

Aquí hay dos preguntas principales y complicadas de implementación:

  1. ¿Qué tipo de pruebas utilizamos? (¿Son pruebas de Merkle? ¿Algo más?)
  2. ¿Cómo aprende la L2 la raíz del estado L1 reciente (Ethereum) (o, como veremos, potencialmente el estado L1 completo) en primer lugar? Y alternativamente, ¿cómo aprende la L1 la raíz del estado L2?
    • En ambos casos, ¿cuánto tiempo transcurren los retrasos entre que algo sucede en un lado y que esa cosa es demostrable para el otro lado?

¿Qué tipos de esquemas de prueba podemos utilizar?

Hay cinco opciones principales:

  • Pruebas de Merkle
  • ZK-SNARK de uso general
  • Las pruebas de propósito especial (p. ej. con KZG)
  • Verkle, que se encuentran en algún lugar entre KZG y ZK-SNARK tanto en la carga de trabajo como en el costo de la infraestructura.
  • No hay pruebas y confía en la lectura directa del estado

En términos de obras de infraestructura requeridas y costo para los usuarios, los clasifico aproximadamente de la siguiente manera:

"Agregación" se refiere a la idea de agregar todas las pruebas proporcionadas por los usuarios dentro de cada bloque en una gran meta-prueba que las combina todas. Esto es posible para SNARKs y para KZG, pero no para las ramas de Merkle (puede combinar un poco las ramas de Merkle, pero solo le ahorra log(txs por bloque) / log (número total de almacenes de claves), tal vez un 15-30% en la práctica, por lo que probablemente no valga la pena el costo).

La agregación solo vale la pena una vez que el esquema tiene un número sustancial de usuarios, por lo que, siendo realistas, está bien que una implementación de la versión 1 omita la agregación e implemente eso para la versión 2.

¿Cómo funcionarían las pruebas de Merkle?

Esto es simple: siga el diagrama de la sección anterior directamente. Más precisamente, cada "prueba" (asumiendo el caso de máxima dificultad de probar una L2 en otra L2) contendría:

  • Una rama de Merkle que demuestra la raíz de estado de la L2 que posee el almacén de claves, dada la raíz de estado más reciente de Ethereum que la L2 conoce. La raíz de estado de la L2 que contiene el almacén de claves se almacena en una ranura de almacenamiento conocida de una dirección conocida (el contrato en L1 que representa la L2), por lo que la ruta a través del árbol podría codificarse.
  • Una rama de Merkle que prueba las claves de verificación actuales, dada la raíz de estado de la L2 que contiene el almacén de claves. Aquí, una vez más, la clave de verificación se almacena en una ranura de almacenamiento conocida de una dirección conocida, por lo que la ruta se puede codificar.

Desafortunadamente, las pruebas de estado de Ethereum son complicadas, pero existen bibliotecas para verificarlas, y si usa estas bibliotecas, este mecanismo no es demasiado complicado de implementar.

El problema más grande es el costo. Las pruebas de Merkle son largas, y los árboles de Patricia son, por desgracia, ~3,9 veces más largas de lo necesario (precisamente: una prueba de Merkle ideal en un árbol que contiene N objetos tiene 32 log2(N) bytes de largo, y debido a que los árboles Patricia de Ethereum tienen 16 hojas por hijo, las pruebas para esos árboles son de 32 15 log16(N) ~= 125 log2(N) bytes de largo). En un estado con aproximadamente 250 millones (~2²⁸) cuentas, esto hace que cada prueba sea de 125 * 28 = 3500 bytes, o alrededor de 56,000 de gas, más costos adicionales para decodificar y verificar hashes.

Dos pruebas juntas terminarían costando alrededor de 100,000 a 150,000 gas (sin incluir la verificación de la firma si se usa por transacción), significativamente más que la base actual de 21,000 gas por transacción. Pero la disparidad empeora si la prueba se verifica en L2. La computación dentro de una L2 es barata, porque la computación se realiza fuera de la cadena y en un ecosistema con muchos menos nodos que L1. Los datos, por otro lado, tienen que ser contabilizados en L1. Por lo tanto, la comparación no es de 21000 gases frente a 150.000 gases; son 21.000 L2 de gasolina frente a 100.000 L1 de gas.

Podemos calcular lo que esto significa observando las comparaciones entre los costos del gas L1 y los costos del gas L2:

L1 es actualmente entre 15 y 25 veces más caro que L2 para envíos simples, y entre 20 y 50 veces más caro para intercambios de tokens. Los envíos simples son relativamente pesados en cuanto a datos, pero los intercambios son mucho más pesados desde el punto de vista computacional. Por lo tanto, los swaps son un mejor punto de referencia para aproximar el costo del cálculo de L1 frente al cálculo de L2. Teniendo todo esto en cuenta, si asumimos una relación de costo de 30x entre el costo de cálculo de L1 y el costo de cálculo de L2, esto parece implicar que poner una prueba de Merkle en L2 costará el equivalente a quizás cincuenta transacciones regulares.

Por supuesto, el uso de un árbol de Merkle binario puede reducir los costos en ~ 4 veces, pero aún así, el costo en la mayoría de los casos será demasiado alto, y si estamos dispuestos a hacer el sacrificio de ya no ser compatibles con el árbol de estado hexatorio actual de Ethereum, también podríamos buscar opciones aún mejores.

¿Cómo funcionarían las pruebas ZK-SNARK?

Conceptualmente, el uso de ZK-SNARKs también es fácil de entender: simplemente reemplaza las pruebas de Merkle en el diagrama anterior con un ZK-SNARK que demuestre que esas pruebas de Merkle existen. Un ZK-SNARK cuesta ~400.000 de gas de cálculo, y unos 400 bytes (compare: 21.000 de gas y 100 bytes para una transacción básica, en el futuro reducible a ~25 bytes con compresión). Por lo tanto, desde una perspectiva computacional, un ZK-SNARK cuesta 19 veces el costo de una transacción básica hoy en día, y desde una perspectiva de datos, un ZK-SNARK cuesta 4 veces más que una transacción básica hoy en día, y 16 veces más de lo que puede costar una transacción básica en el futuro.

Estos números son una mejora masiva con respecto a las pruebas de Merkle, pero siguen siendo bastante caros. Hay dos formas de mejorar esto: (i) pruebas KZG de propósito especial, o (ii) agregación, similar a la agregación ERC-4337 pero usando matemáticas más sofisticadas. Podemos investigar ambos.

¿Cómo funcionarían las pruebas KZG para fines especiales?

Advertencia, esta sección es mucho más matemática que otras secciones. Esto se debe a que vamos más allá de las herramientas de propósito general y construimos algo de propósito especial para que sea más barato, por lo que tenemos que ir mucho más "bajo el capó". Si no te gustan las matemáticas profundas, pasa directamente a la siguiente sección.

Primero, un resumen de cómo funcionan los compromisos de KZG:

  • Podemos representar un conjunto de datos [D_1 ... D_n] con una prueba KZG de un polinomio derivado de los datos: específicamente, el polinomio P donde P(w) = D_1, P(w²) = D_2 ... P(wⁿ) = D_n. w aquí es una "raíz de unidad", un valor donde wN = 1 para algún tamaño de dominio de evaluación N (todo esto se hace en un campo finito).
  • Para "comprometernos" con P, creamos un punto de curva elíptica com(P) = P₀ G + P₁ S₁ + ... + Pk * Sk. Aquí:
    • G es el punto generador de la curva
    • Pi es el coeficiente i-ésimo del polinomio P
    • Si es el i-ésimo punto en la configuración de confianza
  • Para probar P(z) = a, creamos un polinomio cociente Q = (P - a) / (X - z), y creamos un compromiso com(Q) con él. Solo es posible crear un polinomio de este tipo si P(z) es realmente igual a a.
  • Para verificar una demostración, comprobamos la ecuación Q * (X - z) = P - a haciendo una comprobación de curva elíptica en la prueba com(Q) y el compromiso polinómico com(P): comprobamos e(com(Q), com(X - z)) ?= e(com(P) - com(a), com(1))

Algunas propiedades clave que es importante comprender son:

  • Una prueba es solo el valor com(Q), que es de 48 bytes
  • com(P₁) + com(P₂) = com(P₁ + P₂)
  • Esto también significa que puede "editar" un valor en un contrato existente. Supongamos que sabemos que D_i es actualmente a, queremos establecerlo en b, y el compromiso existente con D es com(P). Un compromiso con "P, pero con P(wⁱ) = b, y no se cambiaron otras evaluaciones", entonces establecemos com(new_P) = com(P) + (b-a) * com(Li), donde Li es el "polinomio de Lagrange" que es igual a 1 en wⁱ y 0 en otros puntos wj.
  • Para realizar estas actualizaciones de manera eficiente, cada cliente puede precalcular y almacenar todos los N compromisos con polinomios de Lagrange (com(Li)) . Dentro de un contrato on-chain puede ser demasiado almacenar todos los N compromisos, por lo que en su lugar podría hacer un compromiso KZG con el conjunto de valores com(L_i) (o hash(com(L_i)), de modo que cada vez que alguien necesite actualizar el árbol on-chain simplemente pueda proporcionar el com(L_i) apropiado con una prueba de su corrección.

Por lo tanto, tenemos una estructura en la que podemos seguir agregando valores al final de una lista cada vez mayor, aunque con un cierto límite de tamaño (siendo realistas, cientos de millones podrían ser viables). A continuación, lo utilizamos como nuestra estructura de datos para gestionar (i) un compromiso con la lista de claves en cada L2, almacenado en esa L2 y reflejado en L1, y (ii) un compromiso con la lista de compromisos de claves L2, almacenado en la L1 de Ethereum y reflejado en cada L2.

Mantener los compromisos actualizados podría convertirse en parte de la lógica básica de L2, o podría implementarse sin cambios en el protocolo central de L2 a través de puentes de depósito y retiro.

Por lo tanto, una prueba completa requeriría:

  • La última com(lista de claves) en la L2 que contiene el almacén de claves (48 bytes)
  • La prueba KZG de com(lista de claves) es un valor dentro de com(mirror_list), el compromiso con la lista de todos los compromisos de la lista de claves (48 bytes)
  • Prueba KZG de su clave en com (lista de claves) (48 bytes, más 4 bytes para el índice)

De hecho, es posible fusionar las dos pruebas de KZG en una, por lo que obtenemos un tamaño total de solo 100 bytes.

Tenga en cuenta una sutileza: debido a que la lista de claves es una lista, y no un mapa clave/valor como lo es el estado, la lista de claves tendrá que asignar posiciones secuencialmente. El contrato de compromiso de claves contendría su propio registro interno que asignaría cada almacén de claves a un ID, y para cada clave almacenaría hash (clave, dirección del almacén de claves) en lugar de solo clave, para comunicar inequívocamente a otras L2 de qué almacén de claves está hablando una entrada en particular.

La ventaja de esta técnica es que funciona muy bien en L2. Los datos son de 100 bytes, ~4 veces más cortos que un ZK-SNARK y mucho más cortos que una prueba de Merkle. El costo de cálculo es en gran medida una verificación de emparejamiento de tamaño 2, o alrededor de 119,000 gas. En L1, los datos son menos importantes que la computación, por lo que desafortunadamente KZG es algo más caro que las pruebas de Merkle.

¿Cómo funcionarían los árboles Verkle?

Básicamente, los árboles de Verkle implican apilar los compromisos de KZG (o los compromisos de IPA, que pueden ser más eficientes y utilizar una criptografía más sencilla) uno encima del otro: para almacenar valores de 2⁴⁸, puede hacer un compromiso de KZG con una lista de valores de 2²⁴, cada uno de los cuales es a su vez un compromiso de KZG con valores de 2²⁴. Los árboles Verkle están siendo <a href="https://notes.ethereum.org/@vbuterin/verkle_tree_eip">fuertemente considerado para el árbol de estado de Ethereum, porque los árboles de Verkle se pueden usar para contener mapas clave-valor y no solo listas (básicamente, puede hacer un árbol de tamaño 2²⁵⁶ pero comenzarlo vacío, solo completando partes específicas del árbol una vez que realmente necesite llenarlas).

Cómo es un árbol Verkle. En la práctica, puede asignar a cada nodo un ancho de 256 == 2⁸ para los árboles basados en IPA, o 2²⁴ para los árboles basados en KZG.

Las pruebas en los árboles Verkle son algo más largas que las de KZG; Pueden tener unos pocos cientos de bytes de longitud. También son difíciles de verificar, especialmente si intentas agregar muchas pruebas en una.

Siendo realistas, los árboles Verkle deben considerarse como los árboles Merkle, pero más viables sin SNARKing (debido a los menores costos de datos) y más baratos con SNARKing (debido a los menores costos de prueba).

La mayor ventaja de los árboles de Verkle es la posibilidad de armonizar las estructuras de datos: las pruebas de Verkle se pueden usar directamente sobre el estado L1 o L2, sin estructuras superpuestas, y utilizando exactamente el mismo mecanismo para L1 y L2. Una vez que las computadoras cuánticas se conviertan en un problema, o una vez que las ramas de Merkle se vuelvan lo suficientemente eficientes, los árboles Verkle podrían reemplazarse en el lugar con un árbol hash binario con una función hash adecuada compatible con SNARK.

Agregación

Si N usuarios realizan N transacciones (o más realistamente, N ERC-4337 UserOperations) que necesitan probar N afirmaciones entre cadenas, podemos ahorrar mucho gas agregando esas pruebas: el constructor que combinaría esas transacciones en un bloque o paquete que entra en un bloque puede crear una sola prueba que pruebe todas esas afirmaciones simultáneamente.

Esto podría significar:

En los tres casos, las pruebas solo costarían unos pocos cientos de miles de gas cada una. El constructor tendría que hacer uno de estos en cada L2 para los usuarios de esa L2; por lo tanto, para que esto sea útil de construir, el esquema en su conjunto debe tener suficiente uso como para que muy a menudo haya al menos algunas transacciones dentro del mismo bloque en múltiples L2 principales.

Si se utilizan ZK-SNARK, el principal costo marginal es simplemente la "lógica comercial" de pasar números entre contratos, por lo que tal vez unos pocos miles de L2 de gas por usuario. Si se utilizan pruebas múltiples KZG, el probador necesitaría agregar 48 gas por cada L2 que tenga un almacén de claves que se use dentro de ese bloque, por lo que el costo marginal del esquema por usuario agregaría otro ~ 800 gas L1 por L2 (no por usuario) en la parte superior. Pero estos costos son mucho más bajos que los costos de no agregar, que inevitablemente involucran más de 10,000 gas L1 y cientos de miles de gas L2 por usuario. Para los árboles Verkle, puede usar Verkle multi-proofs directamente, agregando alrededor de 100-200 bytes por usuario, o puede hacer un ZK-SNARK de un Verkle multi-proof, que tiene costos similares a los ZK-SNARK de las ramas de Merkle pero es significativamente más barato de probar.

Desde el punto de vista de la implementación, probablemente sea mejor que los agrupadores agreguen pruebas de cadena cruzada a través del estándar de abstracción de cuentas ERC-4337 . ERC-4337 ya tiene un mecanismo para que los constructores agreguen partes de UserOperations de forma personalizada. Incluso hay una <a href="https://hackmd.io/@voltrevo/BJ0QBy3zi">implementación de esto para la agregación de firmas BLS, que podría reducir los costos de gas en L2 de 1,5 a 3 veces, dependiendo de qué otras formas de compresión se incluyan.

Diagrama de una publicaciónde implementaciónde billetera <a href=" https://hackmd.io/@voltrevo /BJ0QBy3zi">BLS que muestra el flujo de trabajo de las firmas agregadas de BLS dentro de una versión anterior de ERC-4337. Es probable que el flujo de trabajo de agregación de pruebas entre cadenas sea muy similar.

Lectura directa del estado

Una última posibilidad, y que solo se puede utilizar para L2 que lee L1 (y no L1 que lee L2), es modificar L2 para permitirles realizar llamadas estáticas a contratos en L1 directamente.

Esto se puede hacer con un código de operación o una precompilación, que permite llamadas a L1 donde proporciona la dirección de destino, gas y calldata, y devuelve la salida, aunque debido a que estas llamadas son llamadas estáticas, en realidad no pueden cambiar ningún estado L1. Los L2 tienen que ser conscientes de L1 ya para procesar depósitos, por lo que no hay nada fundamental que impida que se implemente tal cosa; es principalmente un desafío de implementación técnica (ver: esta RFP de Optimism para admitir llamadas estáticas en L1).

Tenga en cuenta que si el almacén de claves está en L1 y las L2 integran la funcionalidad de llamada estática L1, ¡no se requieren pruebas en absoluto! Sin embargo, si las L2 no integran llamadas estáticas L1, o si el almacén de claves está en L2 (lo que eventualmente tendrá que estar, una vez que L1 se vuelva demasiado caro para que los usuarios lo usen aunque sea un poco), se requerirán pruebas.

¿Cómo aprende L2 la raíz del estado reciente de Ethereum?

Todos los esquemas anteriores requieren que la L2 acceda a la raíz del estado L1 reciente o a todo el estado L1 reciente. Afortunadamente, todas las L2 ya tienen alguna funcionalidad para acceder al estado L1 reciente. Esto se debe a que necesitan una funcionalidad de este tipo para procesar los mensajes que llegan de L1 a L2, sobre todo los depósitos.

Y, de hecho, si una L2 tiene una función de depósito, entonces puede usar esa L2 tal cual para mover las raíces de estado de L1 a un contrato en la L2: simplemente haga que un contrato en L1 llame al código de operación BLOCKHASH y páselo a L2 como un mensaje de depósito. Se puede recibir el encabezado de bloque completo y extraer su raíz de estado en el lado L2. Sin embargo, sería mucho mejor que cada L2 tuviera una forma explícita de acceder directamente al estado L1 reciente completo o a las raíces del estado L1 reciente.

El principal desafío con la optimización de la forma en que las L2 reciben raíces de estado L1 recientes es lograr simultáneamente seguridad y baja latencia:

  • Si las L2 implementan la funcionalidad de "lectura directa de L1" de forma perezosa, leyendo solo las raíces de estado L1 finalizadas, entonces el retraso normalmente será de 15 minutos, pero en el caso extremo de fugas de inactividad (que hay que tolerar), el retraso podría ser de varias semanas.
  • Las L2 pueden diseñarse para leer raíces de estado L1 mucho más recientes, pero debido a que L1 puede revertirse (incluso con la finalidad de una sola ranura, las reversiones pueden ocurrir durante las fugas de inactividad), L2 también tendría que poder revertirse. Esto es técnicamente desafiante desde una perspectiva de ingeniería de software, pero al menos Optimism ya tiene esta capacidad.
  • Si utiliza el puente de depósito para llevar las raíces del estado L1 a L2, entonces la viabilidad económica simple podría requerir mucho tiempo entre las actualizaciones del depósito: si el costo total de un depósito es de 100,000 gas, y asumimos que ETH está en $ 1800, y las tarifas son de 200 gwei, y las raíces L1 se llevan a L2 una vez al día, eso supondría un coste de 36 dólares por L2 al día, o 13148 dólares por L2 al año para mantener el sistema. Con un retraso de una hora, eso sube a $ 315,569 por L2 por año. En el mejor de los casos, un goteo constante de usuarios adinerados impacientes cubre las tarifas de actualización y mantiene el sistema actualizado para todos los demás. En el peor de los casos, algún actor altruista tendría que pagarlo él mismo.
  • Los "oráculos" (al menos, el tipo de tecnología que algunas personas de defi llaman "oráculos") no son una solución aceptable aquí: la administración de claves de billetera es una funcionalidad de bajo nivel muy crítica para la seguridad, por lo que debería depender como máximo de unas pocas piezas de infraestructura de bajo nivel muy simple y criptográficamente confiable.

Además, en sentido inverso (L1s leyendo L2):

  • En los rollups optimistas, las raíces estatales tardan una semana en llegar a L1 debido a la demora a prueba de fraude. En los rollups ZK se tarda unas horas por ahora debido a una combinación de tiempos de prueba y límites económicos, aunque la tecnología futura reducirá esto.
  • Las confirmaciones previas (de secuenciadores, certificadores, etc.) no son una solución aceptable para la lectura de L1 L2. La gestión de carteras es una funcionalidad de bajo nivel muy crítica para la seguridad, por lo que el nivel de seguridad de la comunicación L2 -> L1 debe ser absoluto: ni siquiera debería ser posible insertar una raíz de estado L1 falsa tomando el control del conjunto de validadores L2. Las únicas raíces estatales en las que la L1 debe confiar son las raíces estatales que han sido aceptadas como definitivas por el contrato de tenencia de raíces estatales de la L2 en la L1.

Algunas de estas velocidades para las operaciones de cadena cruzada sin confianza son inaceptablemente lentas para muchos casos de uso de Defi; Para esos casos, necesita puentes más rápidos con modelos de seguridad más imperfectos. Sin embargo, para el caso de uso de la actualización de las claves de la billetera, los retrasos más largos son más aceptables: no está retrasando las transacciones por horas, está retrasando los cambios de clave. Solo tendrás que mantener las llaves viejas por más tiempo. Si está cambiando las claves porque las roban, entonces tiene un período significativo de vulnerabilidad, pero esto se puede mitigar, por ejemplo. por billeteras que tienen una función de congelación.

En última instancia, la mejor solución para minimizar la latencia es que las L2 implementen la lectura directa de las raíces de estado L1 de una manera óptima, donde cada bloque L2 (o el registro de cálculo de la raíz de estado) contiene un puntero al bloque L1 más reciente, por lo que si L1 se revierte, L2 también puede revertirse. Los contratos de almacén de claves deben colocarse en la red principal o en las L2 que son ZK-rollups y, por lo tanto, pueden confirmarse rápidamente en L1.

Los bloques de la cadena L2 pueden tener dependencias no solo de bloques L2 anteriores, sino también de un bloque L1. Si L1 revierte más allá de dicho enlace, L2 también se revierte. Vale la pena señalar que así es también como se previó que funcionara una versión anterior (anterior a Dank) de la fragmentación; Consulte aquí el código.

¿Cuánta conexión a Ethereum necesita otra cadena para contener billeteras cuyos almacenes de claves están arraigados en Ethereum o en una L2?

Sorprendentemente, no tanto. En realidad, ni siquiera es necesario que sea un rollup: si es un L3 o un validium, entonces está bien mantener billeteras allí, siempre que tenga almacenes de claves en L1 o en un rollup ZK. Lo que sí se necesita es que la cadena tenga acceso directo a las raíces del estado de Ethereum, y un compromiso técnico y social para estar dispuesta a reorganizarse si Ethereum se reorganiza, y a una bifurcación dura si Ethereum se bifurca.

Un problema de investigación interesante es identificar hasta qué punto es posible que una cadena tenga esta forma de conexión con muchas otras cadenas (p. ej. Ethereum y Zcash). Hacerlo ingenuamente es posible: su cadena podría acordar la reorganización si Ethereum o Zcash se reorganizan (y la bifurcación dura si Ethereum o Zcash se bifurcan duramente), pero entonces los operadores de sus nodos y su comunidad en general tienen el doble de dependencias técnicas y políticas. Por lo tanto, esta técnica podría usarse para conectarse a algunas otras cadenas, pero a un costo cada vez mayor. Los esquemas basados en puentes ZK tienen propiedades técnicas atractivas, pero tienen la debilidad clave de que no son robustos a los ataques del 51% o a las bifurcaciones duras. Puede haber soluciones más inteligentes.

Preservar la privacidad

Idealmente, también queremos preservar la privacidad. Si tiene muchas billeteras administradas por el mismo almacén de claves, queremos asegurarnos de que:

  • No se sabe públicamente que todas esas billeteras están conectadas entre sí.
  • Los guardianes de la recuperación social no aprenden cuáles son las direcciones que están protegiendo.

Esto crea algunos problemas:

  • No podemos utilizar las pruebas de Merkle directamente, porque no preservan la privacidad.
  • Si usamos KZG o SNARK, entonces la prueba debe proporcionar una versión oculta de la clave de verificación, sin revelar la ubicación de la clave de verificación.
  • Si usamos agregación, entonces el agregador no debería aprender la ubicación en texto plano; Más bien, el agregador debe recibir pruebas ciegas y tener una forma de agregarlas.
  • No podemos usar la "versión ligera" (usar pruebas de cadena cruzada solo para actualizar las claves), porque crea una fuga de privacidad: si muchas billeteras se actualizan al mismo tiempo debido a un procedimiento de actualización, el momento filtra la información de que esas billeteras probablemente estén relacionadas. Por lo tanto, tenemos que usar la "versión pesada" (pruebas de cadena cruzada para cada transacción).

Con los SNARK, las soluciones son conceptualmente fáciles: las pruebas ocultan información de forma predeterminada y el agregador necesita producir un SNARK recursivo para probar los SNARK.

El principal desafío de este enfoque hoy en día es que la agregación requiere que el agregador cree un SNARK recursivo, que actualmente es bastante lento.

Con KZG, podemos usar <a href="https://notes.ethereum.org/@vbuterin/non_index_revealing_proof">this trabajo sobre pruebas de KZG que no revelan índices (véase también: una versión más formalizada de ese trabajo en el artículo de Calulk ) como punto de partida. Sin embargo, la agregación de pruebas ciegas es un problema abierto que requiere más atención.

La lectura directa de L1 desde dentro de L2, desafortunadamente, no preserva la privacidad, aunque la implementación de la funcionalidad de lectura directa sigue siendo muy útil, tanto para minimizar la latencia como por su utilidad para otras aplicaciones.

Resumen

  • Para tener billeteras de recuperación social entre cadenas, el flujo de trabajo más realista es una billetera que mantenga un almacén de claves en una ubicación y billeteras en muchas ubicaciones, donde la billetera lee el almacén de claves (i) para actualizar su vista local de la clave de verificación o (ii) durante el proceso de verificación de cada transacción.
  • Un ingrediente clave para que esto sea posible son las pruebas de cadena cruzada. Necesitamos optimizar estas pruebas con fuerza. Ya sea los ZK-SNARK, a la espera de las pruebas de Verkle, o una solución KZG hecha a medida, parecen las mejores opciones.
  • A largo plazo, serán necesarios protocolos de agregación en los que los agrupadores generan pruebas agregadas como parte de la creación de un paquete de todas las UserOperations que han enviado los usuarios para minimizar los costos. Esto probablemente debería integrarse en el ecosistema ERC-4337, aunque es probable que se requieran cambios en ERC-4337.
  • Las L2 deben optimizarse para minimizar la latencia de lectura del estado L1 (o al menos la raíz del estado) desde dentro de la L2. Los L2 que leen directamente el estado L1 son ideales y pueden ahorrar espacio de prueba.
  • Las billeteras pueden estar no solo en L2; también puede colocar billeteras en sistemas con niveles más bajos de conexión a Ethereum (L3, o incluso cadenas separadas que solo acuerdan incluir raíces de estado de Ethereum y reorganización o bifurcación dura cuando Ethereum se reorganiza o se bifurca con fuerza).
  • Sin embargo, los almacenes de claves deben estar en L1 o en L2 ZK-rollup de alta seguridad. Estar en L1 ahorra mucha complejidad, aunque a largo plazo incluso eso puede ser demasiado caro, de ahí la necesidad de almacenes de claves en L2.
  • Preservar la privacidad requerirá trabajo adicional y hará que algunas opciones sean más difíciles. Sin embargo, probablemente deberíamos avanzar hacia soluciones que preserven la privacidad de todos modos, y al menos asegurarnos de que todo lo que propongamos sea compatible con la preservación de la privacidad.

Renuncia:

  1. Este artículo es una reimpresión de [vitalik], Todos los derechos de autor pertenecen al autor original [Vitalik Buterin]. Si hay objeciones a esta reimpresión, comuníquese con el equipo de Gate Learn y ellos lo manejarán de inmediato.
  2. Descargo de responsabilidad: Los puntos de vista y opiniones expresados en este artículo son únicamente los del autor y no constituyen ningún consejo de inversión.
  3. Las traducciones del artículo a otros idiomas son realizadas por el equipo de Gate Learn. A menos que se mencione, está prohibido copiar, distribuir o plagiar los artículos traducidos.

Profundización en la lectura cruzada de L2 para billeteras y otros casos de uso

AvanzadoFeb 29, 2024
En este artículo, Vitalik aborda directamente un aspecto técnico específico de un subproblema: cómo leer más fácilmente de L2 a L1, de L1 a L2, o de una L2 a otra L2. Resolver este problema es crucial para lograr una arquitectura de separación de activos/claves, pero también tiene casos de uso valiosos en otras áreas, sobre todo en la optimización de llamadas confiables entre L2, incluidos casos de uso como mover activos entre L1 y L2.
Profundización en la lectura cruzada de L2 para billeteras y otros casos de uso

Un agradecimiento especial a Yoav Weiss, Dan Finlay, Martin Koppelmann y a los equipos de Arbitrum, Optimism, Polygon, Scroll y SoulWallet por sus comentarios y reseñas.

En esta publicación sobre las Tres Transiciones, describí algunas razones clave por las que es valioso comenzar a pensar explícitamente en el soporte L1 + Cross-L2, la seguridad de la billetera y la privacidad como características básicas necesarias de la pila del ecosistema, en lugar de construir cada una de estas cosas como complementos que pueden ser diseñados por separado por billeteras individuales.

Esta publicación se centrará más directamente en los aspectos técnicos de un subproblema específico: cómo facilitar la lectura de L1 de L2, L2 de L1 o L2 de otra L2. Resolver este problema es crucial para implementar una arquitectura de separación de activos / almacén de claves, pero también tiene casos de uso valiosos en otras áreas, sobre todo la optimización de llamadas confiables entre L2, incluidos casos de uso como mover activos entre L1 y L2.

Lecturas previas recomendadas

Tabla de contenidos

¿Cuál es el objetivo?

Una vez que las L2 se generalicen, los usuarios tendrán activos en varias L2 y, posiblemente, también en L1. Una vez que las billeteras de contratos inteligentes (multifirma, recuperación social o de otro tipo) se conviertan en la corriente principal, las claves necesarias para acceder a alguna cuenta cambiarán con el tiempo, y las claves antiguas ya no tendrían que ser válidas. Una vez que sucedan ambas cosas, un usuario deberá tener una forma de cambiar las claves que tienen autoridad para acceder a muchas cuentas que viven en muchos lugares diferentes, sin realizar un número extremadamente alto de transacciones.

En particular, necesitamos una forma de manejar las direcciones contrafácticas: direcciones que aún no han sido "registradas" de ninguna manera en la cadena, pero que, sin embargo, necesitan recibir y mantener fondos de forma segura. Todos dependemos de las direcciones contrafácticas: cuando usas Ethereum por primera vez, puedes generar una dirección ETH que alguien puede usar para pagarte, sin "registrar" la dirección en la cadena (lo que requeriría pagar txfees y, por lo tanto, ya tener algo de ETH).

Con las EOA, todas las direcciones comienzan como direcciones contrafactuales. Con las billeteras de contratos inteligentes, las direcciones contrafácticas aún son posibles, en gran parte gracias a CREATE2, que le permite tener una dirección ETH que solo puede ser completada por un contrato inteligente que tiene un código que coincide con un hash en particular.

Algoritmo de cálculo de direcciones EIP-1014 (CREATE2).

Sin embargo, las billeteras de contratos inteligentes introducen un nuevo desafío: la posibilidad de cambiar las claves de acceso. La dirección, que es un hash del código de inicio, solo puede contener la clave de verificación inicial de la billetera. La clave de verificación actual se almacenaría en el almacenamiento de la billetera, pero ese registro de almacenamiento no se propaga mágicamente a otras L2.

Si un usuario tiene muchas direcciones en muchas L2, incluidas direcciones que (porque son contrafácticas) la L2 en la que se encuentra no conoce, parece que solo hay una forma de permitir que los usuarios cambien sus claves: arquitectura de separación de activos/almacén de claves. Cada usuario tiene (i) un "contrato de almacén de claves" (en L1 o en un L2 en particular), que almacena la clave de verificación para todas las billeteras junto con las reglas para cambiar la clave, y (ii) "contratos de billetera" en L1 y muchos L2, que leen la cadena cruzada para obtener la clave de verificación.

Hay dos formas de implementar esto:

  • Versión ligera (marque solo para actualizar las claves): cada billetera almacena la clave de verificación localmente y contiene una función que se puede llamar para verificar una prueba entre cadenas del estado actual del almacén de claves y actualizar su clave de verificación almacenada localmente para que coincida. Cuando se usa una billetera por primera vez en una L2 en particular, es obligatorio llamar a esa función para obtener la clave de verificación actual del almacén de claves.
    • Ventaja: usa las pruebas de cadena cruzada con moderación, por lo que está bien si las pruebas de cadena cruzada son caras. Todos los fondos solo se pueden gastar con las claves actuales, por lo que sigue siendo seguro.
    • Desventaja: Para cambiar la clave de verificación, debe realizar un cambio de clave en la cadena tanto en el almacén de claves como en cada billetera que ya esté inicializada (aunque no contrafactual). Esto podría costar mucha gasolina.
  • Versión pesada (comprobar para cada tx): es necesaria una prueba de cadena cruzada que muestre la clave actualmente en el almacén de claves para cada transacción.
    • Ventaja: menos complejidad sistémica y la actualización del almacén de claves es barata.
    • Desventaja: caro por tx, por lo que requiere mucha más ingeniería para hacer que las pruebas de cadena cruzada sean aceptablemente baratas. Tampoco es fácilmente compatible con ERC-4337, que actualmente no admite la lectura de contratos cruzados de objetos mutables durante la validación.

¿Cómo es una prueba de cadena cruzada?

Para mostrar toda la complejidad, exploraremos el caso más difícil: donde el almacén de claves está en una L2 y la billetera está en una L2 diferente. Si el almacén de claves o la billetera están en L1, entonces solo se necesita la mitad de este diseño.

Supongamos que el almacén de claves está en Linea y la billetera está en Kakarot. Una prueba completa de las claves de la billetera consiste en:

  • Una prueba que demuestra la raíz actual del estado de Linea, dada la raíz actual del estado de Ethereum que Kakarot conoce
  • Una prueba que demuestre las claves actuales en el almacén de claves, dada la raíz de estado actual de Linea

Aquí hay dos preguntas principales y complicadas de implementación:

  1. ¿Qué tipo de pruebas utilizamos? (¿Son pruebas de Merkle? ¿Algo más?)
  2. ¿Cómo aprende la L2 la raíz del estado L1 reciente (Ethereum) (o, como veremos, potencialmente el estado L1 completo) en primer lugar? Y alternativamente, ¿cómo aprende la L1 la raíz del estado L2?
    • En ambos casos, ¿cuánto tiempo transcurren los retrasos entre que algo sucede en un lado y que esa cosa es demostrable para el otro lado?

¿Qué tipos de esquemas de prueba podemos utilizar?

Hay cinco opciones principales:

  • Pruebas de Merkle
  • ZK-SNARK de uso general
  • Las pruebas de propósito especial (p. ej. con KZG)
  • Verkle, que se encuentran en algún lugar entre KZG y ZK-SNARK tanto en la carga de trabajo como en el costo de la infraestructura.
  • No hay pruebas y confía en la lectura directa del estado

En términos de obras de infraestructura requeridas y costo para los usuarios, los clasifico aproximadamente de la siguiente manera:

"Agregación" se refiere a la idea de agregar todas las pruebas proporcionadas por los usuarios dentro de cada bloque en una gran meta-prueba que las combina todas. Esto es posible para SNARKs y para KZG, pero no para las ramas de Merkle (puede combinar un poco las ramas de Merkle, pero solo le ahorra log(txs por bloque) / log (número total de almacenes de claves), tal vez un 15-30% en la práctica, por lo que probablemente no valga la pena el costo).

La agregación solo vale la pena una vez que el esquema tiene un número sustancial de usuarios, por lo que, siendo realistas, está bien que una implementación de la versión 1 omita la agregación e implemente eso para la versión 2.

¿Cómo funcionarían las pruebas de Merkle?

Esto es simple: siga el diagrama de la sección anterior directamente. Más precisamente, cada "prueba" (asumiendo el caso de máxima dificultad de probar una L2 en otra L2) contendría:

  • Una rama de Merkle que demuestra la raíz de estado de la L2 que posee el almacén de claves, dada la raíz de estado más reciente de Ethereum que la L2 conoce. La raíz de estado de la L2 que contiene el almacén de claves se almacena en una ranura de almacenamiento conocida de una dirección conocida (el contrato en L1 que representa la L2), por lo que la ruta a través del árbol podría codificarse.
  • Una rama de Merkle que prueba las claves de verificación actuales, dada la raíz de estado de la L2 que contiene el almacén de claves. Aquí, una vez más, la clave de verificación se almacena en una ranura de almacenamiento conocida de una dirección conocida, por lo que la ruta se puede codificar.

Desafortunadamente, las pruebas de estado de Ethereum son complicadas, pero existen bibliotecas para verificarlas, y si usa estas bibliotecas, este mecanismo no es demasiado complicado de implementar.

El problema más grande es el costo. Las pruebas de Merkle son largas, y los árboles de Patricia son, por desgracia, ~3,9 veces más largas de lo necesario (precisamente: una prueba de Merkle ideal en un árbol que contiene N objetos tiene 32 log2(N) bytes de largo, y debido a que los árboles Patricia de Ethereum tienen 16 hojas por hijo, las pruebas para esos árboles son de 32 15 log16(N) ~= 125 log2(N) bytes de largo). En un estado con aproximadamente 250 millones (~2²⁸) cuentas, esto hace que cada prueba sea de 125 * 28 = 3500 bytes, o alrededor de 56,000 de gas, más costos adicionales para decodificar y verificar hashes.

Dos pruebas juntas terminarían costando alrededor de 100,000 a 150,000 gas (sin incluir la verificación de la firma si se usa por transacción), significativamente más que la base actual de 21,000 gas por transacción. Pero la disparidad empeora si la prueba se verifica en L2. La computación dentro de una L2 es barata, porque la computación se realiza fuera de la cadena y en un ecosistema con muchos menos nodos que L1. Los datos, por otro lado, tienen que ser contabilizados en L1. Por lo tanto, la comparación no es de 21000 gases frente a 150.000 gases; son 21.000 L2 de gasolina frente a 100.000 L1 de gas.

Podemos calcular lo que esto significa observando las comparaciones entre los costos del gas L1 y los costos del gas L2:

L1 es actualmente entre 15 y 25 veces más caro que L2 para envíos simples, y entre 20 y 50 veces más caro para intercambios de tokens. Los envíos simples son relativamente pesados en cuanto a datos, pero los intercambios son mucho más pesados desde el punto de vista computacional. Por lo tanto, los swaps son un mejor punto de referencia para aproximar el costo del cálculo de L1 frente al cálculo de L2. Teniendo todo esto en cuenta, si asumimos una relación de costo de 30x entre el costo de cálculo de L1 y el costo de cálculo de L2, esto parece implicar que poner una prueba de Merkle en L2 costará el equivalente a quizás cincuenta transacciones regulares.

Por supuesto, el uso de un árbol de Merkle binario puede reducir los costos en ~ 4 veces, pero aún así, el costo en la mayoría de los casos será demasiado alto, y si estamos dispuestos a hacer el sacrificio de ya no ser compatibles con el árbol de estado hexatorio actual de Ethereum, también podríamos buscar opciones aún mejores.

¿Cómo funcionarían las pruebas ZK-SNARK?

Conceptualmente, el uso de ZK-SNARKs también es fácil de entender: simplemente reemplaza las pruebas de Merkle en el diagrama anterior con un ZK-SNARK que demuestre que esas pruebas de Merkle existen. Un ZK-SNARK cuesta ~400.000 de gas de cálculo, y unos 400 bytes (compare: 21.000 de gas y 100 bytes para una transacción básica, en el futuro reducible a ~25 bytes con compresión). Por lo tanto, desde una perspectiva computacional, un ZK-SNARK cuesta 19 veces el costo de una transacción básica hoy en día, y desde una perspectiva de datos, un ZK-SNARK cuesta 4 veces más que una transacción básica hoy en día, y 16 veces más de lo que puede costar una transacción básica en el futuro.

Estos números son una mejora masiva con respecto a las pruebas de Merkle, pero siguen siendo bastante caros. Hay dos formas de mejorar esto: (i) pruebas KZG de propósito especial, o (ii) agregación, similar a la agregación ERC-4337 pero usando matemáticas más sofisticadas. Podemos investigar ambos.

¿Cómo funcionarían las pruebas KZG para fines especiales?

Advertencia, esta sección es mucho más matemática que otras secciones. Esto se debe a que vamos más allá de las herramientas de propósito general y construimos algo de propósito especial para que sea más barato, por lo que tenemos que ir mucho más "bajo el capó". Si no te gustan las matemáticas profundas, pasa directamente a la siguiente sección.

Primero, un resumen de cómo funcionan los compromisos de KZG:

  • Podemos representar un conjunto de datos [D_1 ... D_n] con una prueba KZG de un polinomio derivado de los datos: específicamente, el polinomio P donde P(w) = D_1, P(w²) = D_2 ... P(wⁿ) = D_n. w aquí es una "raíz de unidad", un valor donde wN = 1 para algún tamaño de dominio de evaluación N (todo esto se hace en un campo finito).
  • Para "comprometernos" con P, creamos un punto de curva elíptica com(P) = P₀ G + P₁ S₁ + ... + Pk * Sk. Aquí:
    • G es el punto generador de la curva
    • Pi es el coeficiente i-ésimo del polinomio P
    • Si es el i-ésimo punto en la configuración de confianza
  • Para probar P(z) = a, creamos un polinomio cociente Q = (P - a) / (X - z), y creamos un compromiso com(Q) con él. Solo es posible crear un polinomio de este tipo si P(z) es realmente igual a a.
  • Para verificar una demostración, comprobamos la ecuación Q * (X - z) = P - a haciendo una comprobación de curva elíptica en la prueba com(Q) y el compromiso polinómico com(P): comprobamos e(com(Q), com(X - z)) ?= e(com(P) - com(a), com(1))

Algunas propiedades clave que es importante comprender son:

  • Una prueba es solo el valor com(Q), que es de 48 bytes
  • com(P₁) + com(P₂) = com(P₁ + P₂)
  • Esto también significa que puede "editar" un valor en un contrato existente. Supongamos que sabemos que D_i es actualmente a, queremos establecerlo en b, y el compromiso existente con D es com(P). Un compromiso con "P, pero con P(wⁱ) = b, y no se cambiaron otras evaluaciones", entonces establecemos com(new_P) = com(P) + (b-a) * com(Li), donde Li es el "polinomio de Lagrange" que es igual a 1 en wⁱ y 0 en otros puntos wj.
  • Para realizar estas actualizaciones de manera eficiente, cada cliente puede precalcular y almacenar todos los N compromisos con polinomios de Lagrange (com(Li)) . Dentro de un contrato on-chain puede ser demasiado almacenar todos los N compromisos, por lo que en su lugar podría hacer un compromiso KZG con el conjunto de valores com(L_i) (o hash(com(L_i)), de modo que cada vez que alguien necesite actualizar el árbol on-chain simplemente pueda proporcionar el com(L_i) apropiado con una prueba de su corrección.

Por lo tanto, tenemos una estructura en la que podemos seguir agregando valores al final de una lista cada vez mayor, aunque con un cierto límite de tamaño (siendo realistas, cientos de millones podrían ser viables). A continuación, lo utilizamos como nuestra estructura de datos para gestionar (i) un compromiso con la lista de claves en cada L2, almacenado en esa L2 y reflejado en L1, y (ii) un compromiso con la lista de compromisos de claves L2, almacenado en la L1 de Ethereum y reflejado en cada L2.

Mantener los compromisos actualizados podría convertirse en parte de la lógica básica de L2, o podría implementarse sin cambios en el protocolo central de L2 a través de puentes de depósito y retiro.

Por lo tanto, una prueba completa requeriría:

  • La última com(lista de claves) en la L2 que contiene el almacén de claves (48 bytes)
  • La prueba KZG de com(lista de claves) es un valor dentro de com(mirror_list), el compromiso con la lista de todos los compromisos de la lista de claves (48 bytes)
  • Prueba KZG de su clave en com (lista de claves) (48 bytes, más 4 bytes para el índice)

De hecho, es posible fusionar las dos pruebas de KZG en una, por lo que obtenemos un tamaño total de solo 100 bytes.

Tenga en cuenta una sutileza: debido a que la lista de claves es una lista, y no un mapa clave/valor como lo es el estado, la lista de claves tendrá que asignar posiciones secuencialmente. El contrato de compromiso de claves contendría su propio registro interno que asignaría cada almacén de claves a un ID, y para cada clave almacenaría hash (clave, dirección del almacén de claves) en lugar de solo clave, para comunicar inequívocamente a otras L2 de qué almacén de claves está hablando una entrada en particular.

La ventaja de esta técnica es que funciona muy bien en L2. Los datos son de 100 bytes, ~4 veces más cortos que un ZK-SNARK y mucho más cortos que una prueba de Merkle. El costo de cálculo es en gran medida una verificación de emparejamiento de tamaño 2, o alrededor de 119,000 gas. En L1, los datos son menos importantes que la computación, por lo que desafortunadamente KZG es algo más caro que las pruebas de Merkle.

¿Cómo funcionarían los árboles Verkle?

Básicamente, los árboles de Verkle implican apilar los compromisos de KZG (o los compromisos de IPA, que pueden ser más eficientes y utilizar una criptografía más sencilla) uno encima del otro: para almacenar valores de 2⁴⁸, puede hacer un compromiso de KZG con una lista de valores de 2²⁴, cada uno de los cuales es a su vez un compromiso de KZG con valores de 2²⁴. Los árboles Verkle están siendo <a href="https://notes.ethereum.org/@vbuterin/verkle_tree_eip">fuertemente considerado para el árbol de estado de Ethereum, porque los árboles de Verkle se pueden usar para contener mapas clave-valor y no solo listas (básicamente, puede hacer un árbol de tamaño 2²⁵⁶ pero comenzarlo vacío, solo completando partes específicas del árbol una vez que realmente necesite llenarlas).

Cómo es un árbol Verkle. En la práctica, puede asignar a cada nodo un ancho de 256 == 2⁸ para los árboles basados en IPA, o 2²⁴ para los árboles basados en KZG.

Las pruebas en los árboles Verkle son algo más largas que las de KZG; Pueden tener unos pocos cientos de bytes de longitud. También son difíciles de verificar, especialmente si intentas agregar muchas pruebas en una.

Siendo realistas, los árboles Verkle deben considerarse como los árboles Merkle, pero más viables sin SNARKing (debido a los menores costos de datos) y más baratos con SNARKing (debido a los menores costos de prueba).

La mayor ventaja de los árboles de Verkle es la posibilidad de armonizar las estructuras de datos: las pruebas de Verkle se pueden usar directamente sobre el estado L1 o L2, sin estructuras superpuestas, y utilizando exactamente el mismo mecanismo para L1 y L2. Una vez que las computadoras cuánticas se conviertan en un problema, o una vez que las ramas de Merkle se vuelvan lo suficientemente eficientes, los árboles Verkle podrían reemplazarse en el lugar con un árbol hash binario con una función hash adecuada compatible con SNARK.

Agregación

Si N usuarios realizan N transacciones (o más realistamente, N ERC-4337 UserOperations) que necesitan probar N afirmaciones entre cadenas, podemos ahorrar mucho gas agregando esas pruebas: el constructor que combinaría esas transacciones en un bloque o paquete que entra en un bloque puede crear una sola prueba que pruebe todas esas afirmaciones simultáneamente.

Esto podría significar:

En los tres casos, las pruebas solo costarían unos pocos cientos de miles de gas cada una. El constructor tendría que hacer uno de estos en cada L2 para los usuarios de esa L2; por lo tanto, para que esto sea útil de construir, el esquema en su conjunto debe tener suficiente uso como para que muy a menudo haya al menos algunas transacciones dentro del mismo bloque en múltiples L2 principales.

Si se utilizan ZK-SNARK, el principal costo marginal es simplemente la "lógica comercial" de pasar números entre contratos, por lo que tal vez unos pocos miles de L2 de gas por usuario. Si se utilizan pruebas múltiples KZG, el probador necesitaría agregar 48 gas por cada L2 que tenga un almacén de claves que se use dentro de ese bloque, por lo que el costo marginal del esquema por usuario agregaría otro ~ 800 gas L1 por L2 (no por usuario) en la parte superior. Pero estos costos son mucho más bajos que los costos de no agregar, que inevitablemente involucran más de 10,000 gas L1 y cientos de miles de gas L2 por usuario. Para los árboles Verkle, puede usar Verkle multi-proofs directamente, agregando alrededor de 100-200 bytes por usuario, o puede hacer un ZK-SNARK de un Verkle multi-proof, que tiene costos similares a los ZK-SNARK de las ramas de Merkle pero es significativamente más barato de probar.

Desde el punto de vista de la implementación, probablemente sea mejor que los agrupadores agreguen pruebas de cadena cruzada a través del estándar de abstracción de cuentas ERC-4337 . ERC-4337 ya tiene un mecanismo para que los constructores agreguen partes de UserOperations de forma personalizada. Incluso hay una <a href="https://hackmd.io/@voltrevo/BJ0QBy3zi">implementación de esto para la agregación de firmas BLS, que podría reducir los costos de gas en L2 de 1,5 a 3 veces, dependiendo de qué otras formas de compresión se incluyan.

Diagrama de una publicaciónde implementaciónde billetera <a href=" https://hackmd.io/@voltrevo /BJ0QBy3zi">BLS que muestra el flujo de trabajo de las firmas agregadas de BLS dentro de una versión anterior de ERC-4337. Es probable que el flujo de trabajo de agregación de pruebas entre cadenas sea muy similar.

Lectura directa del estado

Una última posibilidad, y que solo se puede utilizar para L2 que lee L1 (y no L1 que lee L2), es modificar L2 para permitirles realizar llamadas estáticas a contratos en L1 directamente.

Esto se puede hacer con un código de operación o una precompilación, que permite llamadas a L1 donde proporciona la dirección de destino, gas y calldata, y devuelve la salida, aunque debido a que estas llamadas son llamadas estáticas, en realidad no pueden cambiar ningún estado L1. Los L2 tienen que ser conscientes de L1 ya para procesar depósitos, por lo que no hay nada fundamental que impida que se implemente tal cosa; es principalmente un desafío de implementación técnica (ver: esta RFP de Optimism para admitir llamadas estáticas en L1).

Tenga en cuenta que si el almacén de claves está en L1 y las L2 integran la funcionalidad de llamada estática L1, ¡no se requieren pruebas en absoluto! Sin embargo, si las L2 no integran llamadas estáticas L1, o si el almacén de claves está en L2 (lo que eventualmente tendrá que estar, una vez que L1 se vuelva demasiado caro para que los usuarios lo usen aunque sea un poco), se requerirán pruebas.

¿Cómo aprende L2 la raíz del estado reciente de Ethereum?

Todos los esquemas anteriores requieren que la L2 acceda a la raíz del estado L1 reciente o a todo el estado L1 reciente. Afortunadamente, todas las L2 ya tienen alguna funcionalidad para acceder al estado L1 reciente. Esto se debe a que necesitan una funcionalidad de este tipo para procesar los mensajes que llegan de L1 a L2, sobre todo los depósitos.

Y, de hecho, si una L2 tiene una función de depósito, entonces puede usar esa L2 tal cual para mover las raíces de estado de L1 a un contrato en la L2: simplemente haga que un contrato en L1 llame al código de operación BLOCKHASH y páselo a L2 como un mensaje de depósito. Se puede recibir el encabezado de bloque completo y extraer su raíz de estado en el lado L2. Sin embargo, sería mucho mejor que cada L2 tuviera una forma explícita de acceder directamente al estado L1 reciente completo o a las raíces del estado L1 reciente.

El principal desafío con la optimización de la forma en que las L2 reciben raíces de estado L1 recientes es lograr simultáneamente seguridad y baja latencia:

  • Si las L2 implementan la funcionalidad de "lectura directa de L1" de forma perezosa, leyendo solo las raíces de estado L1 finalizadas, entonces el retraso normalmente será de 15 minutos, pero en el caso extremo de fugas de inactividad (que hay que tolerar), el retraso podría ser de varias semanas.
  • Las L2 pueden diseñarse para leer raíces de estado L1 mucho más recientes, pero debido a que L1 puede revertirse (incluso con la finalidad de una sola ranura, las reversiones pueden ocurrir durante las fugas de inactividad), L2 también tendría que poder revertirse. Esto es técnicamente desafiante desde una perspectiva de ingeniería de software, pero al menos Optimism ya tiene esta capacidad.
  • Si utiliza el puente de depósito para llevar las raíces del estado L1 a L2, entonces la viabilidad económica simple podría requerir mucho tiempo entre las actualizaciones del depósito: si el costo total de un depósito es de 100,000 gas, y asumimos que ETH está en $ 1800, y las tarifas son de 200 gwei, y las raíces L1 se llevan a L2 una vez al día, eso supondría un coste de 36 dólares por L2 al día, o 13148 dólares por L2 al año para mantener el sistema. Con un retraso de una hora, eso sube a $ 315,569 por L2 por año. En el mejor de los casos, un goteo constante de usuarios adinerados impacientes cubre las tarifas de actualización y mantiene el sistema actualizado para todos los demás. En el peor de los casos, algún actor altruista tendría que pagarlo él mismo.
  • Los "oráculos" (al menos, el tipo de tecnología que algunas personas de defi llaman "oráculos") no son una solución aceptable aquí: la administración de claves de billetera es una funcionalidad de bajo nivel muy crítica para la seguridad, por lo que debería depender como máximo de unas pocas piezas de infraestructura de bajo nivel muy simple y criptográficamente confiable.

Además, en sentido inverso (L1s leyendo L2):

  • En los rollups optimistas, las raíces estatales tardan una semana en llegar a L1 debido a la demora a prueba de fraude. En los rollups ZK se tarda unas horas por ahora debido a una combinación de tiempos de prueba y límites económicos, aunque la tecnología futura reducirá esto.
  • Las confirmaciones previas (de secuenciadores, certificadores, etc.) no son una solución aceptable para la lectura de L1 L2. La gestión de carteras es una funcionalidad de bajo nivel muy crítica para la seguridad, por lo que el nivel de seguridad de la comunicación L2 -> L1 debe ser absoluto: ni siquiera debería ser posible insertar una raíz de estado L1 falsa tomando el control del conjunto de validadores L2. Las únicas raíces estatales en las que la L1 debe confiar son las raíces estatales que han sido aceptadas como definitivas por el contrato de tenencia de raíces estatales de la L2 en la L1.

Algunas de estas velocidades para las operaciones de cadena cruzada sin confianza son inaceptablemente lentas para muchos casos de uso de Defi; Para esos casos, necesita puentes más rápidos con modelos de seguridad más imperfectos. Sin embargo, para el caso de uso de la actualización de las claves de la billetera, los retrasos más largos son más aceptables: no está retrasando las transacciones por horas, está retrasando los cambios de clave. Solo tendrás que mantener las llaves viejas por más tiempo. Si está cambiando las claves porque las roban, entonces tiene un período significativo de vulnerabilidad, pero esto se puede mitigar, por ejemplo. por billeteras que tienen una función de congelación.

En última instancia, la mejor solución para minimizar la latencia es que las L2 implementen la lectura directa de las raíces de estado L1 de una manera óptima, donde cada bloque L2 (o el registro de cálculo de la raíz de estado) contiene un puntero al bloque L1 más reciente, por lo que si L1 se revierte, L2 también puede revertirse. Los contratos de almacén de claves deben colocarse en la red principal o en las L2 que son ZK-rollups y, por lo tanto, pueden confirmarse rápidamente en L1.

Los bloques de la cadena L2 pueden tener dependencias no solo de bloques L2 anteriores, sino también de un bloque L1. Si L1 revierte más allá de dicho enlace, L2 también se revierte. Vale la pena señalar que así es también como se previó que funcionara una versión anterior (anterior a Dank) de la fragmentación; Consulte aquí el código.

¿Cuánta conexión a Ethereum necesita otra cadena para contener billeteras cuyos almacenes de claves están arraigados en Ethereum o en una L2?

Sorprendentemente, no tanto. En realidad, ni siquiera es necesario que sea un rollup: si es un L3 o un validium, entonces está bien mantener billeteras allí, siempre que tenga almacenes de claves en L1 o en un rollup ZK. Lo que sí se necesita es que la cadena tenga acceso directo a las raíces del estado de Ethereum, y un compromiso técnico y social para estar dispuesta a reorganizarse si Ethereum se reorganiza, y a una bifurcación dura si Ethereum se bifurca.

Un problema de investigación interesante es identificar hasta qué punto es posible que una cadena tenga esta forma de conexión con muchas otras cadenas (p. ej. Ethereum y Zcash). Hacerlo ingenuamente es posible: su cadena podría acordar la reorganización si Ethereum o Zcash se reorganizan (y la bifurcación dura si Ethereum o Zcash se bifurcan duramente), pero entonces los operadores de sus nodos y su comunidad en general tienen el doble de dependencias técnicas y políticas. Por lo tanto, esta técnica podría usarse para conectarse a algunas otras cadenas, pero a un costo cada vez mayor. Los esquemas basados en puentes ZK tienen propiedades técnicas atractivas, pero tienen la debilidad clave de que no son robustos a los ataques del 51% o a las bifurcaciones duras. Puede haber soluciones más inteligentes.

Preservar la privacidad

Idealmente, también queremos preservar la privacidad. Si tiene muchas billeteras administradas por el mismo almacén de claves, queremos asegurarnos de que:

  • No se sabe públicamente que todas esas billeteras están conectadas entre sí.
  • Los guardianes de la recuperación social no aprenden cuáles son las direcciones que están protegiendo.

Esto crea algunos problemas:

  • No podemos utilizar las pruebas de Merkle directamente, porque no preservan la privacidad.
  • Si usamos KZG o SNARK, entonces la prueba debe proporcionar una versión oculta de la clave de verificación, sin revelar la ubicación de la clave de verificación.
  • Si usamos agregación, entonces el agregador no debería aprender la ubicación en texto plano; Más bien, el agregador debe recibir pruebas ciegas y tener una forma de agregarlas.
  • No podemos usar la "versión ligera" (usar pruebas de cadena cruzada solo para actualizar las claves), porque crea una fuga de privacidad: si muchas billeteras se actualizan al mismo tiempo debido a un procedimiento de actualización, el momento filtra la información de que esas billeteras probablemente estén relacionadas. Por lo tanto, tenemos que usar la "versión pesada" (pruebas de cadena cruzada para cada transacción).

Con los SNARK, las soluciones son conceptualmente fáciles: las pruebas ocultan información de forma predeterminada y el agregador necesita producir un SNARK recursivo para probar los SNARK.

El principal desafío de este enfoque hoy en día es que la agregación requiere que el agregador cree un SNARK recursivo, que actualmente es bastante lento.

Con KZG, podemos usar <a href="https://notes.ethereum.org/@vbuterin/non_index_revealing_proof">this trabajo sobre pruebas de KZG que no revelan índices (véase también: una versión más formalizada de ese trabajo en el artículo de Calulk ) como punto de partida. Sin embargo, la agregación de pruebas ciegas es un problema abierto que requiere más atención.

La lectura directa de L1 desde dentro de L2, desafortunadamente, no preserva la privacidad, aunque la implementación de la funcionalidad de lectura directa sigue siendo muy útil, tanto para minimizar la latencia como por su utilidad para otras aplicaciones.

Resumen

  • Para tener billeteras de recuperación social entre cadenas, el flujo de trabajo más realista es una billetera que mantenga un almacén de claves en una ubicación y billeteras en muchas ubicaciones, donde la billetera lee el almacén de claves (i) para actualizar su vista local de la clave de verificación o (ii) durante el proceso de verificación de cada transacción.
  • Un ingrediente clave para que esto sea posible son las pruebas de cadena cruzada. Necesitamos optimizar estas pruebas con fuerza. Ya sea los ZK-SNARK, a la espera de las pruebas de Verkle, o una solución KZG hecha a medida, parecen las mejores opciones.
  • A largo plazo, serán necesarios protocolos de agregación en los que los agrupadores generan pruebas agregadas como parte de la creación de un paquete de todas las UserOperations que han enviado los usuarios para minimizar los costos. Esto probablemente debería integrarse en el ecosistema ERC-4337, aunque es probable que se requieran cambios en ERC-4337.
  • Las L2 deben optimizarse para minimizar la latencia de lectura del estado L1 (o al menos la raíz del estado) desde dentro de la L2. Los L2 que leen directamente el estado L1 son ideales y pueden ahorrar espacio de prueba.
  • Las billeteras pueden estar no solo en L2; también puede colocar billeteras en sistemas con niveles más bajos de conexión a Ethereum (L3, o incluso cadenas separadas que solo acuerdan incluir raíces de estado de Ethereum y reorganización o bifurcación dura cuando Ethereum se reorganiza o se bifurca con fuerza).
  • Sin embargo, los almacenes de claves deben estar en L1 o en L2 ZK-rollup de alta seguridad. Estar en L1 ahorra mucha complejidad, aunque a largo plazo incluso eso puede ser demasiado caro, de ahí la necesidad de almacenes de claves en L2.
  • Preservar la privacidad requerirá trabajo adicional y hará que algunas opciones sean más difíciles. Sin embargo, probablemente deberíamos avanzar hacia soluciones que preserven la privacidad de todos modos, y al menos asegurarnos de que todo lo que propongamos sea compatible con la preservación de la privacidad.

Renuncia:

  1. Este artículo es una reimpresión de [vitalik], Todos los derechos de autor pertenecen al autor original [Vitalik Buterin]. Si hay objeciones a esta reimpresión, comuníquese con el equipo de Gate Learn y ellos lo manejarán de inmediato.
  2. Descargo de responsabilidad: Los puntos de vista y opiniones expresados en este artículo son únicamente los del autor y no constituyen ningún consejo de inversión.
  3. Las traducciones del artículo a otros idiomas son realizadas por el equipo de Gate Learn. A menos que se mencione, está prohibido copiar, distribuir o plagiar los artículos traducidos.
Empieza ahora
¡Regístrate y recibe un bono de
$100
!