// inside head tag

ZK Circuit Security: A Guide for Engineers and Architects

Security

October 31, 2025

ZK Circuit Security: A Guide for Engineers and Architects

Security

October 31, 2025

Introduction

ZK Circuits are a core component of integrating ZK proofs into real world applications, enabling the proving of arbitrary NP complete statements privately. A 2024 analysis revealed that approximately 96% of documented bugs in SNARK-based ZK systems were caused by under-constrained circuits, which are circuits that lack sufficient checks to prevent invalid proofs from passing verification.

These vulnerabilities have enabled everything from unlimited token counterfeiting (Zcash, 2018) to $1.9 billion in forged withdrawals (zkSync Era, 2023). Thus, it is necessary to understand the security considerations when writing and designing circuits for usage in protocols and applications.

Figure 1: How Zero-Knowledge Proofs Work. A prover convinces a verifier that a statement S is valid by sending a proof π and public values x, without revealing the private witness w.

What are zK Circuits?

ZK Circuits are a way of representing statements to be proven through the usage of Zero Knowledge. Essentially, a prover \( P \) is able to convince a verifier \( V \) that an arbitrary statement \( S \) is true given a set of inputs separated into witness (secret values) \( w \) and public values \( x \), by producing a proof \( \pi \). This proof does not reveal anything about the witness, but allows the verifier \( V \) to be convinced of the validity of the statement.

Specifically, a ZK circuit is a way of transforming a statement to prove into a set of arithmetic constraints (defined typically over a finite field) to be proven using a zero knowledge proof. Although this article will not go into the cryptographic details behind generating the proof, it is important the reader understands the following properties of ZK proofs:

  1. Completeness: all true statements must always verify.
  2. Soundness: an invalid statement must verify with negligible probability.
  3. Zero-Knowledge: the proof must not leak information about the witness.

For readers interested in going into more detail in the cryptography we recommend [VITALIK, THALER].

We intend this article for ZK circuit architects and engineers writing in zkDSL’s. They must be aware of common vulnerabilities and this article aims to present a security engineering perspective of writing circuits. Specifically, it will outline ZK vulnerabilities relevant to ZK engineers and ZK circuit designers as well as the security considerations to take into account when writing and designing the circuits. Finally, we will present a checklist to ensure security and privacy.

Vulnerability Classification

It is important to present a generalisation of important vulnerabilities that exist within ZK circuits and are most likely to lead to issues which would break the ZK properties introduced above.

Under-Constrained Circuits

The most common vulnerability is under-constrained circuits, compromising the soundness property of ZK. Essentially, there are not enough checks thus allowing invalid inputs to pass and generate a valid proof.

A simple example of an under-constrained circuit is the following check if a value is a multiple of two values, where the user supplies the two factors for the given value. The example is written in Circom, a popular zkDSL:

template isFactor() {
    signal input valOne;
    signal input valTwo;
    signal input val;

    val === valOne * valtwo;
}

To prove a value val is a multiple of two factors, the user supplies two other inputs valOne and  valTwo which, for the proof to pass, must multiply together to get val . A constraint is thus made to enforce that val must be equal to valOne multiplied by valTwo .

However, there is no enforcement that the value proven is not the number zero. Specifically, by setting zero to val and also setting valOne (or valTwo ) to zero a malicious user can prove that 0 is a factor of 0. Although it satisfies the equation in the circuit, in maths it is not possible for 0 to be a factor and thus this proof should fail.

It's essential to analyse and test the circuit using edge cases and to have a clear understanding of what is and isn't permissible within the circuit.

Over-Constrained Circuits

A less common but similar problem circuits face is over constraining them preventing valid inputs from generating a valid proof. This is a lack of the completeness property. Usually these are logic issues that stem from an incomplete understanding of what is being proven.

A clear example is depicted below when it checks if a value is the square of another but additionally adds an unnecessary check:

template isSquare() {
    signal input y;
    signal input x;

    x * x === y;

    component n2b = Num2Bits(32);
    n2b.in <== x;  
}

This is aiming to prove that there exists some root \( x \) for a value \( y \). However, by also requiring \( x \) to be 32 bits, it excludes valid roots outside the range \( 0 < x < 2^{32} \) which also satisfy \( x^2 = y \) within the field that Circom is working over.

Circuit Vulnerabilities

Having understood the classification of vulnerabilities it is possible to present the actual vulnerabilities themselves that lead to both under and over-constrained circuits.

Arithmetic Over/Underflows

As circuits operate within scalar fields and thus perform modular mathematics, it is possible for two values to be equivalent within the field. In maths, this is described as \( x \equiv y \mod{\mathbb{F}} \) where \( x, y \) are values in the field \( \mathbb{F} \).

Similarly, in circuits and in zkDSL’s that wrap around the modulus, such as Circom, it is necessary to keep in mind that arithmetic overflows and underflows are very possible. As mostly the BN254 elliptic curve is used, the soundness loss can be illustrated as follows:

// BN254 Field Comparision
0-1 === 21888242871839275222246405745257275088548364400416034343698204186575808495617

To demonstrate this issue, we provide the example of the range check.

Range Check: Proving that \( x > y \) is not an arbitrary computation in ZK and can easily lead to malicious inputs based on the issue presented above. Thus, such comparisons must be carefully considered when writing circuits in relation to arithmetic over and underflows.

In the simplest of cases this must be performed by converting the input to its bit representation and comparing as such. A full explanation can be found at [RARESKILLS] but for our purposes it is necessary to perform the following.

  1. Convert to binary: Convert \( x, y \) into binary representation with maximum bit length \( n - 1 \). In Circom this is done by calling Num2Bits(n - 1), see CircomLib’s bitify.
  2. Calculate difference: Calculate \( x_2 - y_2 = \Delta \) to determine \( x > y \) in binary representation. If this is positive, then it is true; otherwise, it is not.
  3. Convert maximum value to binary: Convert \( 2^{n-1} \) into binary representation with maximum bit length \( n \) (one more than the representation of \( x \) and \( y \)). This should look like 011... depending on the size of \( n \), where the most significant bit (the leftmost bit) is zero and the rest are 1’s.
  4. Verify comparison: To check that \( x > y \), the \( \Delta \) calculated must be added to \( 2^{n-1} \) and the most significant bit must be checked. If it is 1, i.e. \( \Delta \) is positive, the statement \( x > y \) is true; otherwise, it is not.

Circomlib constructs it as follows:

// a < b
// out = 1 if a < b
template LessThan(n) {
  assert(n <= 252); 
  signal input in[2];
  signal output out;

  component n2b = Num2Bits(n+1);

  n2b.in <== in[0] + (1<<n) - in[1];

  out <== 1 - n2b.out[n];
}

It is thus clear that simple logic can be much more complicated to perform when done in a circuit, which means that it must carefully considered from the beginning of the circuit construction and design. Arithmetic over and under-flows must be checked with care and threaten the soundness of the protocol.

Public Signal Vulnerabilities

The third pillar of circuits is the privacy and ZK property offered. Although not all protocols rely on this, privacy focused protocols must understand that certain public signals can reveal too much information about what is processed in the circuit. This would end up reducing the privacy and undermine the circuit.

ZK Proofs enable signals to be both private and public. For example, in blockchain applications where ZK is used extensively it is possible to keep a root of a merkle tree on chain and prove in a circuit that a leaf exists in this tree. The input to this circuit would thus be the root, the leaf itself and the path to the root.

Insufficient Public Signals (Soundness Risk)

If the root is not public but rather kept private along with the remaining inputs this could pose a security risk as the prover can prove for any root of their choice. To prevent this the root must be made public and the verifier must source the root from a trusted location to verify that the proof was generated using that root.

Excessive Public Signals (Privacy Risk)

On the other hand, revealing too many details about the signals can leak private information. In the merkle tree example, the leaves must be kept secret otherwise the information it holds will be revealed.

A correct setup for the example would be the following:

template merkleVerifier(n) {
	signal input leaf; // private
	signal input path[n]; // private
	signal input root; // public
}

Thus a thorough review of the public and private signals is paramount to keeping the integrity of the circuit and its usage in a larger protocol.

Unconstrained Public Signals

Similar to the previous issue, unconstrained public signals can allow double verifying with the same proof reducing the proof’s soundness.

Due to optimisations provided by zkDSLs it is possible for the signal d to be removed from the circuit below since it is not constrained.

template badPublicCircuit() {
	signal input a;
	signal input b;
	signal input c;
	signal input d; // public signal

	a + b === c;
}
component main{public [d]} = badPublicCircuit();

A prover can submit the same proof multiple times by changing the value of d each time. When submitting a proof to the verifier, any value for d will succeed on an existing proof. In blockchain applications, this enables replay attacks where an attacker reuses a legitimate proof to perform unauthorized actions multiple times.

If a public input (e.g., a hashed message, signal, or identifier) is included in the proof but not enforced by the relation that the prover proves, an adversary can reuse a legitimately generated proof while substituting that public input. Because verification only checks the proof against the supplied public inputs, the modified proof will validate for the attacker’s chosen input — effectively forging arbitrary public values.

Thus it is required to evaluate public signals and assess whether they are being used within the circuit or not. Should they not be constrained, they must be removed.

Circuit Design Security Considerations

Having outlined the most common and important issues circuit engineers and architects must be aware of, it is thus necessary to outline the security considerations when designing a circuit.

The first stage in designing a circuit is to describe the statement to be proven. Then it must be translated to an arithmetic or a binary circuit. Finally, the circuit can then be written in the chosen zkDSL. It is during each of these stages that various security considerations must be taken into account.

Problem Statement Definition Security Considerations

At the start, it is paramount to understand what exactly is to be proven. As most circuits are used within larger protocols, say on the blockchain, defining the circuit inputs, gates and outputs is critical and the larger protocol must be taken into account.

“Where the proof is verified?”, “how it is used?”, and “what is it proving?”, are all questions that should be asked at this stage.

It must be clear what the problem statement to be proven is. Example’s can be “is a leaf found in the root of the merkle tree?”, “does the owner of this commitment know its contents?” or any NP complete statement.

Within the wider protocol, it must be clear where it is to be used and how the verification occurs. Based on the public values there must be secure verification.

⚠️ Example: Unverified Public Key Attack: For instance, consider a circuit that proves knowledge of a digital signature over a message, where the only public value is the claimed public key. If the circuit verifies the signature correctly but makes no guarantees about the authenticity of the public key itself, a malicious prover could generate their own key pair, sign arbitrary messages, and then pass the proof. Unless the public key is verified against a trusted registry or tied to an authenticated identity within the wider protocol, the proof conveys no meaningful assurance.

Arithmetic/Binary Circuit Design Security Considerations

When translating the problem statement into an arithmetic or binary circuit it is important to take into account how the problem is translated, verifying it and understanding the limitations of zk languages.

When translating the problem statement into a circuit, a number of subtle but critical issues arise. The most important are correctness of translation and constraint completeness. A poorly translated statement can weaken the soundness guarantee, as the circuit only enforces a subset of the intended rules, invalid proofs may still pass.

Beyond correctness, how information is represented and processed must also be taken into account. Arithmetic overflows and modular reductions pose additional risks, as many zk systems operate modulo a field and unintended wraparound can lead to unintended security issues.

Implementation Security Considerations

The final stage is that of actually writing the circuit in a chosen DSL. It requires assessment of the zkDSL landscape and an understanding of proof systems, at least in a high level, to be able to weigh the benefits and draw backs of each one. This article has focused mainly on Circom proving as it is the best for illustrating the examples described, however others such as Noir, Halo2 and Gnark (non exhaustive list) do exist and each have their nuances and optimisations that differ from Circom. Assessing each one is out of scope for this article and ultimately is dependent on the protocol and system attempting to be built.

During the writing stage, once the DSL and proof system have been selected, it is good practice to use trusted, existing circuits that have been audited. Most use cases of circuits will naturally build upon other systems and thus it is best to use the trusted implementations rather than attempting to create a new one entirely. Examples include: Circom’s Circomlib and Noir’s standard library. For example, when trying to prove inclusion in a merkle tree, attempting to write the hash function, Poseidon, Blake and others, from scratch is inadvisable as these complicated structures need attention to detail and are probably out of scope for the protocol most zk engineers work on. They require additional time, effort and complicated cryptography is known to be challenging to implement.

Finally, once the circuit is written an assessment of the constraints must be undertaken to review whether an optimisation is possible, or if the circuit has been overoptimized leading to an underconstrained circuit. Minimising constraints is essentially the fastest way to decrease proving (and verifying) time, and thus it is important to assess the constraints in a circuit. As discussed earlier, over-constraining can lead to valid proofs not passing so reviewing constraints is a necessity. On the other hand, optimising too much can lead to underconstraining and thus soundness issues.

It is paramount to have a third party review it by performing formal verification or a security audit. This will provide assurance to both the engineers as well as users of the circuit that it is indeed providing the privacy and security guarantees advertized. A good security audit will provide not only security insights but also optimisations and a precise review of the constraints within the context of the wider protocol as the circuit is only as secure as its usage.

Conclusion

In summary, this article introduced the concept of zk circuits, common vulnerabilities present in circuits and security considerations that zk circuit engineer’s and architects must be aware of. Specifically, the article discussed how over and under constraining circuits pose a threat to the properties of ZK, soundness and completeness, as well as how the public value selection can hinder the privacy the circuit aims to provide.

The engineers and architects must take into account key security considerations when designing and writing circuits that will have direct affects on the guarantees of the circuit. It is therefore necessary to book an audit to assess the security and privacy of the circuit within the context of the wider protocol.

Nethermind has a strong security team who have proven experience in assessing the security of zk circuits and reviewing their usage within wider protocols from anonymous credential protocols, to staking services in a wide range of languages.

Next Steps

Start by running the security checklist below against your existing circuits. For production ZK systems, consider engaging our team for a comprehensive circuit audit and threat modeling session.

Nethermind Security has proven experience auditing ZK circuits across multiple ecosystems, including formally verifying zkSync's on-chain verifier, auditing Noir circuits for SAMM Protocol, and formal verification of SP1 and RISC Zero zkVMs. Contact our security team to discuss your project.

zK Security Checklist

Use this checklist to assess the security posture of your ZK circuits. We recommendrunning through this before any audit engagement.

-> FILE HERE

Latest articles