Un compromiso polinómico es un mecanismo mediante el cual un probador (prover) puede convencer a un verificador (verifier) de que un polinomio tiene una evaluación en el punto sin revelar nada sobre . La secuencia es la siguiente:
- El probador envía al verificador un compromiso , “fijando” su polinomio.
- El verificador responde con un valor en el cual desea que se evalúe el polinomio.
- El probador responde con y , donde es la evaluación de y es la prueba de que la evaluación fue correcta.
- El verificador comprueba , , , y acepta o rechaza que la evaluación del polinomio haya sido válida.
Este esquema de compromiso no requiere una configuración confiable (trusted setup). Sin embargo, la sobrecarga de comunicación es , ya que el probador debe enviar un compromiso por cada coeficiente de su polinomio.
Comprometerse con el Polinomio
El probador puede comprometerse con el polinomio creando un Pedersen Commitment para cada coeficiente. Para un Pedersen Commitment, el probador y el verificador deben acordar dos puntos de curva elíptica con logaritmos discretos desconocidos. Usaremos y .
Por ejemplo, si tenemos un polinomio
podemos crear un Pedersen Commitment para cada coeficiente. Necesitaremos tres términos de ocultamiento (blinding terms) , , . Por conveniencia, cualquier escalar utilizado para ocultamiento usará una letra griega minúscula. Siempre usamos el punto de curva elíptica para el término de ocultamiento. Nuestros compromisos se producen de la siguiente manera:
El probador envía la tupla al verificador.
El verificador elige
El verificador elige su valor para y se lo envía al probador. Llamamos a ese valor .
El probador calcula la prueba
El probador evalúa el polinomio
El probador calcula el polinomio original de la siguiente manera:
El probador evalúa los términos de ocultamiento
La prueba de que la evaluación se realizó correctamente viene dada por el siguiente polinomio, que utiliza los términos de ocultamiento multiplicados por la potencia asociada de . La razón de esto se explicará más adelante.
El probador envía al verificador. Ten en cuenta que el probador solo envía elementos de campo (escalares), no puntos de curva elíptica.
Paso de verificación
El verificador ejecuta la siguiente comprobación:
Por qué funciona el paso de verificación
Si expandimos los puntos de curva elíptica a sus valores subyacentes, vemos que la ecuación está equilibrada:
En cierto sentido, el probador está evaluando el polinomio usando los coeficientes del polinomio y su elección de . Esto producirá la evaluación del polinomio original más los términos de ocultamiento del polinomio.
La prueba de la evaluación correcta es que el probador puede separar los términos de ocultamiento de la evaluación del polinomio, a pesar de que el probador no conoce los logaritmos discretos de y .
Una ilustración alternativa de por qué funciona la verificación
Recordemos que . Por lo tanto, los compromisos de los coeficientes se calculan de la siguiente manera:
Cuando el verificador envía , el probador calcula:
En el paso final, el verificador comprueba:
Si expandimos los términos verticalmente, vemos que la ecuación está equilibrada si el probador fue honesto:
Es muy importante que comprendas firmemente cómo funciona esta técnica de probar la evaluación correcta de un polinomio dados compromisos de ocultamiento para los coeficientes, porque Bulletproofs utilizan esta técnica en todas partes.
Ejercicio: Escribe los pasos de cómo un probador convencería a un verificador de que evaluó correctamente un polinomio de grado 1, sin revelar el polinomio al verificador.
Ejercicio: Completa el código en Python a continuación para implementar el algoritmo descrito en este capítulo:
from py_ecc.bn128 import G1, multiply, add, FQ
from py_ecc.bn128 import curve_order as p
import random
def random_field_element():
return random.randint(0, p)
# these EC points have unknown discrete logs:
G = (FQ(6286155310766333871795042970372566906087502116590250812133967451320632869759), FQ(2167390362195738854837661032213065766665495464946848931705307210578191331138))
B = (FQ(12848606535045587128788889317230751518392478691112375569775390095112330602489), FQ(18818936887558347291494629972517132071247847502517774285883500818572856935411))
# scalar multiplication example: multiply(G, 42)
# EC addition example: add(multiply(G, 42), multiply(G, 100))
# remember to do all arithmetic modulo p
def commit(f_0, f_1, f_2, gamma_0, gamma_1, gamma_2, G, B):
# fill this in
# return the commitments as a tuple (C0, C1, C2)
pass
def evaluate(f_0, f_1, f_2, u):
return (f_0 + f_1 * u + f_2 * u**2) % p
def prove(gamma_0, gamma_1, gamma_2, u):
# fill this in
# return pi
pass
def verify(C0, C1, C2, G, B, f_u, pi):
# fill this in
# Return true or false
pass
## step 0: Prover and verifier agree on G and B
## step 1: Prover creates the commitments
### f(x) = f_0 + f_1x + f_2x^2
f_0 = ...
f_1 = ...
f_2 = ...
### blinding terms
gamma_0 = ...
gamma_1 = ...
gamma_2 = ...
C0, C1, C2 = commit(f_0, f_1, f_2, gamma_0, gamma_1, gamma_2, G, B)
## step 2: Verifier picks u
u = ...
## step 3: Prover evaluates f(u) and pi
f_u = evaluate(f_0, f_1, f_2, u)
pi = prove(gamma_0, gamma_1, gamma_2, u)
## step 4: Verifier accepts or rejects
if verify(C0, C1, C2, G, B, f_u, pi):
print("accept")
else:
print("reject")
Por qué el probador no puede hacer trampa
Hacer trampa por parte del probador significa que no evalúan honestamente pero aun así intentan pasar el paso de evaluación final.
Sin pérdida de generalidad, digamos que el probador envía los compromisos correctos para los coeficientes .
Decimos sin pérdida de generalidad porque existe una discrepancia entre los coeficientes enviados en los compromisos y los coeficientes utilizados para evaluar el polinomio.
Para hacerlo, el probador envía donde .
Usando la ecuación final de la sección anterior, vemos que el probador debe satisfacer:
Los términos de la ecuación están claramente desequilibrados. La otra “palanca” que el probador puede mover es ajustar el que envía.
Dado que , el probador malicioso debe reequilibrar la ecuación eligiendo un término que compense la discrepancia en los términos . El probador puede intentar despejar con
Pero resolver esta ecuación requiere que el probador malicioso conozca los logaritmos discretos de y .
Despejemos en la ecuación anterior:
y luego reemplazamos con y con , donde y son los logaritmos discretos de y respectivamente:
Pero nuevamente, esto no es posible porque calcular el logaritmo discreto de y es inviable.
Lo que aprende el verificador
El verificador aprende que los compromisos representan compromisos válidos para un polinomio que es de grado 2 como máximo, y que es el valor del polinomio evaluado en .