Signal Reuse Remediation
How to fix signals assigned multiple times by using distinct signals or arrays for each computed value.
Signal Reuse Remediation
Overview
Related Detector: Signal Reuse
Signal reuse occurs when a signal is assigned more than once. Circom enforces single assignment for constrained signals (<==), but unconstrained assignments (<--) in loops or conditional branches can create multiple assignments to the same signal. The last write wins during witness generation, and the prover may exploit this. The fix is to use distinct signals for each value or arrays for loop-based computations.
Recommended Fix
Before (Vulnerable)
template UnsafeAccumulator(n) {
signal input values[n];
signal intermediate;
for (var i = 0; i < n; i++) {
intermediate <-- compute(values[i]); // Reassigned each iteration
}
// intermediate holds only the last iteration's value
// Constraints from earlier iterations reference a stale signal
}
After (Fixed — Array of Signals)
template SafeAccumulator(n) {
signal input values[n];
signal intermediate[n];
for (var i = 0; i < n; i++) {
intermediate[i] <== compute(values[i]); // Unique signal per iteration
}
// Each intermediate[i] is independently constrained
}
After (Fixed — Distinct Named Signals)
For non-loop reuse (e.g., conditional branches):
// Before: same signal assigned in two branches
signal result;
result <-- condition ? valueA : valueB; // One assignment, but see nondeterministic-control
// After: use separate signals and a constrained mux
signal branchA;
branchA <== valueA;
signal branchB;
branchB <== valueB;
component mux = Mux1();
mux.c[0] <== branchB;
mux.c[1] <== branchA;
mux.s <== constrainedCondition;
signal result;
result <== mux.out;
Alternative Mitigations
1. Flatten Loops with Indexed Signals
When the computation involves iterative refinement, use an indexed signal array that captures each step:
signal state[iterations + 1];
state[0] <== initialValue;
for (var i = 0; i < iterations; i++) {
state[i + 1] <== transform(state[i]);
}
// Final result is state[iterations]
2. Component Encapsulation
Move the iterative logic into a component whose interface enforces single assignment:
template IterativeCompute(n) {
signal input in;
signal output out;
// Internal signals are single-assignment by construction
signal steps[n + 1];
steps[0] <== in;
for (var i = 0; i < n; i++) {
steps[i + 1] <== steps[i] * steps[i];
}
out <== steps[n];
}
Common Mistakes
Assuming the compiler catches all double assignments: The Circom compiler catches double <== assignments but may not flag double <-- assignments, which produce last-write-wins semantics without any error.
Using var instead of signal: Variables (var) in Circom are compile-time and can be reassigned freely. Signals cannot. If you need a mutable value for loop logic, use var; but if the value must appear in constraints, it must be a signal array.
Confusing signal scope with reassignment: Declaring a signal inside a loop body does not create a new signal per iteration — Circom does not have block-scoped signals. The signal is created once and reassigned on each iteration.