Skip to content

Commit 8eca121

Browse files
committed
Merge branch 'main' of github.com:zksecurity/zkregex_fuzzer into noir-runner
2 parents 8057e22 + 8a0fdda commit 8eca121

24 files changed

+719
-308
lines changed

.github/workflows/ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: CI Checks
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
8+
jobs:
9+
run-checks:
10+
name: Run Linting & Tests
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v4
16+
17+
- name: Install system dependencies
18+
run: sudo apt-get update && sudo apt-get install -y graphviz graphviz-dev
19+
20+
21+
- name: Set up Python
22+
uses: actions/setup-python@v4
23+
with:
24+
python-version: "3.13"
25+
26+
- name: Install dependencies
27+
run: |
28+
python -m pip install --upgrade pip
29+
pip install -e .[dev]
30+
31+
- name: Run Linter & Tests
32+
run: python scripts/lint_and_tests.py
33+

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ source venv/bin/activate
1212
Next, you can run the fuzzer locally:
1313

1414
```
15-
pip install -r requirements.txt
15+
pip install -e .
1616
python src/zkregex_fuzzer/cli.py --help
1717
```
1818

@@ -23,6 +23,18 @@ pip install -e .
2323
zkregex-fuzzer --help
2424
```
2525

26+
## Linting and tests
27+
28+
```
29+
pip install -e '.[dev]'
30+
# Either run manually
31+
ruff check .
32+
ruff format .
33+
pytest
34+
# or through the script
35+
python scripts/lint_and_tests.py
36+
```
37+
2638
## Example Run
2739

2840
### Fuzzing

pyproject.toml

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,39 @@ authors = [
1111
]
1212
license = { text = "MIT" }
1313
readme = "README.md"
14-
requires-python = ">=3.8"
14+
requires-python = ">=3.13"
1515
keywords = ["fuzzing", "grammar", "zkregex"]
1616
classifiers = [
1717
"Programming Language :: Python :: 3",
1818
"License :: OSI Approved :: MIT License",
1919
"Operating System :: OS Independent"
2020
]
2121
dependencies = [
22-
"fuzzingbook>=0.11.0",
22+
"fuzzingbook",
2323
"rstr",
2424
"exrex",
2525
]
2626

27+
[project.optional-dependencies]
28+
dev = [
29+
"ruff",
30+
"pytest",
31+
]
32+
33+
[tool.ruff]
34+
line-length = 88
35+
target-version = "py38"
36+
lint.select = ["E", "F", "W"]
37+
lint.extend-select = ["I"]
38+
lint.ignore = ["F401", "E501"]
39+
40+
[tool.ruff.format]
41+
quote-style = "double"
42+
indent-style = "space"
43+
2744
[project.scripts]
2845
zkregex-fuzzer = "zkregex_fuzzer.cli:main"
46+
47+
# Automatically find packages under `src/`
48+
[tool.setuptools.packages.find]
49+
where = ["src"]

requirements.txt

Lines changed: 0 additions & 3 deletions
This file was deleted.

scripts/lint_and_tests.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import subprocess
2+
import sys
3+
4+
5+
def run_command(command: str) -> int:
6+
"""Runs a shell command and returns the exit code."""
7+
print(f"Running: {command}")
8+
process = subprocess.run(command, shell=True)
9+
return process.returncode
10+
11+
12+
def run_linter() -> int:
13+
"""Runs Ruff to check for linting errors."""
14+
print("Running Ruff Linter...")
15+
return run_command("ruff check .")
16+
17+
18+
def run_formatter() -> int:
19+
"""Runs Black to check for formatting errors."""
20+
print("Running Black Formatter...")
21+
return run_command("ruff format .")
22+
23+
24+
def run_tests() -> int:
25+
"""Placeholder for future test execution."""
26+
print("Running Tests (Placeholder)...")
27+
return run_command("pytest")
28+
29+
30+
if __name__ == "__main__":
31+
lint_exit_code = run_linter()
32+
test_exit_code = run_tests()
33+
34+
if lint_exit_code != 0 or test_exit_code != 0:
35+
print("❌ Checks failed!")
36+
sys.exit(1) # Fail GitHub Action if linting or tests fail
37+
else:
38+
print("✅ All checks passed!")
39+
sys.exit(0)

src/zkregex_fuzzer/__init__.py

Whitespace-only changes.

src/zkregex_fuzzer/cli.py

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,44 @@
77
import random
88
import uuid
99
from pathlib import Path
10-
from zkregex_fuzzer.reproduce import reproduce
10+
11+
from zkregex_fuzzer.configs import GENERATORS, TARGETS, VALID_INPUT_GENERATORS
1112
from zkregex_fuzzer.fuzzer import fuzz_with_database, fuzz_with_grammar
1213
from zkregex_fuzzer.grammar import REGEX_GRAMMAR
13-
from zkregex_fuzzer.configs import TARGETS, VALID_INPUT_GENERATORS, GENERATORS
1414
from zkregex_fuzzer.harness import HarnessStatus
1515
from zkregex_fuzzer.logger import logger
16+
from zkregex_fuzzer.reproduce import reproduce
1617
from zkregex_fuzzer.runner.circom import CircomSubprocess, SnarkjsSubprocess, ZkRegexSubprocess
1718
from zkregex_fuzzer.runner.subprocess import BarretenbergSubprocess, NoirSubprocess
1819

1920
def fuzz_parser():
20-
parser = argparse.ArgumentParser(
21-
add_help=False
22-
)
21+
parser = argparse.ArgumentParser(add_help=False)
2322
parser.add_argument(
2423
"--regex-num",
2524
type=int,
2625
default=10,
27-
help="Number of regexes to generate (default: 10)."
26+
help="Number of regexes to generate (default: 10).",
2827
)
2928
parser.add_argument(
3029
"--inputs-num",
3130
type=int,
3231
default=10,
33-
help="Number of inputs to generate for each regex (default: 10)."
32+
help="Number of inputs to generate for each regex (default: 10).",
3433
)
3534
parser.add_argument(
3635
"--oracle",
3736
choices=["valid", "invalid"],
38-
help="Wherether the generated inputs should be valid or invalid wrt the regex."
37+
help="Wherether the generated inputs should be valid or invalid wrt the regex.",
3938
)
4039
parser.add_argument(
4140
"--target",
4241
choices=list(TARGETS.keys()),
43-
help=f"The target to fuzz (options: {list(TARGETS.keys())})."
42+
help=f"The target to fuzz (options: {list(TARGETS.keys())}).",
4443
)
4544
parser.add_argument(
4645
"--valid-input-generator",
4746
choices=list(VALID_INPUT_GENERATORS.keys()),
48-
help=f"The valid input generator to use for the fuzzer (options: {list(VALID_INPUT_GENERATORS.keys())})."
47+
help=f"The valid input generator to use for the fuzzer (options: {list(VALID_INPUT_GENERATORS.keys())}).",
4948
)
5049
parser.add_argument(
5150
"--seed",
@@ -62,7 +61,7 @@ def fuzz_parser():
6261
"--save-output",
6362
type=str,
6463
default=os.getcwd(),
65-
help=f"The output path where the reproducible files will be stored (default: .)"
64+
help="The output path where the reproducible files will be stored (default: .)",
6665
)
6766
parser.add_argument(
6867
"--fuzzer",
@@ -74,31 +73,31 @@ def fuzz_parser():
7473
"--grammar-max-depth",
7574
type=int,
7675
default=5,
77-
help="Maximum depth of recursion in the grammar (default: 5)."
76+
help="Maximum depth of recursion in the grammar (default: 5).",
7877
)
7978
parser.add_argument(
8079
"--max-input-size",
8180
type=int,
8281
default=600,
83-
help="Maximum size of the circuit input (default: 600)."
82+
help="Maximum size of the circuit input (default: 600).",
8483
)
8584
parser.add_argument(
8685
"--circom-library",
8786
nargs="*",
8887
type=str,
89-
help="Path to the circom library to be included"
88+
help="Path to the circom library to be included",
9089
)
9190

9291
parser.add_argument(
9392
"--circom-prove",
9493
action="store_true",
95-
help="Run the proving and verification step with SnarkJS."
94+
help="Run the proving and verification step with SnarkJS.",
9695
)
9796

9897
parser.add_argument(
9998
"--circom-ptau",
10099
type=str,
101-
help="Path to the ptau (powers-of-tau) file for the proving step"
100+
help="Path to the ptau (powers-of-tau) file for the proving step",
102101
)
103102

104103
parser.add_argument(
@@ -117,17 +116,16 @@ def fuzz_parser():
117116

118117
return parser
119118

119+
120120
def reproduce_parser():
121-
parser = argparse.ArgumentParser(
122-
add_help=False
123-
)
121+
parser = argparse.ArgumentParser(add_help=False)
124122

125123
parser.add_argument(
126124
"--path",
127125
nargs="+",
128126
type=str,
129127
help="Path to the target directory output that want to be reproduced (support wildcard pattern).",
130-
required=True
128+
required=True,
131129
)
132130
parser.add_argument(
133131
"--logger-level",
@@ -138,11 +136,12 @@ def reproduce_parser():
138136

139137
return parser
140138

139+
141140
def do_fuzz(args):
142141
if args.oracle == "valid" and not args.valid_input_generator:
143142
print("Valid input generator is required for valid oracle.")
144143
exit(1)
145-
144+
146145
if args.target == "circom":
147146
try:
148147
zk_regex_version = ZkRegexSubprocess.get_installed_version()
@@ -177,7 +176,7 @@ def do_fuzz(args):
177176
if not args.circom_ptau:
178177
print("Path to ptau file is required for proving.")
179178
exit(1)
180-
179+
181180
ptau_path = Path(args.circom_ptau).resolve()
182181
if not ptau_path.exists():
183182
print(f"Path to ptau file {ptau_path} does not exist.")
@@ -221,46 +220,44 @@ def do_fuzz(args):
221220
regex_num=args.regex_num,
222221
inputs_num=args.inputs_num,
223222
max_depth=args.grammar_max_depth,
224-
kwargs=kwargs
223+
kwargs=kwargs,
225224
)
226225
elif args.fuzzer == "database":
227226
fuzz_with_database(
228227
target_implementation=args.target,
229228
oracle_params=(args.oracle == "valid", args.valid_input_generator),
230229
regex_num=args.regex_num,
231230
inputs_num=args.inputs_num,
232-
kwargs=kwargs
231+
kwargs=kwargs,
233232
)
234233

234+
235235
def do_reproduce(args):
236236
reproduce(args.path)
237237

238+
238239
def main():
239-
240240
parser = argparse.ArgumentParser()
241241

242242
subparser = parser.add_subparsers(dest="subcommand")
243243
subparser.add_parser(
244-
"fuzz",
245-
help="Fuzz the target regex implementation.",
246-
parents=[fuzz_parser()]
244+
"fuzz", help="Fuzz the target regex implementation.", parents=[fuzz_parser()]
247245
)
248246
subparser.add_parser(
249247
"reproduce",
250248
help="Reproduce the bug that found by the fuzzer.",
251-
parents=[reproduce_parser()]
249+
parents=[reproduce_parser()],
252250
)
253251

254252
args = parser.parse_args()
255253

256-
257254
logger.setLevel(args.logger_level)
258255

259256
if args.subcommand == "fuzz":
260257
do_fuzz(args)
261258
elif args.subcommand == "reproduce":
262259
do_reproduce(args)
263-
260+
264261

265262
if __name__ == "__main__":
266263
main()

src/zkregex_fuzzer/configs.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from zkregex_fuzzer.runner import CircomRunner, PythonReRunner, NoirRunner
22
from zkregex_fuzzer.grammar import REGEX_GRAMMAR
3-
from zkregex_fuzzer.regexgen import GrammarRegexGenerator, DatabaseRegexGenerator
4-
from zkregex_fuzzer.vinpgen import GrammarBasedGenerator, RstrGenerator, ExrexGenerator
5-
3+
from zkregex_fuzzer.regexgen import DatabaseRegexGenerator, GrammarRegexGenerator
4+
from zkregex_fuzzer.runner import CircomRunner, PythonReRunner
5+
from zkregex_fuzzer.vinpgen import ExrexGenerator, GrammarBasedGenerator, RstrGenerator
66

77
TARGETS = {
88
"circom": CircomRunner,
@@ -23,4 +23,4 @@
2323
GENERATORS = {
2424
"grammar": GrammarRegexGenerator,
2525
"database": DatabaseRegexGenerator,
26-
}
26+
}

0 commit comments

Comments
 (0)