Table of Contents
Introduction
In the world of zero-knowledge proofs (ZKPs), Noir has emerged as a powerful, developer-friendly language for building privacy-preserving applications. Aztec has designed Noir as a domain-specific language (DSL) rooted in Rust’s principles and it simplifies the creation of complex arithmetic circuits—programs that define computations to be proven without revealing sensitive information. Noir’s design abstracts much of the cryptographic complexity, but secure circuits still depend on careful implementation, comprehensive testing, and independent review.
Noir and the Core Principles of Zero-Knowledge Systems
Noir abstracts low-level cryptographic complexities, allowing developers to focus on logic rather than circuit optimization. Unlike earlier frameworks like Circom or ZoKrates, Noir leverages Rust-like syntax and tooling to reduce boilerplate code and human error. However, this abstraction does not fully eliminate risk: The Noir compiler automates the transformation of Noir circuits into an Abstract Circuit Intermediate Representation (ACIR), which is then processed by proving systems such as Barretenberg, Groth16, and PLONK to generate and verify proofs. This abstraction removes the burden of handling complex mathematical details. However, developers remain responsible for ensuring the correctness of the circuit logic itself.
The security of these circuits rests on three foundational properties:
A single flaw in a circuit can undermine one or more of these pillars, leading to insecure or incorrect proofs.
Common Security Vulnerabilities in Noir Circuits
Below, we explore vulnerabilities that threaten the soundness, completeness, or zero-knowledge property of Noir-based systems:
1. Logical Errors in Constraints
Logical errors directly jeopardize soundness. For example, a circuit designed to prove “knowledge of a secret value greater than 100” might omit a constraint enforcing this inequality. Attackers could then submit values like 50 and “prove” compliance, tricking verifiers. Such gaps often stem from incomplete translations of requirements into circuit logic.
Audit Focus:
Example:
Consider the following circuit in which we want to constrain a user's age to be at least 18:
Since Noir feels like writing “normal” Rust-style high-level code, the condition `if is_adult` is evaluated during proving, but is not constrained. To enforce that `user.age >= 18`, the circuit implementer must explicitly assert it. The correct implementation would be:
2. Arithmetic Pitfalls in Finite Fields
Noir operates over finite fields, where arithmetics over the native Field type wraps modulo a prime number. This introduces subtle risks for the soundness of the system:
Audit Focus:
Example:
Consider the following example in which the sum of two private `Field` inputs $x,y$ is constrained to be equal to a public `Field` input $z$:
As a side comment, related to constraining values to be in an expected range, we note that Noir adds implicit `assert`-s for fixed-sized inputs such as `Field` and array. Therefore, explicit `assert`-s are necessary only for dynamic objects such as vectors, slices, etc.
3. Intent vs. Implementation Mismatches
Even well-documented circuits can harbor discrepancies between the developer’s intent and the code. For instance, a circuit meant to enforce “user X owns NFT Y” might inadvertently validate proofs although X has already sold Y. These mismatches often arise from ambiguous specifications or misunderstandings of cryptographic primitives.
Audit Focus:
Example:
Consider an authorization scenario in which a user is granted access to some resource, based on knowledge of a secret. E.g., this can be part of a larger ZK-based login system, where authorization is granted without revealing the user credentials.
A somewhat simple, but illustrative vulnerability, that could arise from insufficiently detailed specification or from a misunderstanding of cryptographic assumptions, is introduced with the following implementation:
4. Privacy Leaks
Privacy failures jeopardize the zero-knowledge property and expose sensitive data through two primary vectors:
Audit Focus:
Example:
A naive example of fully leaking a supposedly private input (age) is the following:
Conclusion
Zero-knowledge circuits succeed or fail on rigorous constraint design. By translating every business rule into explicit assertions, guarding against field-arithmetic surprises, and treating each public interface as a potential side-channel, teams preserve both soundness and privacy. As the Noir ecosystem matures, continuous peer review and thoughtful audits - whether internal or external - remain the surest path to trustworthy ZK applications.
If you have any questions about auditing Noir circuits, reach out to our ZK team.