Skip to content

Solve Alpha Regex Issue. - #120

Open
Flecart wants to merge 2 commits into
zkemail:mainfrom
Flecart:fix/enforce-start-anchor
Open

Flecart wants to merge 2 commits into
zkemail:mainfrom
Flecart:fix/enforce-start-anchor

Conversation

@Flecart

@Flecart Flecart commented Oct 12, 2026

Copy link
Copy Markdown

Fixes #119.

I also found that without the epsilon transitions, the states can be around 300, so sadly we need a u16 for that... I needed to make space for the novel packing format. Happy for suggestions to better packing.

The code was vibed by claude 5.5, but I checked it didn't do anything stupid. For example it tried to replace all the Field to u16 and I told it not to.

`^` (Look::Start) was compiled as an unconditional epsilon transition.
Because matchStart is a prover input, a circuit for a `^`-anchored
pattern accepted a match at any haystack offset. For example,
`(?:\r\n|^)from:...` accepted `from:` inside `subject:from:x\r\n`.

Start anchors are now a separate kind of epsilon edge. Ordinary epsilon
closures never cross them, since a `^` reached after consuming a byte
can never hold. Each start state whose closure reaches a `^` gets an
anchored twin that carries the transitions of the anchor-crossing
closure. Nothing transitions into the twin, and both backends require
matchStart == 0 when the path begins there. NFAGraph records these
states in `anchored_start_states`, and witness generation only uses
them for matches at offset 0.

Other look-around assertions are still compiled as unconditional
epsilons. Until they are supported, they are rejected with E1002
instead of being silently dropped.

Also assert `isValidStartState === 1` in Circom circuits with a single
start state (e.g. mixed_capture_length). Before, it was computed but
never constrained.

Tests: regressions for the reported witness, and a differential test
that checks the circuit's path constraints against regex-automata for
every (matchStart, matchLength) window over enumerated haystacks.
Regenerated the bundled Circom and Noir templates. The Noir tests are
regenerated only for anchored patterns; elsewhere the circuit bodies
are unchanged.
Noir transition lookup keys were packed as `state + byte * 257 +
next * 257^2`. That only works while state ids stay below 257, and the
compiler had to scan for aliasing keys and reject larger automata. A
single Unicode `\w` already needs over 300 states. The prover-supplied
states were also never range-checked in-circuit, so a witness could
claim a state beyond the radix (e.g. `s + 257` reading `b - 1`) whose
key collides with a real transition.

Keys are now `state + byte * 2^16 + next * 2^24`. `check_transition`
asserts both states fit in 16 bits, which makes the packing injective
for any witness. The compiler rejects automata with more than 2^16
states, replacing the alias scan. States stay `Field`s, so the
generated `regex_match` signature is unchanged.

The sparse array now range-checks keys to 63 bits instead of 32, since
keys use up to 40 bits. Tables are sized from the largest real key
instead of an estimate that assumed at most 200 states.

Tests: Noir regressions for the `^` fix (the reported witness and its
anchored variant, plus an honest control), a state-aliasing test that
fails without the range check, and Rust tests for key injectivity and
>257-state automata. Noir templates regenerated (only the lookup tables
change).
Copilot AI balanced review requested due to automatic review settings October 12, 2026 21:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Oct 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 107 files, which is 7 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1d646203-d7ea-43ee-8c3b-806d99c84a24

📥 Commits

Reviewing files that changed from the base of the PR and between 89a4adb and af48267.


📒 Files selected for processing (107)
  • circom/circuits/common/body_hash_graph.json
  • circom/circuits/common/body_hash_regex.circom
  • circom/circuits/common/char_class_basic_graph.json
  • circom/circuits/common/complex_alternation_groups_graph.json
  • circom/circuits/common/email_addr_graph.json
  • circom/circuits/common/email_addr_regex.circom
  • circom/circuits/common/email_basic_graph.json
  • circom/circuits/common/email_domain_graph.json
  • circom/circuits/common/fixed_range_quantifier_graph.json
  • circom/circuits/common/from_all_graph.json
  • circom/circuits/common/from_all_regex.circom
  • circom/circuits/common/lazy_quantifier_graph.json
  • circom/circuits/common/literal_match_graph.json
  • circom/circuits/common/message_id_graph.json
  • circom/circuits/common/message_id_regex.circom
  • circom/circuits/common/mixed_capture_length_graph.json
  • circom/circuits/common/mixed_capture_length_regex.circom
  • circom/circuits/common/nested_noncapturing_graph.json
  • circom/circuits/common/nested_quantified_groups_graph.json
  • circom/circuits/common/quantified_alphanumeric_graph.json
  • circom/circuits/common/reversed_bracket_graph.json
  • circom/circuits/common/simple_alternation_graph.json
  • circom/circuits/common/simple_graph.json
  • circom/circuits/common/subject_all_graph.json
  • circom/circuits/common/subject_all_regex.circom
  • circom/circuits/common/to_all_graph.json
  • circom/circuits/common/to_all_regex.circom
  • circom/circuits/common/unbounded_range_graph.json
  • compiler/src/backend/circom.rs
  • compiler/src/backend/noir.rs
  • compiler/src/ir/graph.rs
  • compiler/src/ir/intermediate.rs
  • compiler/src/ir/nfa.rs
  • compiler/src/passes/builder.rs
  • compiler/src/passes/error.rs
  • noir/common/sample_haystacks/circuit_inputs/body_hash_pass_0.json
  • noir/common/sample_haystacks/circuit_inputs/email_addr_pass_1.json
  • noir/common/sample_haystacks/circuit_inputs/email_addr_pass_2.json
  • noir/common/sample_haystacks/circuit_inputs/from_all_pass_0.json
  • noir/common/sample_haystacks/circuit_inputs/from_all_pass_1.json
  • noir/common/sample_haystacks/circuit_inputs/from_all_pass_2.json
  • noir/common/sample_haystacks/circuit_inputs/from_all_pass_3.json
  • noir/common/sample_haystacks/circuit_inputs/from_all_pass_4.json
  • noir/common/sample_haystacks/circuit_inputs/from_all_pass_5.json
  • noir/common/sample_haystacks/circuit_inputs/from_all_pass_6.json
  • noir/common/sample_haystacks/circuit_inputs/from_all_pass_8.json
  • noir/common/sample_haystacks/circuit_inputs/subject_all_pass_0.json
  • noir/common/sample_haystacks/circuit_inputs/subject_all_pass_3.json
  • noir/common/sample_haystacks/circuit_inputs/timestamp_pass_0.json
  • noir/common/sample_haystacks/circuit_inputs/timestamp_pass_1.json
  • noir/common/sample_haystacks/circuit_inputs/timestamp_pass_2.json
  • noir/common/sample_haystacks/circuit_inputs/timestamp_pass_3.json
  • noir/common/sample_haystacks/circuit_inputs/timestamp_pass_4.json
  • noir/common/sample_haystacks/circuit_inputs/to_all_pass_0.json
  • noir/common/sample_haystacks/circuit_inputs/to_all_pass_1.json
  • noir/common/sample_haystacks/circuit_inputs/to_all_pass_3.json
  • noir/common/sample_haystacks/circuit_inputs/to_all_pass_4.json
  • noir/common/sample_haystacks/circuit_inputs/to_all_pass_6.json
  • noir/common/sample_haystacks/circuit_inputs/to_all_pass_7.json
  • noir/src/templates/circuits/body_hash_regex.nr
  • noir/src/templates/circuits/char_class_basic_regex.nr
  • noir/src/templates/circuits/complex_alternation_groups_regex.nr
  • noir/src/templates/circuits/email_addr_regex.nr
  • noir/src/templates/circuits/email_basic_regex.nr
  • noir/src/templates/circuits/email_domain_regex.nr
  • noir/src/templates/circuits/fixed_range_quantifier_regex.nr
  • noir/src/templates/circuits/from_all_regex.nr
  • noir/src/templates/circuits/lazy_quantifier_regex.nr
  • noir/src/templates/circuits/literal_match_regex.nr
  • noir/src/templates/circuits/message_id_regex.nr
  • noir/src/templates/circuits/mixed_capture_length_regex.nr
  • noir/src/templates/circuits/nested_noncapturing_regex.nr
  • noir/src/templates/circuits/nested_quantified_groups_regex.nr
  • noir/src/templates/circuits/quantified_alphanumeric_regex.nr
  • noir/src/templates/circuits/simple_alternation_regex.nr
  • noir/src/templates/circuits/simple_regex.nr
  • noir/src/templates/circuits/subject_all_regex.nr
  • noir/src/templates/circuits/succinct_regex.nr
  • noir/src/templates/circuits/timestamp_regex.nr
  • noir/src/templates/circuits/to_all_regex.nr
  • noir/src/templates/circuits/unbounded_range_regex.nr
  • noir/src/templates/graphs/body_hash_graph.json
  • noir/src/templates/graphs/char_class_basic_graph.json
  • noir/src/templates/graphs/complex_alternation_groups_graph.json
  • noir/src/templates/graphs/email_addr_graph.json
  • noir/src/templates/graphs/email_basic_graph.json
  • noir/src/templates/graphs/email_domain_graph.json
  • noir/src/templates/graphs/fixed_range_quantifier_graph.json
  • noir/src/templates/graphs/from_all_graph.json
  • noir/src/templates/graphs/lazy_quantifier_graph.json
  • noir/src/templates/graphs/literal_match_graph.json
  • noir/src/templates/graphs/message_id_graph.json
  • noir/src/templates/graphs/mixed_capture_length_graph.json
  • noir/src/templates/graphs/nested_noncapturing_graph.json
  • noir/src/templates/graphs/nested_quantified_groups_graph.json
  • noir/src/templates/graphs/quantified_alphanumeric_graph.json
  • noir/src/templates/graphs/simple_alternation_graph.json
  • noir/src/templates/graphs/simple_graph.json
  • noir/src/templates/graphs/subject_all_graph.json
  • noir/src/templates/graphs/succinct_graph.json
  • noir/src/templates/graphs/timestamp_graph.json
  • noir/src/templates/graphs/to_all_graph.json
  • noir/src/templates/graphs/unbounded_range_graph.json
  • noir/src/templates/mod.nr
  • noir/src/templates/start_anchor_tests.nr
  • noir/src/utils/sparse_array.nr
  • noir/src/utils/transitions.nr

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

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

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.

Small bug In starting anchor in alpha.

2 participants