The Circuit and Gates
Semacaulk uses a custom Plonk-style proof system where a prover must convince a verifier that it knows of some private witness values which are the result of the correct execution of predefined logical operations upon public inputs and fixed data. In other terms, there is a circuit which represents some program. In proof systems like Groth16, circuits are represented in the form of a Rank-1 Constraint System (R1CS), and compilers like circom can be used to easily compile circuits to this format. Semacaulk, by contrast, uses a set of custom gates on a set of data columns to represent its logic.
Private inputs (witness)
- \(\mathsf{id\_nul}\): the identity nullifier.
- \(\mathsf{id\_trap}\): the identity trapdoor.
- \(i\): the index of the prover's identity commitment in the accumulator.
Public inputs
- \(\mathsf{ext\_nul}\): the extenal nullifier.
- \(\mathsf{id\_comm}\): the identity commitment, which is the MiMC7
multi_hash
of \([\mathsf{id\_nul}, \mathsf{id\_trap}]\). - \(\mathsf{nul\_hash}\): the nullifier hash, which is the MiMC7
multi_hash
of \([\mathsf{id\_nul}, \mathsf{ext\_nul}]\).
Columns
Row | \(\mathsf{w}_0\) | \(\mathsf{w}_1\) | \(\mathsf{w}_2\) | \(\mathsf{key}\) | \(\mathsf{c}\) | \(\mathsf{q\_mimc}\) |
---|---|---|---|---|---|---|
0 | \(\mathsf{id\_nul}\) | \(\mathsf{id\_trap}\) | \(\mathsf{ext\_nul}\) | \(\mathsf{w}_0[n] + \mathsf{w}_0[0] \) | \(\mathsf{cts}[0]\) | 1 |
1 | \((\mathsf{w}_0[0] + \mathsf{c}[0]) ^ 7\) | \((\mathsf{w}_1[0] + \mathsf{key}[0] + \mathsf{c}[0]) ^ 7\) | \((\mathsf{w}_2[0] + \mathsf{key}[0] + \mathsf{c}[0]) ^ 7\) | \(\mathsf{w}_0[n] + \mathsf{w}_0[0] \) | \(\mathsf{cts}[1]\) | 1 |
\ldots | \ldots | \ldots | \ldots | \ldots | \ldots | \ldots |
\(n\) | \((\mathsf{w}_0[n - 1] + \mathsf{c}[n - 1]) ^ 7\) | \((\mathsf{w}_1[n - 1] + \mathsf{key}[n - 1] + \mathsf{c}[n - 1]) ^ 7\) | \((\mathsf{w}_2[n - 1] + \mathsf{key}[n - 1] + \mathsf{c}[n - 1]) ^ 7\) | \(\mathsf{w}_0[n] + \mathsf{w}_0[0] \) | \(\mathsf{dummy}\) | 0 |
128 | \(b\) | \(b\) | \(b\) | \(b\) | \(b\) | \(b\) |
Notes:
- The 0th row contains the \(\mathsf{id\_nul}\), \(\mathsf{id\_trap}\), etc values. They are not table headers.
- \(n\) is the constant (91) defined in 4.
- \(\mathsf{dummy}\) can be any value as it will not be used by any of the gates.
- \(\mathsf{q\_mimc}\) is a selector column. It is a vector starting with \(n\) 1 values followed by zeros.
- \(\mathsf{c}\) is a fixed column starting with \(n\) MiMC7 round constants.
- \(b\) are random values used to blind the columns, in order to make it computationally infeasible to brute-force their polynomial commitments.
Gates
To understand how the logic of the circuit is encoded, consider each row of the table as inputs to the linear combination of the following gates, which must evaluate to 0 for a valid proof to be generated. In effect:
\(\mathsf{gate}_0(r) + \ldots + \mathsf{gate}_n(r) = 0\) must be true.
Each and every gate must evaluate to 0. It is not possible for the prover to cheat by having some gates evaluate to some value such that the total evaluates to 0, since the prover will be forced to separate each gate with a challenge that they cannot control. Internally, the equation is actually:
\(\mathsf{gate}_0(r) \cdot v_0 + \ldots + \mathsf{gate}_n(r) \cdot v_n = 0\) must be true.
where the \(v\) values are successive powers of the hash of the public inputs. The prover would have to break a strong hash function to choose the public inputs and \(v\) values in order to cheat.
0. Mimc7RoundGate
The equation is:
\(\mathsf{q\_mimc}[i] \cdot (\mathsf{w}_0[i] + 0 + \mathsf{c}[i]) ^ 7\)
This makes each row from 1 to \(n\) contain the successive outputs of the MiMC7 round function over \(\mathsf{id\_nul}\).
The key is set to 0 for all rows.
1. Mimc7RoundGate
for the identity commitment
The equation is:
\(\mathsf{q\_mimc}[i] \cdot (\mathsf{w}_1[i] + \mathsf{key}[i] + \mathsf{c}[i]) ^ 7\)
To understand this, first note that gate 4 (KeyCopyGate
) and gate 3
(KeyEqualityGate
) ensure that the \(\mathsf{key}\) values are all the MiMC7
hash
of \(\mathsf{id\_nul}\) plus \(\mathsf{id\_nul}\).
As described in
4.3.1,
this means that the key for step 4 of the multi_hash
function on two inputs
is the value in any row of \(key\) from 0 to \(n\). As such, this gate
represents the circuit logic for step 4 of multi_hash
, which brings it us
closer to computing the identity commitment.
Recall from 5.1:
\(\mathsf{id\_comm} = \mathsf{multi\_hash}([\mathsf{id\_nul}, \mathsf{id\_trap}])\)
2. Mimc7RoundGate
for the nullifier hash
The equation is:
\(\mathsf{q\_mimc}[i] \cdot (\mathsf{w}_2[i] + \mathsf{key}[i] + \mathsf{c}[i]) ^ 7\)
Recall that:
\(\mathsf{nul\_hash} = \mathsf{multi\_hash}([\mathsf{id\_nul}, \mathsf{ext\_nul}])\)
By the same logic behind the Mimc7RoundGate
for the identity commitment, this
gate brings us closer to compuing the nullifier hash.
3. KeyEqualityGate
The equation is:
\(\mathsf{q\_mimc}[i] \cdot (\mathsf{key}[i] + \mathsf{key}[n])\)
This gate ensures that every row of \(\mathsf{key}\) from 0 to \(n\) contains the same value.
4. KeyCopyGate
The equation is:
\(L_0(\omega_i) \cdot (\mathsf{key}[i] - \mathsf{w}_0[i] - \mathsf{w}_0[n])\)
This gate ensures that the first row in the \(\mathsf{key}\) column is equal to \(\mathsf{id\_nul}\) plus the \(n\)th iteration of the MiMC7 round function on \(\mathsf{id\_nul}\).
\(L_0\) is a precomputed polynomial in the multiplicative subgroup which evaluates to 1 at \(\omega_i\), and 0 at all other roots of unity. Effectively, it acts as a selector without the overhead of a selector column.
5. NullifierHashGate
The equation is:
\(L_0(\omega_i) \cdot (\mathsf{nul\_hash} - \mathsf{w}_2[n] - (2 \cdot \mathsf{key}[i]) - \mathsf{w}_2[i])\)
This gate ensures that the \(\mathsf{nul\_hash}\) public input is equal to:
\(\mathsf{w}_2[n] + (2 \cdot \mathsf{key}[0]) + \mathsf{w}_2[0])\)
To understand why, let us trace the computation of \(\mathsf{nul\_hash}\):
Given inputs \(\mathsf{id\_nul}\) and \(\mathsf{ext\_nul}\):
- Set \(r\) as 0.
- Set \(h_0 = \mathsf{hash}(\mathsf{id\_nul}, r)\).
- Set \(r = r + \mathsf{id\_nul} + h_0\).
- Set \(h_1 = \mathsf{hash}(\mathsf{ext\_nul}, r)\).
- Return \(r + \mathsf{ext\_nul} + h_1\).
Hence, \(\mathsf{nul\_hash}\) equals:
\(r + \mathsf{ext\_nul} + h_1 =\)
\(\mathsf{id\_nul} + h_0 + \mathsf{ext\_nul} + h_1 =\)
\(\mathsf{id\_nul} + \mathsf{hash}(\mathsf{id\_nul}, 0) + \mathsf{ext\_nul} + \mathsf{hash}(\mathsf{ext\_nul}, \mathsf{id\_nul} + \mathsf{hash}(\mathsf{id\_nul}, 0))\)
Since the value \(r\) from step 3 is used as the key in step 4, the above is equal to:
\(\mathsf{key}[0] + \mathsf{ext\_nul} + \mathsf{hash}(\mathsf{ext\_nul}, \mathsf{key}[0])\)
Since \(\mathsf{hash}(x, k)\) equals \(n\) round digests of \(x\) plus \(k\), the above equals:
\(\mathsf{key}[0] + \mathsf{w}_2[0] + \mathsf{w}_2[n] + \mathsf{key}[0] =\)
\(\mathsf{w}_2[n] + (2 \cdot \mathsf{key}[0]) + \mathsf{w}_2[0])\)
6. ExternalNullifierGate
The equation is:
\(L_0(\omega_i) \cdot (\mathsf{w}_2[i] - \mathsf{ext\_nul})\)
This gate ensures that the \(\mathsf{ext\_nul}\) public input is equal to \(\mathsf{w}_2[0]\).