Un programa aritmético cuadrático es un circuito aritmético, específicamente un Sistema de Restricciones de Rango 1 (R1CS) representado como un conjunto de polinomios. Se deriva utilizando la interpolación de Lagrange en un Sistema de Restricciones de Rango 1. A diferencia de un R1CS, un Programa Aritmético Cuadrático (QAP) puede evaluarse para comprobar su igualdad en tiempo mediante el Lema de Schwartz-Zippel.
Ideas clave
En el capítulo sobre el Lema de Schwartz-Zippel, vimos que podemos comprobar si dos vectores son iguales en tiempo convirtiéndolos en polinomios y luego ejecutando la prueba del Lema de Schwartz-Zippel sobre los polinomios. (Para aclarar, la prueba se ejecuta en tiempo , convertir los vectores a polinomios genera una sobrecarga).
Dado que un Sistema de Restricciones de Rango 1 está compuesto en su totalidad por operaciones vectoriales, nuestro objetivo es comprobar si
se cumple en tiempo en lugar de tiempo (donde es el número de filas en , y ).
Pero antes de hacer eso, necesitamos entender algunas propiedades clave de la relación entre los vectores y los polinomios que los representan.
Para todas las matemáticas aquí presentadas, asumimos que estamos trabajando en un campo finito, pero omitiremos la notación por motivos de concisión.
Homomorfismos entre la suma de vectores y la suma de polinomios
La suma de vectores es homomórfica a la suma de polinomios
Si tomamos dos vectores, los interpolamos con polinomios y luego sumamos los polinomios resultantes, obtenemos el mismo polinomio que si sumáramos los vectores entre sí y luego interpoláramos el vector suma.
Expresado de forma más matemática, sea el polinomio resultante de la interpolación de Lagrange sobre el vector utilizando como los valores de , donde es la longitud de . Lo siguiente es cierto:
En otras palabras, la suma de los polinomios resultantes de interpolar los vectores y es igual al polinomio resultante de interpolar la suma de los vectores .
Ejemplo resuelto
Sean y . interpola o el vector y interpola .
La suma de los vectores es y está claro que interpola eso. Sea .
Probando las matemáticas en Python
Hacer pruebas unitarias de una identidad matemática propuesta no la hace verdadera, pero ilustra lo que está sucediendo. Se anima al lector a probar con algunos vectores diferentes para comprobar que la identidad se cumple.
import galois
import numpy as np
p = 17
GF = galois.GF(p)
xs = GF(np.array([1,2,3]))
# two arbitrary vectors
v1 = GF(np.array([4,8,2]))
v2 = GF(np.array([1,6,12]))
def L(v):
return galois.lagrange_poly(xs, v)
assert L(v1 + v2) == L(v1) + L(v2)
Multiplicación por un escalar
Sea un escalar (específicamente, un elemento del campo en un campo finito). Entonces
Ejemplo resuelto
Supongamos que nuestros 3 puntos son . El polinomio que interpola eso es . Si multiplicamos el vector por 3 obtenemos . El polinomio que interpola eso es
from scipy.interpolate import lagrange
x_values = [1, 2, 3]
y_values = [9, 18, 33]
print(lagrange(x_values, y_values))
# 2
# 3 x + 6
, lo cual equivale a .
Ejemplo resuelto en código
import galois
import numpy as np
p = 17
GF = galois.GF(p)
xs = GF(np.array([1,2,3]))
# arbitrary vector
v = GF(np.array([4,8,2]))
# arbitrary constant
lambda_ = GF(15)
def L(v):
return galois.lagrange_poly(xs, v)
assert L(lambda_ * v) == lambda_ * L(v)
La multiplicación por un escalar es en realidad suma de vectores
Cuando decimos “multiplicar un vector por 3” en realidad estamos diciendo “sumar el vector consigo mismo tres veces”. Dado que solo estamos trabajando en campos finitos, no nos preocupamos por la interpretación de escalares como “0.5”
Podemos pensar tanto en los vectores bajo la suma de elementos uno a uno (en un campo finito) como en los polinomios bajo la suma (también en un campo finito) como grupos.
La conclusión más importante de este capítulo es
El grupo de vectores bajo la suma en un campo finito es homomórfico al grupo de polinomios bajo la suma en un campo finito.
Esto es fundamental porque comprobar la igualdad de vectores toma un tiempo , pero comprobar la igualdad de polinomios toma un tiempo .
Por lo tanto, mientras que comprobar la igualdad de un R1CS tomaba tiempo , podemos aprovechar este homomorfismo para comprobar la igualdad de los R1CSs en tiempo .
Esto es lo que es un Programa Aritmético Cuadrático.
Un Sistema de Restricciones de Rango 1 en Polinomios
Consideremos que la multiplicación de matrices entre una matriz rectangular y un vector puede escribirse en términos de suma de vectores y multiplicación por un escalar.
Por ejemplo, si tenemos una matriz de y un vector de 4 dimensiones, entonces podemos escribir la multiplicación de la matriz como
Normalmente pensamos en el vector “volteándose” y realizando un producto interno (producto punto generalizado) con cada una de las filas, es decir
Sin embargo, en su lugar, podríamos pensar en dividir la matriz en un conjunto de vectores de la siguiente manera:
y multiplicando cada vector por un escalar del vector :
Hemos expresado la multiplicación de matrices entre y puramente en términos de suma de vectores y multiplicación por un escalar.
Dado que establecimos anteriormente que el grupo de vectores bajo la suma en un campo finito es homomórfico al grupo de polinomios bajo la suma en un campo finito, podemos expresar el cálculo anterior en términos de polinomios que representan a los vectores.
Comprobando sucintamente que
Supongamos que tenemos las matrices y tales que
y los vectores y
Queremos comprobar si
es cierto.
Obviamente, podemos llevar a cabo la aritmética matricial, pero la comprobación final requerirá comparaciones, donde es el número de filas en y . Queremos hacerlo en tiempo .
Primero, convertimos la multiplicación de matrices y al grupo de vectores bajo la suma:
Ahora queremos encontrar el equivalente homomórfico de
en el grupo de polinomios.
Convirtamos cada uno de los vectores a polinomios sobre los valores de :
Invocaremos algo de Python para calcular la interpolación de Lagrange:
import galois
import numpy as np
p = 17
GF = galois.GF(p)
x_values = GF(np.array([1, 2]))
def L(v):
return galois.lagrange_poly(x_values, v)
p1 = L(GF(np.array([6, 4])))
p2 = L(GF(np.array([3, 7])))
q1 = L(GF(np.array([3, 12])))
q2 = L(GF(np.array([9, 6])))
print(p1)
# 15x + 8 (mod 17)
print(p2)
# 4x + 16 (mod 17)
print(q1)
# 9x + 11 (mod 17)
print(q2)
# 14x + 12 (mod 17)
Finalmente, podemos comprobar si
es cierto invocando el Lema de Schwartz-Zippel:
import random
u = random.randint(0, p)
tau = GF(u) # a random point
left_hand_side = p1(tau) * GF(2) + p2(tau) * GF(4)
right_hand_side = q1(tau) * GF(2) + q2(tau) * GF(2)
assert left_hand_side == right_hand_side
La declaración assert final es capaz de comprobar si realizando una sola comparación en lugar de .
De R1CS a QAP: Comprobando sucintamente que
Dado que sabemos cómo comprobar si de forma sucinta, ¿podemos también comprobar si sucintamente?
Las matrices tienen columnas, así que dividamos cada una de las matrices en vectores columna y procedamos a interpolarlos en para producir polinomios cada uno.
Sean los polinomios que interpolan los vectores columna de .
Sean los polinomios que interpolan los vectores columna de .
Sean los polinomios que interpolan los vectores columna de .
Sin pérdida de generalidad, digamos que tenemos 4 columnas () y tres filas ().
Visualmente, esto puede representarse como
Dado que multiplicar un vector columna por un escalar es homomórfico a multiplicar un polinomio por un escalar, cada uno de los polinomios puede multiplicarse por el elemento respectivo en el witness.
Por ejemplo,
se convierte en
Observa que el resultado final es un solo polinomio con un grado máximo de , ya que cada tiene un grado máximo de .
Esto se deduce de cómo los construimos: cada columna de tiene entradas, y al interpolar a través de puntos mediante la interpolación de Lagrange se produce un polinomio de grado como máximo .
En el caso general, puede escribirse como
después de convertir cada una de las columnas en polinomios.
Siguiendo los mismos pasos anteriores, cada producto matriz-witness en el R1CS puede transformarse como
Dado que cada uno de los términos de la suma produce un solo polinomio, podemos escribirlos como:
¿Por qué interpolar todas las columnas?
Debido a los homomorfismos y , si calculamos como obtenemos el mismo resultado que si aplicamos la interpolación de Lagrange a las columnas de y luego multiplicamos cada uno de los polinomios por el elemento respectivo en sumando el resultado.
Dicho de otra manera,
Entonces, ¿por qué no calcular simplemente una sola interpolación de Lagrange en lugar de ?
Necesitamos hacer una distinción sobre quién está usando el QAP. El verificador (y el trusted setup que cubriremos más adelante) no conocen el witness y, por lo tanto, no pueden calcular . Esta es una optimización que el probador (prover) puede hacer, pero las demás partes en el protocolo ZK no pueden hacer uso de ella.
Todas las partes involucradas necesitan tener un acuerdo común sobre el QAP —las interpolaciones polinómicas de las matrices— antes de que se realice cualquier prueba o verificación.
Desequilibrio en los grados de los polinomios
Sin embargo, no podemos simplemente expresar el resultado final como
porque los grados no coincidirán.
Multiplicar dos polinomios entre sí da como resultado un polinomio producto cuyo grado es la suma de los grados de los dos polinomios que se están multiplicando.
Debido a que cada , y tendrá un grado , generalmente tendrá un grado y tendrá un grado , por lo que no serán iguales a pesar de que los vectores subyacentes que multiplicaron sí lo son.
Esto se debe a que los homomorfismos que establecimos anteriormente solo hacen afirmaciones sobre la suma de vectores, no sobre el producto de Hadamard.
Sin embargo, el vector que interpola, es decir
es el mismo que el vector que interpola, es decir
En otras palabras
Aunque los vectores “subyacentes” son iguales, los polinomios que los interpolan no lo son.
Ejemplo de igualdad subyacente
Supongamos que es el polinomio que interpola
y es el polinomio que interpola
Si consideramos que interpola el vector y interpola el vector , entonces podemos ver que su polinomio producto interpola el producto de Hadamard de los dos vectores. El producto de Hadamard de y es .
Si multiplicamos y entre sí, obtenemos .
Podemos ver en el gráfico de abajo que el polinomio producto interpola el producto de Hadamard de los dos vectores.

Entonces, ¿cómo podemos “hacer” que sea igual a si interpolan los mismos valores de sobre ?
Interpolando el vector
Si , entonces .
En lugar de interpolar con la interpolación de Lagrange y obtener (recuerda que la interpolación de Lagrange encuentra el polinomio interpolador de menor grado), podemos usar un polinomio de mayor grado que equilibrará el desajuste en los grados.
Por ejemplo, el polinomio negro () en la imagen de abajo interpola :

Ahora, dado que es una interpolación válida de , podemos escribir nuestro original
¡y la ecuación estará equilibrada!
se calculó simplemente como (el polinomio azul menos el polinomio rojo)
Sin embargo, no podemos dejar que el probador elija cualquier , de lo contrario, podría elegir un que equilibre y incluso si no interpolan el mismo vector ( en nuestro ejemplo). El probador tiene demasiada flexibilidad al elegir . Específicamente, queremos exigir que tenga raíces en —es decir, que interpole el vector . De esa manera, la transformación polinómica de sigue respetando los vectores subyacentes.
Para restringir su elección de , podemos utilizar el siguiente teorema:
La unión de raíces del producto de polinomios
Teorema: Si y tiene un conjunto de raíces y tiene un conjunto de raíces , entonces tiene como raíces .
Ejemplo
Sean y . Entonces tiene como raíces .
Podemos usar el teorema anterior para obligar a que tenga raíces en .
Forzando a que sea el vector cero
Descomponemos en donde es el polinomio
entonces cualquier polinomio multiplicado con también será el vector cero, ya que debe tener raíces en .
Por lo tanto, reemplazaremos por en nuestra ecuación.
Así, nuestra igualdad se convertirá en
Podemos calcular usando álgebra básica:
QAP de principio a fin
Supongamos que tenemos un R1CS con las matrices , y , y el vector witness .
Las matrices tienen columnas y filas donde y .
Es decir, , y son las siguientes:
Y el vector witness es
Dividimos cada una de las matrices en vectores columna y los interpolamos en para producir polinomios cada uno.
Cada uno de los productos matriz-vector , y es homomórficamente equivalente a los siguientes polinomios:
En nuestro caso, será
y será
La fórmula final para una representación QAP del R1CS original es
Fórmula final para un QAP
Un QAP es la siguiente fórmula:
Donde , y son polinomios que interpolan las columnas de , y respectivamente, es , donde es el número de filas en , y , y es
Pruebas de conocimiento cero sucintas con Programas Aritméticos Cuadráticos
Supongamos que tuviéramos una forma de que el verificador enviara un valor aleatorio al probador y este respondiera con
El verificador podría comprobar que y aceptar que el probador tiene un witness válido que satisface tanto el R1CS como el QAP.
Sin embargo, esto requeriría que el verificador confíe en que el probador está evaluando los polinomios correctamente, y no tenemos un mecanismo para obligar al probador a hacerlo.
En el próximo capítulo, mostraremos código en Python para convertir un R1CS en un QAP en base a nuestra discusión en este capítulo.
Luego discutiremos los trusted setups para comenzar a abordar el problema de cómo conseguir que el probador evalúe los polinomios de forma honesta.
Publicado originalmente el 23 de agosto de 2023