Dado un circuito aritmético codificado como un Rank 1 Constraint System, es posible crear una prueba ZK de que se tiene un testigo (witness), aunque no una que sea sucinta. Este artículo describe cómo lograrlo.
Dado un Rank 1 Constraint System donde cada matriz tiene n filas y m columnas, lo escribimos como
La∘Ra=Oa
Donde L, R, O son matrices con n filas y m columnas, y a es el vector testigo (que contiene una asignación que satisface cada una de las señales en el circuito aritmético). El vector a tiene 1 columna y m filas, y ∘ es la multiplicación elemento por elemento (producto de Hadamard).
En esta configuración, podemos demostrar a un verificador que tenemos un vector testigo a que satisface el R1CS simplemente dándole el vector a, ¡pero con la obvia desventaja de que esto no es una prueba de conocimiento cero!
Algoritmo de prueba de conocimiento cero para un R1CS.
Si “encriptamos” el vector testigo multiplicando cada entrada por G1 o G2, ¡las matemáticas seguirán funcionando correctamente!
Para entender esto, considera que si llevamos a cabo la multiplicación de matrices
[1324][45]=[1432]
y también
[1324][4G15G1]=[14G132G1]
Los logaritmos discretos de los dos puntos de la curva elíptica en la segunda multiplicación de matrices tienen el mismo valor que los elementos de la primera multiplicación de matrices.
En otras palabras, cada vez que multiplicamos el vector columna por una fila en la matriz cuadrada, llevamos a cabo dos multiplicaciones de puntos de curva elíptica y una suma de curva elíptica.
Notación para curvas elípticas
Decimos que [aG1]1 es un punto de curva elíptica en G1 creado al multiplicar el elemento del campo a por G1. Decimos que [aG2]2 es un punto de curva elíptica en G2 generado al multiplicar a con el generador G2. Debido al problema del logaritmo discreto, no podemos extraer a dado [aG1]1 o [aG2]2. Dado un punto A∈G1 y B∈G2, decimos que el emparejamiento (pairing) de los dos puntos es A∙B.
Pasos del probador
Encriptemos nuestro vector a multiplicando cada entrada con el punto generador G1 para producir el punto de curva elíptica [aiG1]1.
Anticipando que el producto de Hadamard se convierta en una lista de emparejamientos de curvas elípticas, podemos usar puntos G2 para encriptar también el vector a de modo que el verificador pueda hacer el emparejamiento.
Después de esta operación, tenemos una sola columna de puntos de curva elíptica en G1 originada por la multiplicación La y una sola columna de puntos G2 de Ra.
El siguiente paso ingenuo sería encriptar el vector a con puntos G12 para que el verificador pueda emparejar el resultado de La con Ra para ver si es igual a Oa, pero los puntos G12 son masivos, por lo que preferiríamos que el verificador empareje los puntos de curva elíptica de Oa en G1 y luego empareje cada entrada con un punto G2. Emparejar con un punto G2 es, en cierto sentido, “multiplicar por uno” pero convirtiendo el punto G1 en un punto G12.
El probador luego entrega el vector G1 y el vector G2 al verificador.
Paso de verificación
Por lo tanto, el paso de verificación se convierte en
Los vectores anteriores de elementos G12 serán iguales elemento por elemento si y solo si el probador ha proporcionado un testigo válido.
Bueno, casi. Llegaremos a eso en una sección posterior.
Primero debemos mencionar un detalle de implementación importante
Entradas públicas
Si nuestra afirmación de conocimiento es “Conozco x tal que x3+5x+5=y donde y=155”, entonces nuestro vector testigo probablemente se verá de la siguiente manera:
[1,y,x,v]
donde v=x2. En este caso, necesitamos que [1,y] sean públicos. Para lograrlo, simplemente no encriptamos los dos primeros elementos del testigo. El verificador comprobará las salidas públicas, luego encriptará las entradas públicas multiplicándolas con un punto G1 o G2 para que la fórmula de verificación no cambie.
Lidiando con un probador malicioso.
Debido a que los vectores están encriptados, el verificador no puede saber de inmediato si el vector de puntos G1 encripta los mismos valores que el vector de puntos G2.
Es decir, el probador está suministrando aG1 y aG2. Dado que el verificador no conoce los logaritmos discretos del vector de puntos, ¿cómo sabe el verificador que el vector de puntos G1 tiene los mismos logaritmos discretos que el vector de puntos G2?
El verificador puede comprobar la igualdad de los logaritmos discretos (sin conocerlos) emparejando ambos vectores de puntos con un vector del generador opuesto y comprobando que los puntos G12 resultantes sean iguales. Específicamente,
Este algoritmo es muy ineficiente para el verificador. Si las matrices en el R1CS son grandes (y para algoritmos interesantes, lo serán), entonces el verificador tiene que hacer muchos emparejamientos y sumas de curvas elípticas. La suma de curvas elípticas es bastante rápida, pero los emparejamientos de curvas elípticas son lentos (y cuestan mucho gas en Ethereum).
Sin embargo, es bueno ver que en esta etapa, las pruebas de conocimiento cero son posibles, y si tienes una buena comprensión de las operaciones de curvas elípticas (y no has olvidado tu aritmética de matrices), no son difíciles de entender.
Haciendo que esta técnica sea verdaderamente de conocimiento cero
Tal como está ahora, nuestro vector testigo no puede ser desencriptado, sin embargo, puede ser adivinado. Si un atacante (alguien que intenta descubrir el testigo sin encriptar) usa alguna información auxiliar para hacer una suposición fundamentada sobre el testigo, puede comprobar su trabajo multiplicando su vector testigo adivinado por los generadores de puntos de la curva elíptica y viendo si el resultado es el mismo que los vectores testigo del probador.
Aprenderemos cómo defendernos de la adivinación de testigos en nuestra cobertura de Groth16.
Además, ten en cuenta que nadie usa el algoritmo descrito en el mundo real, ya que es demasiado ineficiente. Sin embargo, si lo implementas, te ayudará a practicar la implementación de aritmética de curvas elípticas significativa y a construir una prueba de conocimiento cero funcional de extremo a extremo (casi).
Puedes ver un ejemplo de implementación del algoritmo descrito aquí por Obront en este repositorio.