Warning
This project is under development and might contain bugs.
Noir Static Analyzer is a tool designed for Noir, a domain-specific language (DSL) for writing zero-knowledge proofs. Inspired by Cargo Clippy and Cargo Check, it provides static analysis for Noir programs, making it familiar to Rust developers. Noir's syntax closely resembles Rust, borrowing its control flow, functions, and type system.
- Modular architecture: Designed to support multiple lint rules.
- AST-based analysis: Currently, it uses Noir’s Abstract Syntax Tree (AST) for linting.
- Example lint implemented:
unused-functiondetects unused private andpub(crate)functions. - Future potential: Some lints might use ACIR (Abstract Circuit Intermediate Representation) for deeper analysis.
Analyzing ACIR could enable:
- Detecting unnecessary constraints in circuits.
- Optimizing witness assignments.
- Identifying redundant gates in the proof system.
You can install the analyzer using Cargo:
cargo install --git https://github.com/walnuthq/noir-static-analyzerAlternatively, clone the repository and build it manually:
git clone https://github.com/walnuthq/noir-static-analyzer.git
cd noir-static-analyzer
cargo build --releaseTo run the analyzer on a Noir project, use:
cargo run --release -- --manifest-path <path-to-Nargo.toml>By default, it looks for Nargo.toml in the current directory.
Given the following Noir code:
fn private_fn_1() {}
fn private_fn_2() {}
pub(crate) fn crate_fn_1() {}
pub(crate) fn crate_fn_2() {}
pub fn public_fn_1() { private_fn_1() }
pub fn public_fn_2() { public_fn_1() }
pub fn public_fn_3() { crate_fn_1() }The analyzer reports:
Using manifest path: "Nargo.toml"
Workspace root: ""
Package: hello
Entry point: "src/main.nr"
warning: Function 'private_fn_2' is unused
--> src/main.nr:2:19
| fn private_fn_2() {}
^
warning: Function 'crate_fn_2' is unused
--> src/main.nr:4:28
| pub(crate) fn crate_fn_2() {}
A short demo showcasing how the analyzer works is available:
noir-static-analyzer-demo.mp4
Unused or Redundant Code Lints
- Unused Function
- Unused Variable / Value
- Unused Import
- Duplicate or Redundant Constraint
- Redundant Control Flow
ZK-Specific Lints
- Unconstrained Variable
- Public Output Depending on Private Input
- Missing Range Checks on Integers
- Improper Use of Unconstrained Functions
- Ineffective Constraints or Always-True Assertions
Style Lints
- Naming Conventions
- Shadowing Variables
- Overly Complex Function
- Idiomatic Code Suggestions
Performance Lints
- Dead Stores and Unused Assignments
- Inefficient Looping Constructs
- Redundant Re-computation
- Use of Non-ideal Operations
- Large Constraints or Wide Integers Usage
Correctness Lints
- Unchecked Division or Modulus
- Missing Constraints
- Ignored Return Values
- Inconsistent Type Usage or Overflow Risk
- Constant or Unreachable Branch Conditions
- Noir AST (used for analysis): noirc_frontend AST
- ACIR (potential future analysis): ACIR repository
Contributions are welcome! Feel free to open issues or pull requests in the GitHub repository.
-
Unused Function
Detect functions that are never called anywhere. This is already implemented in the Noir Analyzer (e.g. flagging a helper function that isn’t referenced) and is similar to Rust’sdead_codelint.
Removing unused functions reduces code size and audit surface. If the function is intended for future use or an external call, developers can annotate it (once Noir supports an attribute similar to#[allow(dead_code)]). -
Unused Variable/Value
Warn if a local variable is never read after being assigned, or if a value is calculated and not used. This includes function parameters that are never used inside the function body.
Such code might be vestigial or a sign that something was forgotten. For example, an unused function argument might indicate an incomplete implementation.
This lint aligns with common practice in many languages (Rust compiler warns on unused variables unless prefixed with_). It helps declutter the code and catch potential mistakes. -
Unused Import
If a module or package is imported but none of its definitions are used, the static analyzer should warn to remove it.
This is analogous to Rust’s unusedusewarnings and keeps the circuit code minimal. Removing unused imports can slightly reduce compile times and avoid confusion about what dependencies are actually needed. -
Duplicate or Redundant Constraint
Identify if the exact same assertion or requirement is written twice, or if one logically subsumes another.
For instance:assert(x < 100); assert(x < 100); // duplicate
Or:
assert(x == y); assert(x - y == 0); // redundant
While the constraint system will simply have a duplicate constraint (which doesn’t harm correctness), it’s inefficient. The lint can merge or flag duplicates.
-
Redundant Control Flow
Flag anyif/elseblocks ormatcharms that do nothing or are identical, as well as empty loops.
For example:if condition { do_something(); } else { do_something(); // identical to above }
The else branch repeats the same code, so the condition is irrelevant. Or:
for i in 0..n { // empty loop body }
- Unconstrained Variable The lint triggers if a private witness or intermediate variable does not appear in any constraints, equality, or output - the value is never uset to enforce anything.
- Public Output Depending on Private Input Warn if private (witness) value flows directly into a public output or public-facing variable without any cryptographic transformation
- Missing Range Checks on Integers If unconstraind Field is used for values that have an expected range, the circuit might accept invalid out-of-range values.
- Improper Use of Unconstrained Functions
- Ineffective Constraints or Always-True Assertions
- Naming Conventions
- Shadowing Variables
- Overly Complex Function
- Idiomatic Code Suggestions
- Dead Stores and Unused Assignments
- Inefficient Looping Constructs
- Redundant Re-computation
- Use of Non-ideal Operations
- Large Constraints or Wide Integers Usage
- Unchecked Division or Modulus
- Missing Constraints
- Ignored Return Values
- Inconsistent Type Usage or Overflow Risk
- Constant or Unreachable Branch Conditions