Una entrada pública en Circom es una señal en el witness que será revelada al verificador.
Por ejemplo, supongamos que queremos crear una prueba ZK que declare: “conocemos la entrada a un hash que produjo 0x492c…9254”. Para que esta afirmación tenga sentido, el valor 0x492c…9254 (la salida del hash objetivo) debe ser público. De lo contrario, semánticamente estaríamos afirmando que “hasheamos algo”, lo cual no es tan útil.
El siguiente circuito afirma: “multipliqué dos números entre sí y obtuve un tercero”:
template Main() {
signal input a;
signal input b;
signal input c;
a * b === c;
}
component main = Main();
Este siguiente circuito hace una afirmación similar, pero con el cambio de que el resultado es público: “multipliqué dos números entre sí y obtuve un tercero cuyo valor es de conocimiento público”:
template Main() {
signal input a;
signal input b;
signal input c;
a * b === c;
}
component main {public [c]} = Main();
- Todas las señales de entrada son privadas por defecto a menos que se hagan explícitamente públicas utilizando la sintaxis
component main {public [c]}. El componente main es el único lugar donde podemos definir qué entradas son públicas. - La lista
[c]es una lista de señales a hacer públicas. Podría haber contenido más señales, como[a,c], si también quisiéramos hacer aapública. - Solo las señales de entrada pueden especificarse como públicas, las señales intermedias no.
La plantilla anterior compila a un Rank-1 Constraint System (R1CS) idéntico al siguiente, donde introducimos la palabra clave output en el componente main:
template Main() {
signal input a;
signal input b;
signal output c;
a * b ==> c;
}
component main = Main();
En las dos plantillas anteriores, c es pública y está restringida a ser el producto de a y b. Por lo tanto, el R1CS subyacente es el mismo. Sin embargo, la segunda versión es más “conveniente” ya que no tenemos que proporcionar c explícitamente. En el primer circuito que usa component main {public [c]}, si proporcionamos un c que no obedece las restricciones, el witness no será generado. Sin embargo, en el segundo circuito que usa c como un output, el generador de witness calcula automáticamente el valor correcto para c, eliminando la entrada manual.
Dado que c está totalmente determinada por a y b, no hay razón para proporcionar explícitamente un valor para c, por lo que la notación output es preferible.
Ten en cuenta que los outputs son públicos.
En el caso de las entradas, si queremos hacer algunas públicas, eso significa que tenemos una señal cuyo valor no está totalmente determinado por los valores de otras señales. En tales casos, debemos usar el método modificador public. Por ejemplo, si afirmamos “Multipliqué a, b y c entre sí para obtener d, con a y d como públicos, pero b y c como privados”, estructuraríamos ese circuito de la siguiente manera:
template Main() {
signal input a; // explicitly public
signal input b;
signal input c;
signal output d; // implicitly public
signal s <== a * b; // intermediate signal
d <== c * s;
}
component main{public [a]} = Main();
Así es como se deben entender las señales output:
- Para un subcomponente, un
outputes una señal a la que se le asignará un valor a partir de las otras entradas y que potencialmente será utilizada más tarde por el componente que instancia el subcomponente. - Para el componente main, un
outputes una señal pública en el witness cuyo valor debe estar totalmente determinado por otras señales de entrada. Declarar una señal output y no asignarle un valor puede crear una vulnerabilidad porque un probador puede asignar el valor que desee. Mostraremos el mecanismo de este exploit en un próximo capítulo.
A pesar del nombre “output”, no hay un mecanismo para obtener el “output” del componente main: Circom no puede retornar nada. No hay forma de que alguna otra base de código lea el valor de “output”.
Solo genera un R1CS, ayuda a calcular el witness para el R1CS. Snarkjs luego usa el código de Circom para generar una prueba ZK de que el witness satisface el R1CS.
Circom no se está “ejecutando”, por lo que no “retorna” nada. No estás “corriendo” Circom, simplemente estás describiendo un circuito abstracto que se está convirtiendo en dos partes: el R1CS y un generador de witness, los cuales se utilizan por separado.
Una señal output en el componente main puede considerarse como una señal intermedia que es pública para el verificador.
Disposición del witness con señales públicas
Circom organiza el vector del witness de la siguiente manera:
[constant, public signals, private signals]
Usemos como ejemplo: “Multipliqué los valores ocultos a y b con un valor público c para obtener un valor público de d”:
// assert that a*b === c*d
template Example() {
signal input a;
signal input b;
signal input c;
signal input d;
signal s;
s <== a * b;
d === s * c;
}
component main {public [c, d]} = Example();
Ten en cuenta que podríamos ahorrar algo de código haciendo que d sea un output, pero no lo hacemos aquí para que la siguiente demostración sea más clara.
Para ver cómo está estructurado el witness:
- Guarda el archivo anterior como
Example.circom - Compílalo con
circom Example.circom --sym --r1cs --wasm - Crea el archivo
input.json:echo '{"a": "3", "b": "4", "c":"2", "d":"24"}' > input.json cd example_js- Calcula el witness:
node generate_witness.js example.wasm ../input.json witness.wtns - Convierte el witness a json y muéstralo con cat:
snarkjs wej witness.wtns && cat witness.json
Deberíamos obtener el siguiente resultado. Nota que esto coincide con los valores que proporcionamos en input.json:
[
"1", // constant
"2", // c (public signal)
"24", // d (public signal)
"3", // a
"4", // b
"12" // s
]
Por lo tanto, podemos ver que la disposición del witness es siempre:
- La entrada constante en el witness (que siempre es 1)
- Las señales públicas (
c,d) - Las señales de entrada (
a,b) - Las señales intermedias (
s).
Resumen
- Los inputs son privados por defecto
- Podemos hacer los inputs públicos utilizando la sintaxis
component main {public [in1, in2]} = Main(); - Los outputs son señales públicas
- Los outputs son señales que se calculan para el usuario en base a otros inputs