信号和变量
使用circom构建的算术电路对包含Z/pZ域中的信号进行操作。信号可以使用标识符命名,也可以存储在数组中并使用关键字signal进行声明。信号可以定义为输入或输出,否则被视为中间信号。
#![allow(unused)] fn main() { signal input in; signal output out[N]; signal inter; }
这个小例子声明了一个带有标识符的输入信号in、一个带有标识符的输出信号的N维数组out以及一个带有标识符的中间信号inter。
信号始终被认为是私有的。只有在定义主要组件时,通过提供公共输入信号列表,程序员才能区分公共信号和私有信号。
#![allow(unused)] fn main() { pragma circom 2.0.0; template Multiplier2(){ //Declaration of signals signal input in1; signal input in2; signal output out; out <== in1 * in2; } component main {public [in1,in2]} = Multiplier2(); }
从circom2.0.4开始,还允许在声明后立即初始化中间和输出信号。那么,前面的例子可以重写如下:
#![allow(unused)] fn main() { pragma circom 2.0.0; template Multiplier2(){ //Declaration of signals signal input in1; signal input in2; signal output out <== in1 * in2; } component main {public [in1,in2]} = Multiplier2(); }
此示例将主要组件的输入信号in1和声明为公共信号。in2
在这种情况下,主组件的所有输出信号都是公共的(并且不能设为私有),如果没有另外声明,则主组件的输入信号是私有的,使用上面的关键字public。其余信号都是私有的,不能公开。
因此,从程序员的角度来看,只有公共输入和输出信号从电路外部可见,因此不能访问中间信号。
#![allow(unused)] fn main() { pragma circom 2.0.0; template A(){ signal input in; signal outA; //We do not declare it as output. outA <== in; } template B(){ //Declaration of signals signal output out; component comp = A(); out <== comp.outA; } component main = B(); }
此代码会产生编译错误,因为signal outA未声明为输出信号,因此无法访问它并将其分配给signal out。
信号是不可变的,这意味着一旦为它们分配了值,该值就不能再更改。因此,如果一个信号被分配两次,就会产生编译错误。这可以在下一个示例中看到,其中信号out被分配两次,从而产生编译错误。
#![allow(unused)] fn main() { pragma circom 2.0.0; template A(){ signal input in; signal output outA; outA <== in; } template B(){ //Declaration of signals signal output out; out <== 0; component comp = A(); comp.in <== 0; out <== comp.outA; } component main = B(); }
在编译时,信号的内容始终被视为未知(请参阅Unknowns),即使已经为其分配了常量。这样做的原因是为了提供一个精确的定义哪些结构是允许的,哪些结构是不允许的,而不依赖于编译器检测信号是否始终具有常量值的能力。
#![allow(unused)] fn main() { pragma circom 2.0.0; template A(){ signal input in; signal output outA; var i = 0; var out = 0; while (i < in){ out++; i++; } outA <== out; } template B(){ component a = A(); a.in <== 3; } component main = B(); }
此示例会产生编译错误,因为signal的值outA取决于signal的值in,尽管该值是常量3。
信号只能使用 <-- 或 <==(见基本运算符)进行赋值,信号位于左侧;使用 --> 或 ==>(见基本运算符)进行赋值,信号位于右侧。安全的选项是 <== 和 ==>,因为它们在赋值的同时也会产生约束。一般来说,使用 <-- 和 --> 是危险的,只有在赋值表达式不能包含在约束中时才能使用,例如下面的例子。
#![allow(unused)] fn main() { out[k] <-- (in >> k) & 1; }