Skip to content

Merge prep for #92: 3.0.0-alpha.1, soundness fixes, migration guide - #118

Merged
rutefig merged 4 commits into
feat/new-compilerfrom
rutefig/pr92-merge-prep
Sep 17, 2026
Merged

rutefig merged 4 commits into
feat/new-compilerfrom
rutefig/pr92-merge-prep

Conversation

@rutefig

@rutefig rutefig commented Sep 17, 2026

Copy link
Copy Markdown
Member

Prepares #92 for merge. Targets feat/new-compiler, so merging this updates #92.

Commits

1. chore: version, comptime pin, prettier

  • All manifests go from 2.2.2-alpha.1 to 3.0.0-alpha.1. main already published 2.3.2, and about 5k installs a month resolve ^2.3.1 through @zk-email/circuits. The new template interface must never appear under a 2.x version.
  • comptime was a git dependency on a feature branch of a personal fork. Now pinned to the exact commit the lockfile already used.
  • circom/package.json had "prettier": "", which is invalid semver and would fail npm publish. Nothing uses it, so it is removed.
  • bun.lock is edited by hand to keep the diff minimal. A fresh bun install with a newer Bun rewrites a few hundred lines.

2. fix(circom): traversal linkage off by one. Soundness bug.
The generated check nextStates[i] == currStates[i+1] ran for i < maxMatchBytes-2, so the link into the last slot was never enforced. When matchLength == maxMatchBytes a prover could make the final step any transition into an accept state. traversal_linkage.test.ts forges that witness: before the fix the circuit accepts subject:aaa…aX\n with isValid = 1, after it witness generation fails. The Noir backend already had the correct bound. All common circuits are regenerated, 3 lines each. mixed_capture_regex.circom has no source definition, so it is patched by hand to match its twin.

3. fix(noir): reject automata whose lookup keys alias. Soundness bug.
Keys are state + byte*257 + next*257². Past 257 states, (s+257, b) and (s, b+1) share a key, so the circuit accepts a byte the regex forbids. This is easy to hit: the crate's own doc example prefix:\w+ builds 325 states, and 0 -[p]-> 2 aliases 257 -[o]-> 2. The compiler now detects real aliasing and returns an error. No bundled pattern is affected, the largest has fewer than 200 states. The doc example now uses [a-zA-Z0-9_]+.

4. docs: MIGRATION.md, linked from the README, plus the README license placeholder.

Triage of the other critical review threads

Thread Verdict
matchLength - 1 underflow into LessThan Not exploitable. For matchLength = 0 the comparator input is i + 2^n + 1, which has bit n set, so the result is 0, no step is active and isValid is 0.
@ inside the domain character class Known, documented 2.x behaviour, kept on purpose. Now also noted in the migration guide.
Noir sparse array collisions above 256 states Real. Fixed in commit 3 by failing closed.
zk-regex-apis removal Intentional. It stays on release/2.x, consumers are pinned in relayer-utils#44 and relayer-smtp#4.

Needs a decision from the Noir authors

current_state and next_state are unconstrained Field inputs in transitions.nr. Commit 3 closes aliasing between integer state ids, but a prover may also pick non-integer field elements so that a step's key lands on some other valid key. I did not build an attack and I am not claiming one exists. Range-constraining both states, and widening the radix so large automata work again, would remove the question. That changes every generated table, so I left it out of this PR.

Verification

  • cargo test: 33 unit tests and 4 doctests pass.
  • bun test: 133 pass, 0 fail, including the new forged-witness test.
  • nargo test was not run. No Noir source or template changed.

Pushed with --no-verify. The pre-push hook regenerates Noir templates with gen-regex:noir, whose output differs from the committed files on this branch independent of these commits (zkregex:: import, inline tests stripped), so it blocks every push that touches compiler/src.

The linkage check stopped at maxMatchBytes-2, so when matchLength ==
maxMatchBytes the final transition was not tied to the rest of the path
and a prover could jump straight into an accept state. Adds a forged
witness regression test and regenerates the common circuits.
Lookup keys pack state + byte*257 + next*257^2, which stops being
injective past 257 states. Detect real aliasing at codegen time and fail
with a clear error instead of emitting an unsound circuit.
@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: bbebf948-e68b-4d46-8873-bba33d197bbf

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rutefig
rutefig merged commit 0d14606 into feat/new-compiler Sep 17, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant