#4 Circom: Intermediate signals
Topics covered: intermediate signals, variables vs signals, gotcha on var as signal
Intermediate Signals
In the previous article, we reviewed quadratic constraints. In this article, we will examine how to breakdown non-quadratic constraints into acceptable quadratic ones.
We will look to convert res === a * b * c;
into an acceptable form.
Given we previously discussed variables, you might think you can get around the quadratic constraint requirement by using a variable to break up the multiplication. Like so:
Declare a variable
prod
and use it to calculatea * b * c
.Constraint
res
against the the value ofprod
.However, this circuit will not compile.
It may seem like prod
stores the final result; instead prod
holds the full expression a * b * c
.
This results in the non-quadratic constraint: res === a * b * c
, which is not allowed.
This would hold true for other non-linear operations like division as well.
Usage of variables like in the manner above to flatten non-quadratic expressions will not work. The reader might be confused, given the prior explanation of variables in article 2. Let us clarify.
Cannot use variables to flatten non-quadratic signal expressions
When variables are used in signal operations, like the one above, they do not "flatten" the signal operations. Instead, they will hold the entire expression.
variables which involve signal operations, will hold the entire expression.
variables which do not involve signal operations can be treated normally.
So how do we reduce non-quadratic constraints into quadratic ones? By using intermediate signals as explained in the next section.
signal
and <--
operator
signal
and <--
operatorAn intermediate signal is declared using the signal
keyword:
res === a * b * c
is transformed into 2 quadratic constraintsprod
as an intermediate signal allows for flattening of constraint expressions
We can draw a parallel between prod
as an intermediate signal and the intermediate variables we employ during the arithmetization stage of R1CS.
In arithmetization, we flatten non-quadratic constraints into quadratic ones by introducing intermediate variables, which are specified within the witness vector.
This form should not be unfamiliar to the reader: [1 , out , x , y , v1 , v2]
, where v1
and v2
are intermediate variables.
Therefore, intermediate signals are simply the intermediate variables that we used during the pen and paper approach.
Takeaway: Intermediate signals are used to flatten constraints; not variables.
Signal assignment operator: <--
<--
After declaring
prod
, a value must be assigned to it, before it can be used in a constraint definition.That is the purpose of the
<--
operator, it assignsprod
the resulting value ofa * b
.Subsequently,
prod
is constrained toa * b
.
Why constraint prod === a * b
?
prod === a * b
?The reader will be concerned as to why we constrain prod
against the very thing we assigned it to.
This is to ensure that during proof generation, the witness values (actual inputs) obey this relationship. Without the constraint, there’s no guarantee that prod
will equal a * b
during proof generation.
It is important to understand that the proof is based on the constraints, not the assignments.
In the next article, we will explore how Circom circuits are actualized from proof generation to verification, which will clearly illustrate the need for constraining an intermediate signal when assigning it.
Exercise
We will review the process of circuit compilation and witness generation again, in the context of intermediate signals and variables.
We will use our Mul3 circuit from earlier in this walkthrough.
Compile circuit
circom mul3.circom --r1cs --sym --wasm
Sanity check: terminal output should reflect that
non-linear constraints: 2
Examine the R1CS file
snarkjs r1cs print mul3.r1cs
there should be only 2 constraints, as dictated by our circuit
It should be obvious to the reader that the constraints match our circuit.
Generate the witness
create
inputs.json
file in./mul3_js
directoryvalues passed will be as below
This makes sense as the product of a
, b
, c
is 6, which we have assigned to res
.
run:
node generate_witness.js **mul3**.wasm inputs.json witness.wtns
.export to json:
snarkjs wtns export json witness.wtns
cat witness.json
, should output:
The reader will observe in the layout of the witness, that intermediate signals are included, and come after input signals.
Last updated
Was this helpful?