Arithmetic Circuit Primer

An arithmetic circuit (or just "circuit") is a core piece in ZK proofs. The circuit takes some inputs and it allows us to express "how the computation is done" in a verifiable way. If you have no prior experience with arithmetic circuits, it is recommended to read Vitalik's intro to arithmetic programs. Though one can go very deep into this topic, surprisingly, there aren't many concepts you need to know before you can write circuit applications. Here are some key points to remember:

Circuits are "Fixtures"

When you compile and setup a circuit, you get a proving key (PK) and a verifying key (VK). These keys are unique per circuit. It means if you change any circuit logic and recompile, these won't be the same. Normally in the blockchain space, you want your contract to hold the verifying key.

Just like how you can be sure that an Ethereum transaction is signed correctly by someone using their private key if you can recover their public key (address) from it, if you can verify a set of inputs against a proof using your VK, it means the proof is for sure generated by someone using the set of inputs in the circuit that corresponds to your VK.

By using the PK and the circuit, it allows us to prove that because a set of inputs satisfy a fixed circuit, the inputs are correct in terms of that circuit.

Variables Have an "Upper Limit"

Much like how you can only use up to 2^64 - 1 for uint64 in normal programming languages, in circuits, we also have a limit for circuit variables. This upper limit is dependent on which elliptic curve the underlying proving system uses, but in our framework it's always the max scalar field number of BLS12_377. It is ok to not know what this is as long as we keep in mind the max limit is 2^252 (or 248 bits if we round down to the nearest byte, which is 31 bytes). Because actual integer values on Ethereum rarely reach their type limit (uint256), this means for most cases, we can represent Solidity uint256 numbers as circuit variables. But hashes (bytes32) are often truly 32 bytes, and cannot be fit into 31 bytes (our upper limit), how do we represent them in circuit? The Brevis SDK simply "chops off" the bytes32 at a certain point and puts them into two circuit variables. That's it.

All Operations are Arithmetic

This might be the most counter-intuitive part. The circuits we write are not the same as the programs we use to do the writing. The biggest difference comes from that any meaningful "logic" is implemented through arithmetic gates. For example, there is no comparison operator (a == b), instead, such checks can be done through subtracting b from a and checking whether the result is 0.

There are No "Dynamic" Circuits

Just like what you would expect when you solder a physical circuit, you wouldn't expect the wires to magically desolder themselves and jump around at runtime. Our arithmetic circuits are the same. If you wire some variables together through some arithmetic gates, the wires are soldered once you compile, and your if statements will not make them desolder themselves and jump around at runtime.