Skip to main content
Sigvex

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.

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.

References