Circom no permite instanciar componentes directamente en un bucle. Por ejemplo, compilar el siguiente código produce el siguiente error.
include "./node_modules/circomlib/circuits/comparators.circom";
template IsSorted(n) {
signal input in[n];
for (var i = 0; i < n; i++) {
component lt = LessEqThan(252); // error here
lt.in[0] <== in[0];
lt.in[1] <== in[1];
lt.out === 1;
}
}
component main = IsSorted(8);
Signal or component declaration inside While scope. Signal and component can only be defined in the initial scope or in If scopes with known condition
La solución es declarar un array de los componentes pero no especificar el tipo de componente:
pragma circom 2.1.8;
include "./node_modules/circomlib/circuits/comparators.circom";
template IsSorted(n) {
signal input in[n];
// declare array of components
// but do not specify the component type
component lessThan[n];
for (var i = 0; i < n - 1; i++) {
lessThan[i] = LessEqThan(252); // specify type in the loop
lessThan[i].in[0] <== in[i];
lessThan[i].in[1] <== in[i+1];
lessThan[i].out === 1;
}
}
component main = IsSorted(8);
Cuando los componentes se declaran de esta manera, no es posible hacer una “asignación en una sola línea” a una signal como se muestra a continuación:
pragma circom 2.1.8;
include "./node_modules/circomlib/circuits/comparators.circom";
template IsSorted() {
signal input in[4];
signal leq1;
signal leq2;
signal leq3;
// one line assignment to the signal
leq1 <== LessEqThan(252)([in[0], in[1]]);
leq2 <== LessEqThan(252)([in[1], in[2]]);
leq3 <== LessEqThan(252)([in[2], in[3]]);
leq1 === 1;
leq2 === 1;
leq3 === 1;
}
component main = IsSorted();
Fuera de un bucle, las signals se pueden establecer en una sola línea. Sin embargo, dentro de un bucle, tenemos que escribir la asignación en más pasos, como hicimos en lessThan[i] = LessEqThan(252); // specify type in the loop.
Ejemplo 1: máximo de un array
Para ilustrar un ejemplo útil de declaración de componentes en un bucle, mostramos cómo probar que k es el máximo de un array. Para hacer esto, necesitamos restringir que k sea mayor o igual a todos los demás elementos y que sea igual a al menos uno de los elementos. Para ver por qué es necesaria la comprobación de igualdad, considere que 18 es mayor o igual a todos los elementos en [7, 8, 15], pero no es el máximo del array.
El siguiente código de Circom calcula el valor máximo del array sin generar restricciones. Luego, ejecuta n componentes GreaterEqualThan para restringir que el valor max propuesto sea efectivamente el valor máximo, y también comprueba que al menos uno de los elementos sea igual a k usando un array de componentes IsEqual.
include "./node_modules/circomlib/circuits/comparators.circom";
template Max(n) {
signal input in[n];
signal output out;
// no constraints here, just a computation
// to find the max
var max = 0;
for (var i = 0; i < n; i++) {
max = in[i] > max ? in[i] : max;
}
out <-- max;
// for each element in the array, assert that
// max ≥ that element
component GTE[n];
component EQ[n];
var acc;
for (var i = 0; i < n; i++) {
GTE[i] = GreaterEqThan(252);
GTE[i].in[0] <== out;
GTE[i].in[1] <== in[i];
GTE[i].out === 1;
// this is used in the
// next code block to ensure
// that out equals at
// least one of the inputs
EQ[i] = IsEqual();
EQ[i].in[0] <== out;
EQ[i].in[1] <== in[i];
// acc is greater than zero
// (acc != 0) if EQ[i].out
// equals 1 at least one time
acc += EQ[i].out;
}
// assert that out is
// equal to at least one of the
// inputs. if acc = 0 then
// none of the inputs equals
// out
signal allZero;
allZero <== IsEqual()([0, acc]);
allZero === 0;
}
component main = Max(8);
Ejercicio: Cree un circuito que haga lo mismo que el anterior, pero para el min.
Ejemplo 2: el array está ordenado
Podemos asegurar que un array está ordenado comprobando que cada elemento es menor o igual al siguiente. A diferencia del ejemplo anterior, que necesitaba n componentes, necesitamos n - 1 componentes ya que estamos comparando valores vecinos entre sí. Dado que tenemos n elementos, vamos a hacer n - 1 comparaciones.
Aquí hay un template que restringe un array de entrada in[n] para que esté ordenado. Tenga en cuenta que si un array solo tiene un elemento, está ordenado por definición, y el circuito a continuación también es compatible con ese escenario:
pragma circom 2.1.6;
include "circomlib/comparators.circom";
template IsSorted(n) {
signal input in[n];
component lt[n - 1];
// loop goes up to n - 1, not n
for (var i = 0; i < n - 1; i++) {
lt[i] = LessThan(252);
lt[i].in[0] <== in[i];
lt[i].in[1] <== in[i+1];
lt[i].out === 1;
}
}
component main = IsSorted(3);
Ejemplo 3: Todos los elementos son únicos
Para comprobar que todos los elementos en una lista son únicos, la forma más directa es usar un hashmap, pero los hashmaps no están disponibles en los circuitos aritméticos. La segunda forma más eficiente es ordenar la lista, pero ordenar dentro de un circuito es bastante complicado, así que lo evitaremos por ahora. Esto nos deja con la solución de fuerza bruta de comparar cada elemento con todos los demás elementos. Esto requiere un bucle for anidado.
El cálculo que estamos realizando se puede ilustrar de la siguiente manera:
En general, habrá
comprobaciones de desigualdad, por lo que necesitaremos esa cantidad de componentes.
A continuación mostramos cómo lograr esto:
pragma circom 2.1.8;
include "./node_modules/circomlib/comparators.circom";
template ForceNotEqual() {
signal input in[2];
component iseq = IsEqual();
iseq.in[0] <== in[0];
iseq.in[1] <== in[1];
iseq.out === 0;
}
template AllUnique (n) {
signal input in[n];
// the nested loop below will run
// n * (n - 1) / 2 times
component Fneq[n * (n-1)/2];
// loop from 0 to n - 1
var index = 0;
for (var i = 0; i < n - 1; i++) {
// loop from i + 1 to n
for (var j = i + 1; j < n; j++) {
Fneq[index] = ForceNotEqual();
Fneq[index].in[0] <== in[i];
Fneq[index].in[1] <== in[j];
index++;
}
}
}
component main = AllUnique(5);
Resumen
Para usar componentes de Circom dentro de un bucle, declaramos un array de componentes fuera del bucle sin especificar el tipo.
Luego, dentro del bucle, declaramos los componentes y restringimos las entradas y salidas del componente.