Prueba de conocimiento cero: ¿qué son los zk-STARK y cómo funcionan? (zk-Stark V2)
¿Qué es proof of reserves y zero knowledge proof?
Proof of reserves (PoR)
Se trata de un proceso en el que los exchanges cripto demuestran que tienen suficientes activos para cubrir todos los saldos de los clientes. Esto crea confianza porque demuestra que el exchange no oculta ningún pasivo. La forma más sencilla de mostrarlo es publicar tanto los montos de activos del exchange como una lista de saldos de los usuarios de forma que todos puedan comprobarlo.
Las posiciones de activos totales de los usuarios que OKX afirma tener es la suma del saldo de activos totales de cada usuario.
El saldo total de cada usuario es superior a cero y sus activos son contabilizados para cubrir sus pasivos y para garantizar que cada usuario tiene un capital neto positivo
El valor total que el exchange proclama tiene en cuenta a cada uno de los usuarios, por lo que cada usuario debe poder verificar la inclusión de su valor neto en el valor total.
Sin embargo, revelar estos saldos puede poner en peligro la privacidad de los usuarios. Para resolver este problema, utilizamos un método llamado zero knowledge proof (ZKP) o prueba de conocimiento cero con el que se protege la privacidad del usuario.
Zero knowledge proof (ZKP)
Es una técnica de seguridad que permite a los exchanges de criptomonedas demostrar que una afirmación es verdadera sin revelar información adicional.
En nuestro caso, queremos demostrar que tenemos fondos suficientes sin compartir detalles específicos de cada usuario. La mayoría de los ZKP se pueden clasificar en dos categorías:
zk-SNARK
zk-STARK
Usamos zk-STARK porque es más seguro y tiene un nivel mínimo de suposición de seguridad. En este artículo explicaremos cómo usamos zk-STARK para proteger la privacidad de los usuarios y para probar nuestra solvencia. Antes de continuar, conviene entender algunos términos básicos de ZKP, como circuit, árbol de Merkle y commitments.
Para principiantes, hay muchos recursos disponibles que te ayudarán en tus comienzos. Los usuarios avanzados pueden consultar el Curso MOOC y la monografía académica.
¿Cómo funciona zk-STARK?
Creamos un árbol de Merkle usando como hojas el hash de la cuenta de cada usuario. Cada cuenta muestra los saldos en USD para varios tokens (por ejemplo, BTC, ETH). Para gestionar estos saldos, separamos sus saldos en capital y deuda no negativos para cada token. De este modo, solo trabajamos con números positivos, lo que facilita el manejo de los cálculos y evita errores.
Por ejemplo:
si el saldo de tokens BTC de un usuario es A, su capital BTC es A y la deuda de BTC es 0 y
si el saldo de tokens ETH de un usuario es -B, su capital correspondiente es 0 y la deuda es B.
Después construimos un árbol de Merkle con estos valores de cuenta como hojas. La raíz del árbol actúa como un único valor que representa todos los saldos de los usuarios. Cada usuario puede demostrar que su cuenta es parte de este árbol utilizando una ruta de Merkle que muestra cómo su cuenta se conecta a la raíz.
También publicamos el capital total y la deuda sumados, incluyendo todos los tokens y a todos los usuarios. Después creamos una prueba de conocimiento cero (ZKP) para mostrar dos cosas.
Prueba de suma: para mostrar que los valores de capital y de deuda en el árbol de Merkle se suman correctamente.
Prueba no negativa: para mostrar que el patrimonio total de cada usuario es mayor que su deuda total.
Cuando intentamos verificar el árbol de Merkle para un gran número de cuentas, se vuelve demasiado grande como para ser manejado de una vez. Para superar este reto, dividimos las cuentas en grupos más pequeños llamados lotes. Cada lote es procesado por separado utilizando circuitos de lote que verifican la parte inferior del árbol de Merkle.
El loto no solo lo hace manejable, sino que también nos permite realizar estas comprobaciones al mismo tiempo (procesamiento en paralelo). Una vez que tengamos los resultados de cada lote, utilizamos otra capa de circuitos llamada circuitos recursivos para combinar y verificar todos los lotes juntos hasta que hayamos probado todo el árbol de Merkle.
¿Qué es el circuito de lote?
El circuito de lote toma 1024 cuentas (cuenta0, cuenta1,..., cuenta1023) como entradas y genera 3 salidas principales: un hash (loteh), un valor total de capital (lotee), y un valor total de deuda (loted). Comprueba que:
el capital nominal en USD total de cada cuenta es mayor que su deuda total,
el lote ees la suma de todos los valores de capital denominados en USD en estas cuentas,
el loted es la suma de todos los valores de deuda denominados en USD en estas cuentas,
el loteh es la raíz del árbol de Merkle creado utilizando los hashes de las cuentas,
no hay desbordamiento de flujo durante la suma para el lotee y elloted.
¿Qué es el circuito recursivo?
El circuito recursivo toma 64 pruebas diferentes (π0, ..., π63), hashes (h0, ..., h63), capital (e0, ..., e63) y deudas (d0, ..., d63) de los circuitos de capa inferior como entradas. Combina estas entradas y produce 3 salidas: un nuevo hash (hrecursivo), el capital total (erecursivo), y la deuda total (drecursiva). Comprueba que:
cada una de las 64 pruebas es válida;
cada prueba π0, ..., π63 del circuito de capa inferior es válida;
erecursivo es la suma de e0, ..., e63;
drecursivo es la suma de d0, ..., d63;
hrecursivo es el hash de la concatenación de h0, ..., h63, es decir, que
hrecursivo = hash (h0 || h1 || ... || h63);
no hay desbordamiento de flujo durante la suma para erecursivo y drecursivo.
¿Cuál es la relación entre los circuitos de lote y los circuitos recursivos?
El siguiente diagrama muestra cómo se conectan y se transmiten los detalles entre el circuito de lote y los circuitos recursivos. Ten en cuenta que en el diagrama duplicamos los circuitos con fines ilustrativos, pero en nuestra implementación solo utilizamos un circuito por capa.
Nuestro árbol de Merkle está estructurado de manera ligeramente diferente. En los últimos 10 niveles cada nodo padre tiene 2 hijos, mientras que en los niveles superiores cada padre tiene 64 hijos. Esto se debe a que los circuitos del lote gestionan la parte inferior y los circuitos recursivos gestionan la parte superior. El siguiente diagrama utiliza un ejemplo con "Alice" para mostrar el árbol de Merkle y la prueba de Merkle para ese usuario de ejemplo (en color verde).
Para más detalles técnicos, como, por ejemplo, cómo ajustamos los números de cuenta para que se adapten al tamaño del lote o cómo elegimos el algoritmo de hash adecuado, consulta esta página.
Avances en zk-PoR Version 2
Nuestra versión zk-PoR 2 supone varios avances con respecto a la versión anterior.
Mayor eficiencia: ahora es 50 veces más rápido que con la versión anterior. Tarda 3 horas en una sola máquina de 10 núcleos frente a las 36 horas de la versión anterior, que utiliza nueve máquinas de 64 núcleos. Esta aceleración es gracias al uso del marco Plonky2, que compila circuitos codificados en Rust en un lenguaje de máquina eficiente en lugar de utilizar scripts de Python más lentos. También hemos mejorado Plonky2 para ejecutar algunos cálculos en GPU, reduciendo el tiempo en un 30 % extra.
Mejor auditabilidad: con la Versión 2 utilizamos un marco de alto nivel que maneja los detalles criptográficos complejos por nosotros. Esto hace que nuestro código sea más claro, legible y menos propenso a errores.
Prueba concisa: el tamaño de la prueba de la V2 (~500 KB) es solo el 0.05 % de la V1 (~1.2 GB). Gracias al método recursivo las pruebas se pueden agregar repetidamente y condensarse en una sola prueba.
¿Cómo puedo llevar a cabo una autoverificación del proof of reserves (PoR)?
Te explicamos cómo puedes comprobar si tu saldo de activos está incluido como una hoja de Merkle zk-STARK.
Inicia sesión en tu cuenta de OKX, ve a Activos y selecciona informes de PoR
Selecciona Detalles para ver tus detalles de auditoría
Obtén los detalles que necesitas para la verificación manual seleccionando Copiar datos
Después de seleccionar Copiar datos, abre el editor de texto (por ejemplo, usando un cuaderno) y luego pega y guarda la cadena JSON como un archivo. El archivo debe terminar con el nombre "_inclusion_proof.json.". La cadena JSON contiene el saldo de su cuenta y una instantánea de la ruta Merkle, luego guarda el archivo en una nueva carpeta
Abre un editor de texto (por ejemplo, bloc de notas), después pega y guarda la cadena JSON como archivo. El nombre del archivo debe terminar con "_inclusion_proof.json.". Guarda el archivo en una nueva carpeta.
La cadena JSON contiene el saldo de su cuenta y una instantánea de la ruta Merkle.
El texto JSON aparece a continuación:
{"sum_tree_siblings":["9ffb169fecf075e203edca2af65e4c69fa4331d13ac75ccae4cd5b990c91b675","7149661a789763cb61293ebf5d8bdd5570e79ee203738f87a444c79642b89a79","788aac9e392fa62bc3f79c98c7afd7bb41ee7d5bd496876cd0580080f19e002f","e828a44d345e6799e232aabc57cb2b92986ee1c52b65344d83e79d84b4b571b7","6c0675de9cd6b2be1abd6a98260e7ea776492c4aa9aadf31086f23452cb7c48d","2dfe3aadb5ac00ee0b1110ee8c313afdee85d9f9c62904d6ee79c8f02354d80a","5068ae26192587432892a6de8b54ea25a8aafd1c010ab5e67b55b2c30c6257fa","a1bb026ec9f3d8a1fa1b6f498c40ed8b117a57e1af9816d08d9135ab4fe43a60","119dfcd214191405b7f7f7c7091b89196c0cae818bfcd8252a48f20d9cf3c378","4d9403482ca177c669df34a60bb2afab7a18097012d0b70703c8e59258cdfee6"],"recursive_tree_siblings":[{"right_hashes":["e041eaa366259f873e9e1477aac77362f4b1b460c2d5e1c14907fa9288d66cff","b45a8c503e649ff39543a918996b06fc65f4df9b61d071b22f7342f94862c9be","e00ec1225dfe6b7e950f6b9b8e9d1121bf17eb60c444fd7191b861a2ddddad23","c02c12beb73c03f996508cdce7bef927f0aa8b77ebd899f6a75df83de9d4022e","d36b95f14c5fd5bfaf1347e3177340e2fc9475a77b852321b80527132e7d539c","c0b9770178e70a7bba4ac8aeaadab2bcb2ae7f90d0f678bd463f2c42ff4f4a7b","fab5e7c6f7f8bc6d51f515c5db235cc1ebe987adee8c19c9bc7313e9e266d72c","b3884fb88fc95949c78ca8867cfa9e8a3c4c59fa1a48d8371f7fbfbebda0acfd","0c6da9bdbd40065f92ddaa45297670f2f0bffedb74020c5d5752e70d8b507b77","left_hashes":["1101beee3c6a36a168ceee9d43fcf6cb6de7e5c87ed4d22cd0308c9870d17839","d40a8e9eb4c873996ec515600def480eaa9378ca8481a7bcdf5f77725dbec4ae","63b12566ba8473f502386e92d500664cb63683dca6c26593378dcc9715257b77","166440a8ccbfbc1ce6ec5efaf8bc0b25e1bf692fa972e2729e45ce709d1d35a3","724451ad1d937fc47de5ede930d159dce78093d5e6a1f2e698452f8a29b4de3a","081a88f12d4e23173a1bf5038d4a9413cc92dd421c92261065de06492b5010ec","a76dbb1d4c393539b9546f4460d50ebc7582748d7de63c62c463b793c55bac7c","91e6c21de3f4060e1bd864131a570af42de31bbcd84a5afcbbc8fedcbf806002","fad08eca5bfdc5f37d39eabb44c2216afc6498afcb6b913d72586eaaf132a572","d39b06fe28387ba8045e2b2f95e90613916beef4f79df7961514e6e4cbfd07fa","81d07e300a116a0e4fcb56c39715c5fd5921abe8d10329b07c3f33d417b70ca8","7b72a7e62a45c9958a8a55eec2ba47352f2af701bacba098668589f6a3ce0423","8766bc64c38c2bb4188d89de0e732bca103daaed0c779cba9a8b191e24b51c9c","fa57ae4409e46c605f3cbfd01dfd9ccebc86cbd765cdc067206cb9367832442f"]}, ...... "index":9583119,"account":{"id":"50f5f08cc5036e15a541c64ac4ac6d2d9aa8ddab1ec32ed58b10e6ed3edfad59","debt":["0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"],"equity":["8412384","9386185","45265193","0","0","8751","3824171","2716990","0","313671","28319","0","0","0","41261","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","142353","0","0","0","0","0","4435","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","662","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","993","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","25132","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","305","0","0","0","0","0","0","0","0","6141","0","0","0","0","0","0","0","0","0","0","0","5511","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"]}}
Descarga la herramienta de verificación de código abierto de OKX: zk-STARKValidator
Guarda la herramienta de verificación de código abierto de OKX, zk-STARKValidator, y el archivo de cadena JSON juntos en la nueva carpeta creada en el paso 5. En nuestro caso, colocamos la herramienta y el archivo de datos en la carpeta de Descargas, llamada "proof of reserves", tal y como se muestra a continuación:
Abre el zk-STARKValidator y se ejecutará automáticamente el archivo JSON que guardaste en la carpeta
Comprueba el resultado.
Si la verificación es positiva, se mostrará como resultado Inclusion constraint validation passed:
Si la verificación falla, se mostrará como resultado Inclusion constraint validation failed:
¿Cómo puedo verificar el saldo total de zk-STARK y la restricción de no negatividad?
Aquí te explicamos cómo puedes verificar que los activos que afirmamos tener son reales y que ningún usuario tiene un patrimonio neto negativo.
Ve a nuestra página Proof of reserve y selecciona Informe de pasivos
Descarga el archivo zk-STARK y guárdalo en una nueva carpeta
Descomprime el archivo para extraer un archivo "sum_proof_data.json"
Descarga la herramienta de verificación de código abierto de OKX: zk-STARKValidator
Guarda la herramienta de verificación de código abierto de OKX zk-STARKValidator y el archivo de cadena "sum_proof_data.json" juntos en la nueva carpeta creada en el paso 2. En nuestro caso, colocamos la herramienta y el archivo de datos en la carpeta de Descargas, llamada "proof of reserves", tal y como se muestra a continuación:
Abre el zk-STARKValidator y se ejecutará automáticamente el archivo sum proof data que guardaste en la carpeta
Comprobar el resultado
Si la verificación es positiva, se mostrará como resultado Total sum and non-negative constraint validation passed:
Si la verificación falla, se mostrará como resultado Total sum and non-negative constraint validation failed:
Para explorar más detalles técnicos, nuestro sistema de proof of reserves (PoR) es de código abierto y está disponible para su revisión y su uso en Github.