El algoritmo Groth16 permite que un Programa Aritmético Cuadrático (QAP) sea calculado por un prover sobre puntos de curva elíptica derivados en un trusted setup, y verificado rápidamente por un verifier. Utiliza puntos auxiliares de curva elíptica del trusted setup para evitar pruebas falsificadas.
Nos referimos a un punto de curva elíptica que pertenece al grupo de curva elíptica como y a un punto de curva elíptica que pertenece al grupo de curva elíptica como . Un emparejamiento entre y se denota como y produce un elemento en . Las variables en negrita como son vectores, las letras mayúsculas en negrita como son matrices, y los elementos de campo (a veces referidos informalmente como “escalares”) son letras minúsculas como . Todas las operaciones aritméticas ocurren en un campo finito con una característica que es igual al orden del grupo de curva elíptica.
A partir de esto, podemos construir un Programa Aritmético Cuadrático (QAP):
donde
y
Si un tercero crea una cadena de referencia estructurada (srs) mediante una ceremonia de powers of tau, entonces el prover puede evaluar los términos de suma (los términos ) en el QAP en un punto oculto . Supongamos que las cadenas de referencia estructuradas se calculan de la siguiente manera:
Nos referimos a como un polinomio evaluado en una cadena de referencia estructurada mediante el producto interno:
o para el srs de :
es una abreviatura para la expresión anterior, y produce un punto de curva elíptica. No significa que el prover conozca .
El prover puede evaluar su QAP en el trusted setup calculando:
Si el QAP está equilibrado, entonces se cumple la siguiente ecuación:
Motivación
Simplemente presentar no es un argumento convincente de que el prover conoce tal que el QAP está equilibrado.
El prover simplemente puede inventar valores , , donde , calcular
y presentarlos como puntos de curva elíptica , , al verifier.
Por lo tanto, el verifier no tiene idea de si fueron el resultado de un QAP satisfecho o no.
Necesitamos forzar al prover a ser honesto sin introducir demasiada sobrecarga computacional. El primer algoritmo en lograr esto fue “Pinocchio: Nearly Practical Verifiable Computation”. Esto fue lo suficientemente utilizable como para que ZCash basara la primera versión de su blockchain en él.
Sin embargo, Groth16 logró hacer lo mismo en muchos menos pasos, y el algoritmo todavía se usa ampliamente en la actualidad, ya que ningún algoritmo desde entonces ha producido un algoritmo tan eficiente para el paso de verificación (aunque otros algoritmos han eliminado el trusted setup o reducido significativamente la cantidad de trabajo para el prover).
Actualización para 2024: Un artículo titulado de manera bastante triunfal “Polymath: Groth16 is not the limit” publicado en Cryptology demuestra un algoritmo que requiere menos pasos del verifier que Groth16. Sin embargo, no se conocen implementaciones del algoritmo al momento de escribir este documento.
Previniendo la falsificación Parte 1: Introduciendo y
Una fórmula de verificación “irresoluble”
Supongamos que actualizamos nuestra fórmula de verificación a lo siguiente:
Nota que estamos usando notación aditiva para el grupo por conveniencia.
Aquí, es un elemento de y tiene un logaritmo discreto desconocido.
Ahora mostramos que es imposible para que un verifier proporcione una solución a esta ecuación, sin conocer el logaritmo discreto de .
Ataque 1: Falsificando A y B y derivando C
Supongamos que el prover selecciona aleatoriamente y para producir y e intenta derivar un valor que sea compatible con la fórmula del verifier.
Conociendo los logaritmos discretos de y , el prover malicioso intenta resolver para haciendo
La línea final requiere que el prover resuelva el logaritmo discreto de , por lo que no puede calcular un logaritmo discreto válido para .
Ataque 2: Falsificando C y derivando A y B
Aquí el prover elige un punto aleatorio y calcula . Debido a que conoce , puede intentar descubrir una combinación compatible de y tal que
Esto requiere que el prover, dado , proponga un y un que se emparejen para producir .
Al igual que en el problema del logaritmo discreto, dependemos de suposiciones criptográficas no probadas de que este cálculo (descomponer un elemento de en un elemento de y ) es inviable. En este caso, la suposición de que no podemos descomponer en y se llama la Suposición Bilineal de Diffie-Hellman (Bilinear Diffie-Hellman Assumption). El lector interesado puede ver una discusión relacionada sobre la Decisional Diffie-Hellman Assumption.
(No probada no significa poco confiable. Si puedes encontrar una forma de probar o refutar esta suposición, ¡la fama y la fortuna te esperan! En la práctica, no se conoce una forma de descomponer en y y se cree que es computacionalmente inviable).
Cómo se usan y
En la práctica, Groth16 no utiliza un término . En cambio, el trusted setup genera dos escalares aleatorios y y publica los puntos de curva elíptica calculados como:
A lo que nos referimos como es simplemente .
Rederivando las fórmulas de prueba y verificación
Para hacer la fórmula de verificación “resoluble”, necesitamos alterar nuestra fórmula del QAP para incorporar y .
Ahora considera qué sucede si introducimos los términos y en el lado izquierdo de la ecuación:
Podemos sustituir los términos más a la derecha usando la definición original del QAP:
Ahora podemos introducir un QAP “expandido” con la siguiente definición:
Como un adelanto de hacia dónde vamos, si reemplazamos con y con , obtenemos la fórmula de verificación actualizada de antes:
donde
El prover puede calcular y sin conocer , o . Dada la cadena de referencia estructurada (potencias de ) y los puntos de curva elíptica , el prover calcula y como
Aquí, no significa que el prover conozca . El prover está usando la cadena de referencia estructurada para calcular para y el srs de para .
Sin embargo, actualmente no es posible calcular sin conocer y . El prover no puede emparejar con y con porque eso crearía un punto en , mientras que el prover necesita un punto en para .
En su lugar, el trusted setup necesita precalcular polinomios para el problemático término del QAP expandido.
Con un poco de manipulación algebraica, combinamos los términos de las sumas en una sola suma:
y factorizamos :
El trusted setup puede crear polinomios evaluados en a partir del término recuadrado anterior, y el prover puede usar eso para calcular la suma. Los detalles exactos se muestran en la siguiente sección.
Resumen del algoritmo hasta ahora
Pasos del trusted setup
Concretamente, el trusted setup calcula lo siguiente:
El trusted setup publica
Pasos del prover
El prover calcula
Nota que reemplazamos el polinomio “problemático”
(el que contenía y ) con
Pasos del verifier
El verifier calcula:
Soportando inputs públicos
La fórmula del verifier hasta ahora no soporta inputs públicos, es decir, hacer pública una porción del testigo (witness).
Por convención, las porciones públicas del witness son los primeros elementos del vector . Para hacer públicos esos elementos, el prover simplemente los revela:
Para que el verifier pruebe que esos valores fueron de hecho utilizados, el verifier debe llevar a cabo parte del cálculo que el prover estaba haciendo originalmente.
Específicamente, el prover calcula:
Nota que solo el cálculo de cambió – el prover solo usa los términos y desde hasta .
El verifier calcula los primeros términos de la suma:
Y la ecuación de verificación es:
Parte 2: Separando los inputs públicos de los inputs privados con o
Falsificando pruebas por el mal uso de para
La suposición en la ecuación anterior es que el prover solo está usando de a para calcular , pero nada impide que un prover deshonesto use de a para calcular , lo que lleva a una prueba falsificada.
Por ejemplo, aquí está nuestra ecuación de verificación actual:
Si expandimos el término C subyacente, obtenemos lo siguiente:
Supongamos, por ejemplo y sin pérdida de generalidad, que y . En ese caso, la parte pública del witness es y la parte privada es .
La ecuación final sería la siguiente:
Sin embargo, nada impide que el prover cree una porción válida del witness público como [1,2,0] y mueva la porción pública anulada a la parte privada del cálculo de la siguiente manera:
La ecuación anterior es válida, pero el witness no satisface necesariamente las restricciones originales.
Por lo tanto, necesitamos evitar que el prover use de a como parte del cálculo de .
Introduciendo y/o
Para evitar el problema anterior, el trusted setup introduce un nuevo escalar: o para forzar a que a estén separados de a . Para hacer esto, el trusted setup divide (multiplica por el inverso modular) los términos privados (que constituyen ) por y/o los términos públicos (que constituyen , la suma que calcula el verifier) por .
Dado que el término está incrustado en , esos términos también necesitan ser divididos por . Si tanto como tienen un logaritmo discreto desconocido, entonces la falsificación descrita anteriormente, junto con otros métodos posibles, se evitan. Este método se utilizó en los trusted setups basados en Sapling de Zcash, donde simplemente se deja en y todavía se actualiza desde a un valor aleatorio en etapas posteriores del trusted setup.
El trusted setup publica
Los pasos del prover son los mismos que antes:
Y los pasos del verifier ahora incluyen emparejar con y/o para cancelar los denominadores:
Parte 3: Haciendo cumplir el verdadero zero knowledge: r y s
Nuestro esquema aún no es verdaderamente zero knowledge. Si un atacante es capaz de adivinar nuestro vector witness (lo cual es posible si solo hay un rango pequeño de inputs válidos, ej. votación secreta de direcciones privilegiadas), entonces puede verificar que su suposición es correcta comparando su prueba construida con la prueba original.
Como ejemplo trivial, supongamos que nuestra afirmación es que y son ambos ya sea o . El circuito aritmético correspondiente sería
Un atacante solo necesita adivinar cuatro combinaciones para descubrir cuál es el witness. Es decir, adivinan un witness, generan una prueba y ven si su respuesta coincide con la prueba original.
Para evitar que adivinen, el prover necesita añadir “salt” a su prueba, y la ecuación de verificación debe ser modificada para acomodar el salt.
El prover muestrea dos elementos de campo aleatorios y y los añade a y para hacer el witness impredecible – un atacante tendría que adivinar tanto el witness como los salts y :
Para derivar la fórmula de verificación final, ignoremos temporalmente que no conocemos los logaritmos discretos de los términos con letras griegas y calculemos el lado izquierdo de la ecuación de verificación :
Expandiendo los términos obtenemos:
Podemos seleccionar los términos originales para
Y combinarlos a la izquierda, dejando los nuevos términos a la derecha:
Reorganizamos además los términos subrayados para escribirlos en términos de y de la siguiente manera. También dividimos en :
Agrupamos los términos con y :
Factorizamos y :
Sustituimos y :
Así que nuestra ecuación final es
Ahora la dividimos en las porciones pública y privada:
Queremos que la porción pública y la porción privada estén separadas por y respectivamente:
se cancela en algunos de los términos:
Ahora separamos esta ecuación en las porciones del verifier y del prover. Los términos recuadrados son la porción del verifier, los términos con llave inferior (underbrace) son los términos que proporciona el prover:
Algoritmo de Prueba Groth16
Ahora estamos listos para mostrar el algoritmo Groth16 de principio a fin. El trusted setup y los pasos de verificación permanecen sin cambios respecto al ejemplo anterior donde incorporamos y . Solo el cálculo del prover cambia para incorporar y .
Trusted Setup
El trusted setup publica
Pasos del Prover
El prover tiene un witness y genera escalares aleatorios y .
El prover publica .
Pasos del Verifier
El verifier comprueba
Verificando Groth16 en Solidity
En este punto, tienes suficiente conocimiento para entender el código de verificación de la prueba en Solidity. Aquí está el código de verificación de la prueba de Tornado Cash. Se anima al lector a leer el código fuente de cerca. Si el lector se siente cómodo con la programación en ensamblador (assembly) de Solidity, entonces entender este código fuente no será difícil ya que los nombres de las variables son consistentes con los de este artículo.
Intuitivamente, el atacante está multiplicando y por , y se cancela a sí mismo en el emparejamiento.
Por lo tanto, si la fórmula de verificación acepta
entonces también aceptará
La defensa contra este ataque se describe en la siguiente sección.
Puedes ver una prueba de concepto (proof of concept) de este ataque en este artículo.
El prover puede crear un número ilimitado de pruebas para el mismo witness
Esto no es un “problema de seguridad” per se – es necesario para lograr Zero Knowledge. Sin embargo, la aplicación necesita un mecanismo para rastrear qué hechos ya han sido probados y no puede depender de la singularidad de la prueba para lograrlo.
Aprende más con RareSkills
Nuestra capacidad para publicar material como este de forma gratuita depende del apoyo continuo de nuestros estudiantes. Considera inscribirte en nuestro Zero Knowledge Bootcamp, Web3 Bootcamps, o conseguir un trabajo en RareTalent.