Important
This tool is under active development. Usage patterns and features may change over time.
zkFuzz is a ZK circuit fuzzer designed to help you identify vulnerabilities in zero-knowledge proof circuits. It leverages fuzzing with program mutation to uncover counterexamples that reveal under-constrained or over-constrained behavior in your circuits. zkFuzz currently supports Circom, with support for additional languages coming soon.
Option 1: Install via Cargo
cargo install --git https://github.com/Koukyosyumei/zkFuzz zkfuzz
Option 2: Build from Source
git clone https://github.com/Koukyosyumei/zkFuzz.git
cd zkFuzz
cargo build --release
zkFuzz’s CLI provides numerous options to tailor your fuzzing session. Below is a summary of the available commands and flags:
ZK Circuit Fuzzer
USAGE:
zkfuzz [FLAGS] [OPTIONS] [--] [input]
FLAGS:
--constraint_assert_dissabled Does not add asserts in the generated code for === constraint equalities
--lessthan_dissabled (zkFuzz) Does not detect overflow erros due to LessThan template
--print_ast (zkFuzz) Prints AST
--show_stats_of_ast (zkFuzz) Prints the basic stats of AST
--print_stats (zkFuzz) Prints the stats of constraints
--print_stats_csv (zkFuzz) Prints the stats of constraints in CSV format
--symbolic_template_params (zkFuzz) Treats the template parameters of the main template as symbolic values
--save_output (zkFuzz) Save the output when the counterexample is found
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-l <link_libraries> Adds directory to library search path
-p, --prime <prime>
To choose the prime number to use to generate the circuit. Receives the name of the curve (bn128, bls12381,
goldilocks, grumpkin, pallas, vesta, secq256r1) [default: bn128]
--debug_prime <debug_prime>
(zkFuzz) Prime number for zkFuzz [default:
21888242871839275222246405745257275088548364400416034343698204186575808495617]
--search_mode <search_mode>
(zkFuzz) Search mode to find the counter example that shows the given circuit is not well-constrained [default: ga]
--heuristics_range <heuristics_range>
(zkFuzz) Heuristics range for zkFuzz [default: 100]
--path_to_mutation_setting <path_to_mutation_setting>
(zkFuzz) Path to the setting file for Mutation Testing [default: none]
--path_to_whitelist <path_to_whitelist>
(zkFuzz) Path to the white-lists file [default: none]
ARGS:
<input> Path to a circuit with a main component [default: ./circuit.circom]
Example Command:
Run zkFuzz using your circuit file written in Circom:
# Using the debug build:
./target/debug/zkfuzz ./tests/sample/iszero_vuln.circom
# Using the release build:
./target/release/zkfuzz ./tests/sample/iszero_vuln.circom
Example Output:
Fuzzing with program mutation mode (ga
mode) suppots a detailed configuration through the path_to_mutation_setting
option. The configuration is specified as a JSON file.
Here is an example of the JSON configuration schema:
{
"seed": 0,
"program_population_size": 30,
"input_population_size": 30,
"max_generations": 300,
"input_initialization_method": "random",
"trace_mutation_method": "constant",
"fitness_function": "error",
"mutation_rate": 0.3,
"crossover_rate": 0.5,
"operator_mutation_rate": 0.2,
"num_eliminated_individuals": 5,
"max_num_mutation_points": 10,
"input_update_interval": 1,
"input_generation_max_iteration": 30,
"input_generation_crossover_rate": 0.66,
"input_generation_mutation_rate": 0.5,
"input_generation_singlepoint_mutation_rate": 0.5,
"random_value_ranges": [
[ "-10",
"10"
],
[
"21888242871839275222246405745257275088548364400416034343698204186575808495517",
"21888242871839275222246405745257275088548364400416034343698204186575808495617"
]
],
"random_value_probs": [
0.5,
0.5
],
"save_fitness_scores": false
}
If the configuration JSON file omits some keys, the default values are used for those omitted keys.
Field Descriptions – Click to view all configuration options
- seed (u64)
- Purpose: Seed for random number generation to ensure reproducibility. If set to 0, a new seed is internally generated using the thread-local random number generator.
- Default: 0
- program_population_size (usize)
- Purpose: Size of the program population in the genetic algorithm.
- Default: 30
- input_population_size (usize)
- Purpose: Size of the input population in the genetic algorithm.
- Default: 30
- max_generations (usize)
- Purpose: Maximum number of generations for the evolutionary process.
- Default: 500
- input_initialization_method (String)
- Purpose: Method used to initialize inputs ("random", "fitness", "coverage").
- Default: "random"
- trace_mutation_method (String)
- Purpose: Method used for trace mutation ("naive", "constant", "constant_operator", "constant_operator_add", "constant_operator_delete").
- Default: "constant_operator"
- fitness_function (String)
- Purpose: Function used to evaluate fitness of solutions ("error", "const").
- Default: "error"
- mutation_rate (f64)
- Purpose: Rate at which mutations occur in the genetic algorithm.
- Default: 0.3
- crossover_rate (f64)
- Purpose: Rate at which crossover occurs in the genetic algorithm.
- Default: 0.5
- operator_mutation_rate (f64)
- Purpose: Rate of mutation for operators in the genetic algorithm.
- Default: 0.1
- runtime_mutation_rate (f64)
- Purpose: Specifies the mutation rate applied during runtime mutation processes.
- Default: 0.3
- num_eliminated_individuals (usize)
- Purpose: The number of individuals with poor fitness eliminated in each generation.
- Default: 5
- max_num_mutation_points (usize)
- Purpose: The maximum number of mutation points allowed in the symbolic trace.
- Default: 10
- input_update_interval (usize)
- Purpose: Interval at which inputs are updated.
- Default: 1
- input_generation_max_iteration (usize)
- Purpose: Maximum number of iterations for input generation.
- Default: 30
- input_generation_crossover_rate (f64)
- Purpose: Crossover rate for input generation.
- Default: 0.66
- input_generation_mutation_rate (f64)
- Purpose: Mutation rate for input generation.
- Default: 0.5
- input_generation_singlepoint_mutation_rate (f64)
- Purpose: Single-point mutation rate for input generation.
- Default: 0.5
- random_value_ranges (Array of Arrays)
- Purpose: Specifies ranges for random value generation. Each range is defined as a pair of big integers (provided as strings) representing the lower and upper bounds.
- Default: [["0", "2"], ["2", "11"], ["11", "21888242871839275222246405745257275088548364400416034343698204186575808495517"], ["21888242871839275222246405745257275088548364400416034343698204186575808495517", "21888242871839275222246405745257275088548364400416034343698204186575808495617"]]
- random_value_probs (Array of f64)
- Purpose: Probabilities associated with each range in `random_value_ranges`.
- Default: [0.15, 0.34, 0.01, 0.5]
- binary_mode_prob (f64)
- Purpose: Probability of restricting random input to only 0 or 1.
- Default: 0.0
- binary_mode_search_level (usize)
- Purpose: Search depth for the binary pattern (x * (1 - x) === 0) check.
- Default: 1
- binary_mode_warmup_round (f64)
- Purpose: Ratio of warmup rounds where binary_mode_prob is temporarily set to 1 upon detecting the binary pattern.
- Default: 0.0
- zero_div_attempt_prob (f64)
- Purpose: Probability of invoking the quadratic equation solver to analytically determine solutions for zero-division patterns.
- Default: 0.2
- statement_deletion_prob (f64)
- Purpose: Probability of deleting a statement during mutation.
- Default: 0.2
- add_random_const_prob (f64)
- Purpose: Probability of adding a random constant during mutation.
- Default: 0.2
- dissable_runtime_mutation_for_hash_check (bool)
- Purpose: When enabled, disables runtime mutation for hash checks.
- Default: false
- dissable_heuristic_for_invalid_array_subscript (bool)
- Purpose: When enabled, disables heuristics that handle invalid array subscripts.
- Default: false
- save_fitness_scores (bool)
- Purpose: Flag indicating whether fitness scores should be saved.
- Default: false
When the --save_output
option is enabled, the counterexample is saved to the directory when found.
Example Command with --save_output
./target/release/zkfuzz ./tests/sample/test_vuln_iszero.circom --search_mode="ga" --save_output
The output filename will follow the pattern <TARGET_FILE_NAME>_<RANDOM_SUFFIX>_counterexample.json
.
Example Output:
{
"0_target_path": "./tests/sample/test_vuln_iszero.circom",
"1_main_template": "VulnerableIsZero",
"2_search_mode": "ga",
"3_execution_time": "36.3001ms",
"4_git_hash_of_zkfuzz": "106b20ddad6431d0eee3cd73f9aac0153af4bbd9",
"5_flag": {
"1_type": "UnderConstrained-NonDeterministic",
"2_expected_output": {
"name": "main.out",
"value": "0"
}
},
"6_target_output": "main.out",
"7_assignment": {
"main.in": "21888242871839275222246405745257275088548364400416034343698204186575808495524",
"main.inv": "0",
"main.out": "1"
},
"8_auxiliary_result": {
"mutation_test_config": {
"crossover_rate": 0.5,
"fitness_function": "error",
"input_generation_crossover_rate": 0.66,
"input_generation_max_iteration": 30,
"input_generation_mutation_rate": 0.5,
"input_generation_singlepoint_mutation_rate": 0.5,
"input_initialization_method": "random",
"input_population_size": 30,
"input_update_interval": 1,
"max_generations": 300,
"mutation_rate": 0.3,
"operator_mutation_rate": 0.2,
"program_population_size": 30,
"random_value_probs": [
0.5,
0.5
],
"random_value_ranges": [
[
"-10",
"10"
],
[
"21888242871839275222246405745257275088548364400416034343698204186575808495517",
"21888242871839275222246405745257275088548364400416034343698204186575808495617"
]
],
"save_fitness_scores": false,
"seed": 0,
"trace_mutation_method": "constant"
},
"mutation_test_log": {
"fitness_score_log": [],
"generation": 7,
"random_seed": 13057132941229430025
}
}
}
zkFuzz offers multiple verbosity levels for detailed analysis with the environmental variable RUST_LOG
:
warn
: Outputs warnings and errors.info
: Includes everything fromwarn
and adds the basic statistics about the trace and constraints.debug
: Includes everything frominfo
and adds the trace of the final state.trace
: Includes everything fromdebug
and outputs all intermediate trace states during execution.
Example Command with Verbosity:
RUST_LOG=trace ./target/debug/zkfuzz ../sample/lessthan3.circom --print_ast --print_stats
Example Output:
Here are some of the most notable vulnerabilities uncovered using zkfuzz, confirmed by developers. If you’ve discovered a significant issue with our tool, we’d love to hear about it. Please submit a pull request with the relevant details.
- zkemail/zk-regex#83 (awarded a bug bounty)
- rarimo/passport-zk-circuits#60
- banyancomputer/hot-proofs-blake3-circom#11
- https://github.com/tokamak-network/zvm
- inference-labs-inc/omron-circom#2 (awarded a bug bounty)
- siv-org/secretly-cancellable-votes#13
- SpiralOutDotEu/zk_whitelist#54
- voidcenter/ew-zw#2
- wizicer/dark-factory#2
- numtel/ntru-circom#1
There are several excellent tools for bug detection and verification of ZK circuits.
- Circomspect: A linter and static analysis tool for ZK circuits
- ZKAP: A collection of static analysis tools designed to detect bugs in ZK circuits
- Picus: A tool for automatically verifying non-deterministic behaviors in ZK circuits
- ConsCS: Enables fast verification of non-deterministic properties in ZK circuits
- CIVER: Verification tool for weak safety properties, pre-/post-conditions and tag-based specifications
- CODA: A statically-typed programming language for constructing and certifying ZK circuits
Using zkFuzz in conjunction with these tools can significantly enhance the likelihood of detecting subtle and critical bugs early in the development cycle.
@misc{takahashi2025zkfuzzfoundationframeworkeffective,
title={zkFuzz: Foundation and Framework for Effective Fuzzing of Zero-Knowledge Circuits},
author={Hideaki Takahashi and Jihwan Kim and Suman Jana and Junfeng Yang},
year={2025},
eprint={2504.11961},
archivePrefix={arXiv},
primaryClass={cs.CR},
url={https://arxiv.org/abs/2504.11961},
}