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 G1 como [x]1 y a un punto de curva elíptica que pertenece al grupo de curva elíptica G2 como [x]2. Un emparejamiento entre [x]1 y [x]2 se denota como [x]1∙[x]2 y produce un elemento en G12. Las variables en negrita como a son vectores, las letras mayúsculas en negrita como L son matrices, y los elementos de campo (a veces referidos informalmente como “escalares”) son letras minúsculas como d. 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.
Dado un circuito aritmético (circuito ZK), lo convertimos en un sistema de restricciones de rango 1 (R1CS)La∘Ra=Oa con matrices de dimensión de n filas y m columnas con un vector testigo (witness) a. Luego, podemos convertir el R1CS a un Programa Aritmético Cuadrático (QAP) interpolando las columnas de las matrices como valores y sobre los valores x[1,2,...,n]. Dado que L, R y O tienen m columnas, terminaremos con tres conjuntos de m polinomios:
u1(x),...,um(x)v1(x),...,vm(x)w1(x),...,wm(x)m polinomios interpolados en las m columnas de Lm polinomios interpolados en las m columnas de Rm polinomios interpolados en las m columnas de O
A partir de esto, podemos construir un Programa Aritmético Cuadrático (QAP):
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 ∑aifi(x)) en el QAP en un punto oculto τ. Supongamos que las cadenas de referencia estructuradas se calculan de la siguiente manera:
[Ωn−1,Ωn−2,…,Ω2,Ω1,G1][Θn−1,Θn−2,…,Θ2,Θ1,G2][Υn−2,Υn−3,…,Υ1,Υ0]=[τnG1,τn−1G1,…,τG1,G1]=[τnG2,τn−1G2,…,τG2,G2]=[τn−2t(τ)G1,τn−3t(τ)G1,…,τt(τ)G1,t(τ)G1]srs para G1srs para G2srs para h(τ)t(τ)
Nos referimos a f(τ) como un polinomio evaluado en una cadena de referencia estructurada [τdG1,...,τ2G1,τG1,G1] mediante el producto interno:
Si el QAP está equilibrado, entonces se cumple la siguiente ecuación:
[A]1∙[B]2=?[C]1∙G2
Motivación
Simplemente presentar ([A]1,[B]2,[C]1) no es un argumento convincente de que el prover conoce a tal que el QAP está equilibrado.
El prover simplemente puede inventar valores a, b, c donde ab=c, calcular
[A]1[B]2[C]1=aG1=bG2=cG1
y presentarlos como puntos de curva elíptica [A]1, [B]2, [C]1 al verifier.
Por lo tanto, el verifier no tiene idea de si ([A]1,[B]2,[C]1) 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:
[A]1∙[B]2=?[D]12+[C]1∙G2
Nota que estamos usando notación aditiva para el grupo G12 por conveniencia.
Aquí, [D]12 es un elemento de G12 y tiene un logaritmo discreto desconocido.
Ahora mostramos que es imposible para que un verifier proporcione una solución ([A]1,[B]2,[C]1) a esta ecuación, sin conocer el logaritmo discreto de [D]12.
Ataque 1: Falsificando A y B y derivando C
Supongamos que el prover selecciona aleatoriamente a’ y b’ para producir [A]1 y [B]2 e intenta derivar un valor [C’] que sea compatible con la fórmula del verifier.
[A]1∙[B]2=?[D]12+[C]1∙G2
Conociendo los logaritmos discretos de [A]1 y [B]2, el prover malicioso intenta resolver para [C’] haciendo
La línea final requiere que el prover resuelva el logaritmo discreto de χ12, por lo que no puede calcular un logaritmo discreto válido para [C′]1.
Ataque 2: Falsificando C y derivando A y B
Aquí el prover elige un punto aleatorio c′ y calcula [C′]1. Debido a que conoce c′, puede intentar descubrir una combinación compatible de a′ y b′ tal que
Esto requiere que el prover, dado [ζ]12, proponga un [A]1 y un [B]2 que se emparejen para producir [ζ]12.
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 G12 en un elemento de G1 y G2) es inviable. En este caso, la suposición de que no podemos descomponer [ζ]12 en [A]1 y [B]2 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 [ζ]12 en [A]1 y [B]2 y se cree que es computacionalmente inviable).
Cómo se usan α y β
En la práctica, Groth16 no utiliza un término [D]12. En cambio, el trusted setup genera dos escalares aleatorios α y β y publica los puntos de curva elíptica ([α]1,[β]2) calculados como:
[α]1[β]2=αG1=βG2
A lo que nos referimos como [D]12 es simplemente [α]1∙[β]2.
Rederivando las fórmulas de prueba y verificación
Para hacer la fórmula de verificación [A]1∙[B]2=?[α]1∙[β]2+[C]1∙G2 “resoluble”, necesitamos alterar nuestra fórmula del QAP para incorporar α y β.
El prover puede calcular [A]1 y [B]2 sin conocer τ, α o β. Dada la cadena de referencia estructurada (potencias de τ) y los puntos de curva elíptica ([α]1,[β]2), el prover calcula [A]1 y [B]2 como
Aquí, aiui(τ) no significa que el prover conozca τ. El prover está usando la cadena de referencia estructurada [τn−1G1,τn−2G1,…,τG1,G1] para calcular ui(τ) para i=1,2,…,m y el srs de G2 para [B]2.
Sin embargo, actualmente no es posible calcular [C]1 sin conocer α y β. El prover no puede emparejar [α]1 con ∑aiui(τ) y [β]2 con ∑aivi(τ) porque eso crearía un punto en G12, mientras que el prover necesita un punto en G1 para [C]1.
En su lugar, el trusted setup necesita precalcular m polinomios para el problemático término C del QAP expandido.
αi=1∑maivi(τ)+βi=1∑maiui(τ)+i=1∑maiwi(τ)
Con un poco de manipulación algebraica, combinamos los términos de las sumas en una sola suma:
=i=1∑m(αaivi(τ)+βaiui(τ)+aiwi(τ))
y factorizamos ai:
=i=1∑mai(αvi(τ)+βui(τ)+wi(τ))
El trusted setup puede crear m 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:
α,β,τ[τn−1G1,τn−2G1,…,τG1,G1][τn−1G2,τn−2G2,…,τG2,G2][τn−2t(τ)G1,τn−3t(τ)G1,…,τt(τ)G1,t(τ)G1][Ψ1]1[Ψ2]1[Ψm]1←escalares aleatorios←srs para G1←srs para G2←srs para h(τ)t(τ)=(αv1(τ)+βu1(τ)+w1(τ))G1=(αv2(τ)+βu2(τ)+w2(τ))G1⋮=(αvm(τ)+βum(τ)+wm(τ))G1
El trusted setup publica
([α]1,[β]2,srsG1,srsG2,srs para h(τ)t(τ),[Ψ1]1,[Ψ2]1,…,[Ψm]1)
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 a. Para hacer públicos esos elementos, el prover simplemente los revela:
[a1,a2,…,aℓ]
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.
Nota que solo el cálculo de [C]1 cambió – el prover solo usa los términos ai y Ψi desde ℓ+1 hasta m.
El verifier calcula los primeros ℓ términos de la suma:
[X]1=i=1∑ℓaiΨi
Y la ecuación de verificación es:
[A]1∙[B]2=?[α]1∙[β]2+[X]1∙G2+[C]1∙G2
Parte 2: Separando los inputs públicos de los inputs privados con γ o δ
Falsificando pruebas por el mal uso de Ψi para i≤ℓ
La suposición en la ecuación anterior es que el prover solo está usando de Ψℓ+1 a Ψm para calcular [C]1, pero nada impide que un prover deshonesto use de Ψ1 a Ψℓ para calcular [C]1, lo que lleva a una prueba falsificada.
Por ejemplo, aquí está nuestra ecuación de verificación actual:
[A]1∙[B]2=?[α]1∙[β]2+i=1∑ℓaiΨi+[C]1∙G2
Si expandimos el término C subyacente, obtenemos lo siguiente:
Supongamos, por ejemplo y sin pérdida de generalidad, que a=[1,2,3,4,5] y ℓ=3. En ese caso, la parte pública del witness es [1,2,3] y la parte privada es [4,5].
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 Ψ1 a Ψℓ como parte del cálculo de [C]1.
Introduciendo γ y/o δ
Para evitar el problema anterior, el trusted setup introduce un nuevo escalar: γ o δ para forzar a que Ψℓ+1 a Ψm estén separados de Ψ1 a Ψℓ. Para hacer esto, el trusted setup divide (multiplica por el inverso modular) los términos privados (que constituyen [C]1) por δ y/o los términos públicos (que constituyen [X]1, la suma que calcula el verifier) por γ.
Dado que el término h(τ)t(τ) está incrustado en [C]1, 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 G2 y δ todavía se actualiza desde G2 a un valor aleatorio en etapas posteriores del trusted setup.
α,β,τ,γ,δ[τn−1G1,τn−2G1,…,τG1,G1][τn−1G2,τn−2G2,…,τG2,G2][δτn−2t(τ)G1,δτn−3t(τ)G1,…,δτt(τ)G1,δt(τ)G1][Ψ1]1[Ψ2]1[Ψℓ]1[Ψℓ+1]1[Ψℓ+2]1[Ψm]1←escalares aleatorios←srs para G1←srs para G2←srs para h(τ)t(τ)porcioˊn puˊblica del witness=γαv1(τ)+βu1(τ)+w1(τ)G1=γαv2(τ)+βu2(τ)+w2(τ)G1⋮=γαvℓ(τ)+βuℓ(τ)+wℓ(τ)G1porcioˊn privada del witness=δαvℓ+1(τ)+βuℓ+1(τ)+wℓ+1(τ)G1=δαvℓ+2(τ)+βuℓ+2(τ)+wℓ+2(τ)G1⋮=δαvm(τ)+βum(τ)+wm(τ)G1
El trusted setup publica
([α]1,[β]2,[γ]2,[δ]2,srsG1,srsG2,srs para h(τ)t(τ),[Ψ1]1,[Ψ2]1,…,[Ψm]1)
Y los pasos del verifier ahora incluyen emparejar con [γ]2 y/o [δ]2 para cancelar los denominadores:
[A]1∙[B]2=?[α]1∙[β]2+[X]1∙[γ]2+[C]1∙[δ]2
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 x1 y x2 son ambos ya sea 0 o 1. El circuito aritmético correspondiente sería
x1(x1−1)=0x2(x2−1)=0
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 r y s y los añade a A y B para hacer el witness impredecible – un atacante tendría que adivinar tanto el witness como los salts r y s:
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 AB:
Reorganizamos además los términos subrayados para escribirlos en términos de Asδ y Brδ de la siguiente manera. También dividimos rδsδ en rsδ2+rsδ2−rsδ2:
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:
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 r y s.
Trusted Setup
α,β,τ,γ,δ[τn−1G1,τn−2G1,…,τG1,G1][τn−1G2,τn−2G2,…,τG2,G2][δτn−2t(τ)G1,δτn−3t(τ)G1,…,δτt(τ)G1,δt(τ)G1][Ψ1]1[Ψ2]1[Ψℓ]1[Ψℓ+1]1[Ψℓ+2]1[Ψm]1←escalares aleatorios←srs para G1←srs para G2←srs para h(τ)t(τ)porcioˊn puˊblica del witness=γαv1(τ)+βu1(τ)+w1(τ)G1=γαv2(τ)+βu2(τ)+w2(τ)G1⋮=γαvℓ(τ)+βuℓ(τ)+wℓ(τ)G1porcioˊn privada del witness=δαvℓ+1(τ)+βuℓ+1(τ)+wℓ+1(τ)G1=δαvℓ+2(τ)+βuℓ+2(τ)+wℓ+2(τ)G1⋮=δαvm(τ)+βum(τ)+wm(τ)G1
El trusted setup publica
([α]1,[β]1[β]2,[γ]2,[δ]1[δ]2,srsG1,srsG2,srs para h(τ)t(τ),[Ψ1]1,[Ψ2]1,…,[Ψm]1)
Pasos del Prover
El prover tiene un witness a y genera escalares aleatorios r y s.
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.
Las pruebas Groth16 son maleables. Dada una prueba válida
([A]1,[B]2,[C]1), un atacante puede calcular la negación del punto de [A]1 y [B]2 y presentar una nueva prueba como ([A′]1,[B′]2,[C]1) donde [A′]1=neg([A]1) y [B′]2=neg([B]2).
Para ver que [A]1∙[B]2=[A′]1∙[B′]2, considera el siguiente código:
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.