Under-Constrained Circuits vulnerability
Constraints in ZK circuits are used to create boundaries on the inputs. The lack of an important constraint can lead to the Verifier accepting faulty proofs from the Prover.
Also, a common misconception during the development of a ZK circuit, for example, with popular Rust libraries, is that the code of a Prover application restricts the inputs. In reality, the input is only restricted if a constraint is applied. Sometimes, developers can intentionally perform computations without constraints and apply them later indirectly to optimize the circuit. This approach should be taken with caution because it can make the circuit nondeterministic and can introduce some unnecessary flexibility and fragility.
Example
Consider the following Circom code[1]:
pragma circom 2.0.0;
template NonIdentityFactors() {
signal factorOne;
signal factorTwo;
signal val;
val <== factorOne * factorTwo;
}
component main{public [val]} = Factors();
As you can see, the purpose is to verify that the Prover knows two secret factors such that their multiplication equals the public parameter val
. There are three problems:
- Factors can be equal to one.
- Factors can be negative.
- The circuit doesn't check for overflow.