---
title: Tips for secure Circom circuits
tags: Templates, Talk
description: View the slide with "Slide Mode".
---
# Tips for safe circom circuits
---
### Write documentation and comments
<!-- - Circom is not a general purpose language. -->
- Write detailed documentation and spec before writing the circuits.
- Bugs can be discovered here!
- Will help you while writing and reviewing the circuits.
- *Example*: Something is intended to be private but it's actually not in circuits.
---
### ZK stack
- Underlying theory (Groth16, Plonk, STARK)
- Implementing theory: Circom / Halo2 / Winterfell - Proving and Verifiction
- Application: Circuits in Circom - Easiest to start with if no cryptography background.
---
### Bugs in ZK applications
- Privacy bugs: expected private property not private.
- Verify incorrect computation, or reject correct computation.
---
### Types of circuits
- Cryptography primitives: ECDSA, Hash
- Libraries: LessThan, IsZero
- User Applications: Tornado Cash, zk rollups
You may have to write libraries yourself to write applications.
---
### Understand Elliptic Curves
- Many cryptographic primitives rely on Elliptic curves.
- Ethereum addresses, ECDSA, BLS signatures, Hashes, ...
---
### Split computation and constraints
- Computation and proof generation is in adversarial setting.
- Just focusing on constraints helps internalize this fact.
```js
signal c <== a*b;
```
Split it into:
```js
c <-- a*b;
```
and
```js
c === a*b;
```
---
```js
function quotient_and_remainder(a,b) {
return [a/b, a%b];
}
signal input a;
signal input b;
signal qr[2] <-- quotient_and_remainder(a,b);
a === qr[0]*b + qr[1]
```
---
Removing all computation
```js
a === qr[0]*b + qr[1]
```
- Does this ensure that `qr[0]` and `qr[1]` are quotient and remainder?
---
Removing all computation
```js
a === qr[0]*b + qr[1]
```
- Does this ensure that `qr[0]` and `qr[1]` are quotient and remainder?
- Missing constraint: `qr[1] < b`.
---
### Signal overflow
ZK circuits operate on field elements:
$$
(p-1) + 1 = 0 \mod p
$$
```js
signal a <== x+y+z;
```
For `a=11`:
```json
"x": "5",
"y": "7",
"z": p-1
```
```json
"x": "5",
"y": "6",
"z": "0"
```
```
p=21888242871839275222246405745257275088
548364400416034343698204186575808495617
```
---
- EVM uint size: 256 bits
- BN254 prime field, common Circom field: 254 bits
- `uint` passed as a public input to a verifier may overflow.
- Ensure `uint` fits in the field range (`< p`).
---
### Library Integration
- Library assumptions should be documented.
- Libraries built for high-performance circuits do not enforce preconditions on inputs.
- Minimizing constraints is critical for circuits built on top of it.
- Precondition constraint is done outside the library.
---
### `LessThan`
- You may expect `LessThan(n)` to compare two signals of max `n` bits, but it doesn't verify the bit length.
- According to `LessThan(2)`: `1 >= p-2`.
- Fix: Constraint the inputs to specific max bit using `Num2Bits`.
---
### Under-constrained signals
- A broad class of bugs.
- Free input signals.
- Underconstrained signals during library integration.
- Or simply just missing contraining a signal.
---
### Logic Bugs
- The algorithm developed through the circuits. Any footguns or bugs there?
- What if a shielded pool allows depositing arbitrary amount?
- Integration with smart contracts.
---
### Testing
- [circom_tester](https://github.com/iden3/circom_tester) by iden3: afaik can't test with fake witness.
- [circomkit](https://github.com/erhant/circomkit) by [erhant](https://twitter.com/0xerhant): can test with fake witness.
- Formal verification tools: CODA, Picus by Veridise. [circom-bigint report](https://f8t2x8b2.rocketcdn.me/wp-content/uploads/2023/02/VAR-circom-bigint.pdf).
- [circomspect](https://github.com/trailofbits/circomspect) by ToB: static analyzer.
---
### Resources
- ZK bug tracker: https://github.com/0xPARC/zk-bug-tracker
- Moonmath manaul: https://github.com/LeastAuthority/moonmath-manual
- Elliptic curves basics: https://hackmd.io/@blockdev/BJIW0Rlr3
- Rareskills ZK book: https://www.rareskills.io/zk-book