Signal Reuse
Detects signals assigned more than once, including silent unconstrained overwrites that bypass constraint generation.
Signal Reuse
Overview
Remediation Guide: How to Fix Signal Reuse
The signal reuse detector finds signals that receive more than one assignment within a template. Circom’s signal model treats each signal as immutable: every signal should be assigned exactly once. Multiple <== assignments to the same signal cause a compiler error, but multiple <-- assignments compile silently and overwrite the witness without producing any constraint.
The dangerous case is two or more <-- assignments to the same signal. The witness generator executes all of them in source order, the last assignment wins, and the R1CS contains nothing that ties the final value to any earlier computation.
Why This Is an Issue
A double <-- is the textbook recipe for an unsound circuit. The developer typically reads it as “I’m computing the value step by step,” but the semantics are “the prover may pick any value consistent with the final assignment.” Earlier assignments are dead code in the proof; only the final witness value persists, and that value is bound to nothing unless a === constraint somewhere references the signal.
Mixing <-- and <== on the same signal is also a code smell. Even though the <== adds a constraint, the prior <-- writes a value that is then overwritten, which usually means the developer intended a different computation than what the circuit encodes.
How to Resolve
Use a fresh signal for every distinct computation step. If the goal is to update a value across iterations, materialize the iterations as an array or as a sequence of named intermediates, exactly as for cycle removal.
// Vulnerable: double <-- silently overwrites
template Compute() {
signal input x;
signal y;
signal output z;
y <-- x * x;
y <-- x * x + 1; // silently overwrites — no constraint
z <== y;
}
// Fixed: each step uses a distinct constrained signal
template Compute() {
signal input x;
signal y0;
signal y1;
signal output z;
y0 <== x * x;
y1 <== y0 + 1;
z <== y1;
}
If the original double assignment was a typo, delete the redundant line. If it represented a conditional, encode the condition explicitly with a multiplexer pattern (out <== sel * a + (1 - sel) * b).
Sample Sigvex Output
{
"detector": "double-unconstrained-assignment",
"severity": "high",
"confidence": 0.95,
"title": "Signal `y` assigned multiple times in template `Compute`",
"template": "Compute",
"description": "Signal `y` receives two unconstrained assignments at lines 6 and 7. The second assignment silently overwrites the first, and no constraint binds either value.",
"recommendation": "Use distinct signal names for each computation step and constrain each via `<==`."
}
Detection Methodology
For each template, the detector groups assignments by their left-hand side, normalizing array accesses to their base signal name. Groups containing more than one assignment are inspected. The detector counts how many of the assignments are unconstrained (<--); two or more unconstrained assignments are reported as double-unconstrained-assignment at High severity. A mix of <-- and <== is reported at Medium severity to flag the structural inconsistency.
Limitations
- Array elements are normalized to their base name, which means multiple assignments to distinct elements of the same array (
a[0] <-- ...; a[1] <-- ...) may be reported as a false positive. - Assignments inside conditional blocks are treated as unconditional; a circuit that uses sub-components to gate writes will not be analyzed across the component boundary.
- The detector does not currently distinguish between assignments inside loop bodies that legitimately produce per-iteration array writes and unintended duplicates.
Related Detectors
- Signal Mutation — self-referential overwrites in loops
- Feedback Loop — circular signal dependencies
- Trivial Constraint — constraints providing no binding