
Hemos implementado un prototipo de transacciones anónimas basado en zkSNARK para garantizar transacciones confidenciales en la cadena de bloques Waves. En nuestra implementación, utilizamos el sistema de evidencia Groth16 en la curva BN254 y el circuito DSL.
Te explicamos cómo funciona.
zkSNARKs
zkSNARK es una primitiva criptográfica que confirma el conocimiento de un conjunto especial de datos (evidencia) correspondiente al conjunto de las siguientes ecuaciones (sistema de restricción):
⟨ai,w⟩⟨bi,w⟩+⟨ci,w⟩=0
Parte de la evidencia es privada. Esta construcción nos permite demostrar, por ejemplo, el conocimiento de la imagen inversa hash sin divulgar la imagen inversa. También se puede usar en el mecanismo de transacción privada para el modelo UTXO ( Salida de transacción no gastada (TX)), donde solo se publican hashes UTXO, y la validez de la transacción se prueba dentro de zkSNARK (prueba de propiedad, prueba de ahorro del importe).
zkSNARK es una tecnología no interactiva de divulgación cero, es decir, no implica un protocolo de interacción entre los participantes que se implementa para probar el conocimiento. En la tecnología zkSNARK, el probador construye la prueba y la envía al probador; no se requieren interacciones adicionales. El examinador puede verificar la exactitud y la corrección del uso de datos de evidencia sin recurrir a información adicional. Inicialmente, los zkSNARK se crearon como un protocolo de "computación confidencial": al calcular el resultado, los datos involucrados en los cálculos no se revelan.
Usando la tecnología zkSNARK, se puede implementar un esquema de confirmación de revelación: el probador calcula el hash, se lo entrega al probador y hace una prueba especial de que conoce la imagen inversa del hash x. Al sustituir los valores de x y hash en la fórmula, y pasar esta fórmula y la prueba al verificador, el verificador puede asegurarse de que el probador sepa x. Esta es la base para transacciones anónimas: conocemos la clave privada y alguna entrada específica (UTXO no gastado) con una cantidad específica que el usuario creó en el contrato inteligente. Sin revelar esta información, el usuario puede confirmar con un contrato inteligente que esta es su entrada, que puede deshacerse de ella y dársela a alguien para que la use.
Ahora la tecnología no se usa en todas partes, porque la prueba se genera durante varios minutos, lo que no es muy conveniente para el usuario.
Obtenga más información sobre la programación de zkSNARK en el artículo de Vitalik Buterin "Programas aritméticos cuadráticos: de cero a héroe" y en el artículo de Iden3 sobre diseño de circuitos circom.
Modelo de cuenta
Para las transacciones en Waves, generalmente usan claves y direcciones basadas en curve25519
. Esta curva no es compatible con zkSNARK, por lo que para transacciones anónimas usamos el subgrupo Edwards de curvas retorcidas de BabyJubJub
. Además, utilizamos claves públicas como direcciones, porque al enviar, debe cifrar los datos para el destinatario.
Modelo UTXO
En nuestro modelo, UTXO está representado por un conjunto de 3 parámetros: equilibrio, clave pública del propietario y secreto único. La cadena de bloques contiene solo hashes sin cifrado adicional. El propietario está representado por una clave pública y, como se señaló anteriormente, utilizamos claves públicas no en la curva curve25519
, sino en la curva BabyJubJub
con BabyJubJub
. El ID de UTXO debe generarse aleatoriamente, porque si el usuario especifica dos ID idénticos, puede recoger (gastar) UTXO solo en uno de ellos. En este caso, solo se bloqueará UTXO con la identificación correspondiente para el usuario actual, pero no para el resto. Es del interés del usuario elegir la identificación utilizando un generador de números aleatorios (se asignan 253 bits en la identificación, por lo que es difícil obtener una colisión).
Para gastar UTXO, debe publicar un anulador, una función determinista de UTXO, definida como hash (secret, owner_privkey). Este valor es determinista y exclusivo de cada UTXO; solo el propietario lo sabe. Aparte del propietario, nadie puede asociar UTXO con su anulador correspondiente.
Los UTXO se almacenan dentro del mapa hash de dApp, es decir, en el estilo de contrato. En la cadena de bloques, los UTXO están encriptados. Para tomar su dinero, el usuario debe escanear la cadena de bloques e intentar descifrar cada UTXO.
Dapp del estado
El estilo dApp contiene mapas hash que representan dos conjuntos:
Por lo tanto, dApp puede verificar la existencia de un conjunto anónimo UTXO y la unicidad de los anuladores. Esto es suficiente para procesar transferencias anónimas con protección contra la falsificación de nuevos activos y el doble gasto.
DApp tiene 3 métodos que corresponden a los tipos básicos de transacciones:
- Depositar
- Transferencia
- Conclusión
Para transferir y retirar fondos, utilizamos nuestros propios verificadores que garantizan la interacción de dApp con cuentas anónimas especiales basadas en la curva BabyJubJub. Los depósitos se procesan desde cuentas Waves normales.
Comisiones
Para el reabastecimiento de la cuenta, se cobra una tarifa desde la cuenta curve25519
. Para transferencias y retiros, la comisión se debita de la cuenta anónima. En el nivel dApp, se ve así:
dApp paga la transacción en sí, es decir, el token nativo que se gasta en pagar la comisión se debita de su saldo
Entre entradas y salidas, parte de la comisión se quema para anular los activos correspondientes a los activos reales en el contrato inteligente
En el nivel UTXO, quemamos una cierta cantidad como comisión al procesar una transacción.
Transacciones
Un depósito es una operación simple, cada depósito agrega un nuevo elemento a UTXO.

Las transferencias se basan en la primitiva de traducción 2 a 2.

Algunas entradas y salidas pueden ser cero. Como ejemplo parcial de tal construcción, se puede representar cualquier tipo de traducción simple (unir, dividir y otras transferencias, con la excepción de los intercambios atómicos).
Las conclusiones funcionan como otras transacciones, solo que en lugar de crear un segundo UTXO, permiten al usuario retirar su UTXO de dApp. También puede haber dos UTXO en la entrada, UTXO en la salida con el resto de los fondos y retirar, que se publican en la cadena de bloques.

Al retirar o transferir, dApp verifica que los anuladores correspondientes aún no se hayan encontrado en su pila.
Usando zkSNARK podemos calcular que la suma de las entradas de transacción es igual a la suma de las salidas. Al ejecutar una transacción, la gastamos en UTXO y en algún otro UTXO cero, que no está en el árbol de merkle. Para gastar UTXO, debe demostrar el conocimiento de la clave privada de su propietario. Compruebe que la clave privada, cuando se multiplica por el generador de grupo, da como resultado una clave pública. En consecuencia, sin conocer la clave privada, no se puede completar una transacción.
Conjunto anónimo
Utilizamos un conjunto anónimo de 8 elementos. El elemento de destino se selecciona de forma privada del conjunto representado en la entrada pública de zkSNARK. Este método le permite ofuscar el gráfico de transacciones (si es posible restaurar el gráfico de interacción UTXO, existe la posibilidad de desanonizar las transacciones).
Además, un simple colector de 8 elementos puede ser reemplazado por un colector de árboles Merkle. El enfoque oculta el gráfico de transacción.
Para crear una transacción válida, demostramos que gastamos algo de UTXO del conjunto de UTXO. Ponemos pruebas de merk zkSNARK y datos UTXO en entradas privadas y hash de raíz en la entrada pública. Por lo tanto, usando SNARK, demostramos que conocemos UTXO.

Protección de doble gasto
Para proteger contra el doble gasto, utilizamos anuladores, funciones deterministas que no dependen directamente del hash UTXO. Para calcular el anulador, tomamos la función de la clave privada, se demuestra que corresponde a la clave pública, id y hash UTXO. Para cada UTXO, solo hay un anulador.
Para cada transacción, el anulador de las salidas UTXO gastadas debe presentarse como una entrada pública para zkSNARK. Dentro del circuito zkSNARK, también debe confirmarse que pertenece a UTXO gastado.
Si el contrato de dApp recibe un valor de anulación no único, la transacción se rechaza. Por lo tanto, se garantiza que cada UTXO se gasta una vez.
Después de gastar UTXO, el anulador se publica en la lista de anuladores gastados en el artículo de dApp. Es decir, en hash-map, opuesto a este anulador, se establece "verdadero". Antes de publicar el anulador en el artículo, verificamos que este anulador aún no se haya utilizado, lo que significa que se puede gastar UTXO con esta identificación.