Contratos Inteligentes en Stellar

Prototipando Pools de Privacidad en Stellar

Autor

Yan Michalevsky

Fecha de publicación

Introducción

Lo siguiente describe el prototipado de Privacy Pools en la plataforma de contratos inteligentes Stellar y la red Stellar. Privacy Pools [1] es una manera de realizar transferencias preservando la privacidad mediante la gestión de un fondo de donde los depositantes pueden retirar, mientras se oscurece el vínculo entre retiros y depósitos. Un aspecto innovador de Privacy Pools es la incorporación de Proveedores de Conjunto de Asociación (ASPs) que definen criterios de inclusión destinados a ayudar a mitigar el uso ilícito. Los ASPs permiten a los participantes asociarse selectivamente con conjuntos de otros participantes que cumplen con sus estándares de cumplimiento elegidos. Las garantías de privacidad se logran utilizando técnicas criptográficas de conocimiento cero que permiten probar propiedades útiles de las transacciones sin revelar sus detalles completos.

Diseño de Privacy Pools

En lugar de idear un esquema novedoso, buscamos lograr la paridad en Stellar con capacidades que ya se implementaron para Ethereum por marcos como 0xbow [5]. Nuestro esquema sigue de cerca a Privacy Pools de Buterin et al. [1]. Privacy Pools son esencialmente un protocolo que preserva la privacidad e incorpora ASPs que especifican estándares de cumplimiento destinados a ayudar a mitigar el uso ilícito y permitir a los participantes elegir con qué ASP, y qué estándares de cumplimiento, asociarse [1].

Un mezclador (Fig. 1) oscurece el vínculo entre los que retiran y los depositantes al permitir que los retiradores prueben que previamente depositaron una cantidad suficiente de fondos en el contrato del mezclador, sin revelar el depósito exacto. Note que el grado de privacidad que disfruta un participante corresponde al conjunto de anonimato que, en nuestro caso, es el número de depósitos precediendo un retiro.

Figura 1. Un “mezclador” oscurece el vínculo entre depósitos y retiros.

Esquema del Mezclador

Permítanos detallar el esquema criptográfico que permite mezclar. El depositante genera dos números aleatorios, un secreto s, y un anulador n. Calcula un hash c=H(s, n) que se llama un compromiso. El compromiso c se proporciona al contrato junto con un depósito. El contrato almacena el compromiso como una nueva hoja en un árbol de Merkle. Para retirar los fondos, el retirador debe probar que conoce (s, n) de tal manera que H(s, n) es uno de los compromisos previamente registrados por el mezclador. El retirador no revela s o n. Solo prueban usando un SNARK que H(s, n) está incluido en el árbol de Merkle con un valor de raíz igual al calculado independientemente por el contrato del mezclador.

Para prevenir el doble gasto el retirador también debe proporcionar un hash-anulador H(n) como parte de las salidas del SNARK. El contrato del mezclador almacena los hashes anuladores, y para cada intento de retiro verifica que el hash-anulador proporcionado no esté entre los hashes anuladores previamente registrados, es decir, la moneda aún no ha sido retirada. Note que el SNARK garantiza que es el mismo n en ambos H(n) y H(s, n) proporcionados al mezclador, sin revelar el anulador n en sí mismo.

Nota: Separación de dominio entre fondos
En la práctica, para garantizar la separación de dominio entre diferentes fondos usamos una etiqueta l que es una concatenación del nombre del fondo y un nonce de 32 bytes. En lugar de c=H(s,n) calculamos c=H(H(s,n), l) como el compromiso de la moneda.

Privacy Pools

Privacy Pools extienden el diseño básico del mezclador incorporando ASPs que especifican estándares de cumplimiento destinados a ayudar a mitigar el uso ilícito. Para retirar una moneda, el receptor debe probar que calificó para un ASP y que, por lo tanto, cumplieron con los estándares de cumplimiento del ASP. Esto se hace proporcionando una prueba de inclusión de conocimiento cero. De hecho, las hojas en tal conjunto de asociación podrían ser las etiquetas-s utilizadas por los participantes para generar compromisos de monedas y mencionadas anteriormente en el contexto de separación de dominio.

Implementación

La implementación de Privacy Pools en Stellar está inspirada en 0xbow [6]. Su repositorio de Github contiene los circuitos relevantes para las pruebas ZK de propiedad de monedas e inclusión en el árbol de Merkle.

El esquema ZK es Groth16 usando una curva BLS12-381, soportado por Soroban.

Cadena de herramientas

Figura 3. Cadena de herramientas para el despliegue de contratos.

Para probar declaraciones sobre transacciones, usamos el compilador Circom. Nos permite describir cálculos con la comodidad del lenguaje Circom y construir circuitos para los cuales podemos generar pruebas de conocimiento cero. Para la máxima eficiencia de generación y verificación de pruebas, usamos el esquema SNARK Groth’16 [7]. Verificar un SNARK (por compleja que sea la declaración) requiere solo un cálculo de emparejamiento bilineal. Esto toma aproximadamente 40 millones de instrucciones en un contrato de Soroban, que es %40 del presupuesto máximo de instrucciones en testnet.

En nuestra base de código el main.circom circuito (Fig.2) se utiliza para generar el SNARK para retirar una moneda. El circuito toma como entradas los parámetros de generación de monedas (s, n). La raíz del árbol de Merkle, y los hermanos a lo largo del camino de la hoja de compromiso. Prueba que la raíz del árbol de Merkle calculada independientemente por el circuito y la raíz de entrada son iguales, y emite el hash anulador.

Figura 2. main.circom: Un circuito Circom probando que una moneda fue previamente depositada en el mezclador.

Previniendo el frontrunning por una parte retransmisora

Si verificamos ingenuamente una prueba de conocimiento cero de depositar una moneda sin verificar quién está retirando, permitimos a una parte retransmisora retirar a cualquier dirección deseada editando la transacción. La prueba de conocimiento cero debe incluir la dirección del destinatario. Esto aún no está implementado en main.circom.

Para ejecutar un verificador ZK en Soroban usamos una no_std implementación compatible de un verificador Groth16, proporcionado en https://github.com/stellar/soroban-examples/tree/main/groth16_verifier.

Adaptamos los circuitos del repositorio 0xbow y los construimos usando Circom.

Implementamos funciones de conversión para usar salidas generadas por Circom con el verificador Groth’16 (es importante asegurarnos de que usamos la misma curva (BLS12-381).

circom2soroban

Dado que Soroban solo admite un subconjunto limitado de Rust y Ark y excluye el uso de cualquier std características, hay algunos desafíos alrededor de la conversión de salidas de Circom a entradas compatibles con el módulo Verificador Groth’16. Por lo tanto, creamos una herramienta fuera de la cadena que procesaría las salidas de Circom y las serializaría, de tal manera que puedan ser fácilmente deserializadas por nuestro contrato de Soroban.

circom2soroban es una herramienta de línea de comandos escrita en Rust que convierte salidas JSON de Circom en código Rust que puede ser incrustado en un contrato inteligente de Soroban. circom2soroban acepta dos argumentos. El primero indica el tipo de entrada, ya sea una clave de verificación o una prueba. Puede ser uno de tres valores, vk (para clave de verificación), proof (el SNARK) o public (para entradas y salidas públicas). El segundo argumento es la ruta al archivo JSON que contiene la clave o prueba generada por Circom. Además de las variables de Rust que pueden ser pegadas en un contrato inteligente como parámetros codificados, circom2soroban puede generar una conversión a bytes que puede ser utilizada al construir un contrato o al invocar uno de sus métodos.

La Fig. 3 ilustra la cadena de herramientas utilizada para desplegar un contrato de mezclador, donde las constantes generadas por SnarkJS de Circom la salida del compilador se convierte usando circom2soroban y se proporciona al constructor del contrato.

coinutils

coinutils es una herramienta CLI que maneja los aspectos prácticos de la participación en el pool de privacidad. Gestiona el ciclo de vida completo de las monedas del pool de privacidad, desde la generación hasta la preparación del retiro.

El comando generate crea nuevas monedas con parámetros criptográficamente seguros. Genera un secreto s, un nulificador n, y calcula el compromiso usando el hash de Poseidón sobre elementos de campo BLS12-381. La herramienta produce formatos decimal y hexadecimal, con el formato hexadecimal listo para la integración directa en el contrato.

Para el retiro, el comando withdraw toma un archivo de moneda y un archivo de estado que contiene el árbol de Merkle de compromisos. Reconstruye el árbol de Merkle, genera las pruebas de inclusión de Merkle necesarias y produce entradas compatibles con SNARK para el circuito main.circom. Si se utilizan conjuntos de asociación para verificar la elegibilidad de participación, también genera una prueba de participación legítima.

El comando updateAssociation gestiona listas de participantes que han sido aprobados por ASPs de acuerdo a sus respectivos estándares de cumplimiento y construye árboles de Merkle para habilitar la verificación del estado de aprobación del participante. Mantiene un árbol de Merkle del conjunto de asociación con cierta profundidad, soportando hasta 2depth etiquetas por conjunto.

Todas las operaciones trabajan directamente con elementos de campo BLS12-381, asegurando la compatibilidad con el entorno de Soroban y las pruebas Groth16. La herramienta usa la representación de cadena decimal para la legibilidad humana mientras proporciona salidas hexadecimales para la integración de contrato. Se integra sin problemas con la cadena de herramientas más amplia: las monedas generadas pueden ser depositadas directamente en contratos, y las entradas de retiro están listas para la generación de pruebas de SnarkJS.

Sugerencias futuras

  • SnarkJS es una de las herramientas estándar para cosas relacionadas con ZK (generación de pruebas, generación de claves de verificación, Powers-of-Tau, etc.). Es capaz de producir código de verificación en Solidity que puede ser utilizado directamente en Ethereum (y más generalmente en contratos inteligentes compatibles con Solidity). Deberíamos considerar extender SnarkJS para generar de manera similar código de verificación compatible con Soroban.

Trabajo futuro

  • Protección contra frontrunning: incluir dirección del destinatario en la prueba ZK.
  • Almacenar las últimas N raíces en el contrato para permitir que el retirador retire mientras se realizan nuevos depósitos.
  • Funcionalidad de “rage-quit”: permitir a un usuario retirar sus fondos depositados públicamente si no fueron aprobados mediante un conjunto de asociación.