Prueba de conocimiento cero: ¿qué son los zk-STARK y cómo funcionan? (zk-Stark V2)

Publicado el 21 oct 2024Actualizado el 10 nov 2024Lectura de 14 min33

¿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 las cantidades 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 y 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 e es 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 datos 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.

CT-web-PoR-relationship

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).

CT-web-PoR-example

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.

  1. Inicia sesión en tu cuenta de OKX, ve a Activos y selecciona informes de PoR

  2. Selecciona Detalles para ver tus datos de auditoría

    CT-web-PoR-step2
  3. Obtén los datos que necesitas para la verificación manual seleccionando Copiar datos

    CT-web-PoR-step3
  4. Después de seleccionar Copiar datos, abre el editor de texto (por ejemplo, usando el bloc de notas) 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

  5. 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"]}}

  6. Descarga la herramienta de verificación de código abierto de OKX: zk-STARKValidator

  7. 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:

    CT-web-PoR-json
  8. Abre el zk-STARKValidator y se ejecutará automáticamente el archivo JSON que guardaste en la carpeta

  9. Comprueba el resultado.

    • Si la verificación es positiva, se mostrará como resultado Inclusion constraint validation passed:

      zero-knowledge-proofs-what-are-zk-starks-and-how-do-they-work image 21
    • Si la verificación falla, se mostrará como resultado Inclusion constraint validation failed:

      zero-knowledge-proofs-what-are-zk-starks-and-how-do-they-work image 22

¿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.

  1. Ve a nuestra página Proof of reserve y selecciona Informe de pasivos

  2. Descarga el archivo zk-STARK y guárdalo en una nueva carpeta

    CT-web-PoR-self-verification-step2
  3. Descomprime el archivo para extraer un archivo "sum_proof_data.json"

    CT-web-PoR-json-sum
  4. Descarga la herramienta de verificación de código abierto de OKX: zk-STARKValidator

  5. 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:

    CT-web-PoR-json
  6. Abre el zk-STARKValidator y se ejecutará automáticamente el archivo sum proof data que guardaste en la carpeta

  7. Comprueba el resultado.

    • Si la verificación es positiva, se mostrará como resultado Total sum and non-negative constraint validation passed:

      zero-knowledge-proofs-what-are-zk-starks-and-how-do-they-work image 21
    • Si la verificación falla, se mostrará como resultado Total sum and non-negative constraint validation failed:

      zero-knowledge-proofs-what-are-zk-starks-and-how-do-they-work image 22

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.