Skip to main content
Sigvex

Feedback Loop

Detects circular signal dependencies in circuit constraints that prevent deterministic witness generation.

Feedback Loop

Overview

Remediation Guide: How to Fix Feedback Loops

The feedback loop detector identifies circular signal dependencies inside a template. A cycle exists when a signal participates, directly or transitively, in the computation of its own value. The simplest case is a <-- f(a); more elaborate cases involve a chain such as b <-- g(a); c <-- h(b); a <-- k(c).

Cyclic dependencies prevent the witness generator from picking a unique value for the involved signals. Either no assignment satisfies all constraints simultaneously, or many do. In either situation, the circuit no longer expresses a function from inputs to outputs.

Why This Is an Issue

Witness generation in Circom proceeds by topological order: each signal must be computable from signals already known. A cycle breaks this order. When the compiler encounters a cycle, behavior depends on the constraint type. For pure constraints (===), the cycle may admit multiple solutions, and the prover gets to choose. For witness assignments (<--), the cycle may produce undefined behavior depending on evaluation order — and any value the prover finds will satisfy the structural recurrence even if it disagrees with the intended semantics.

In both cases, the circuit’s claim no longer reduces to a single mathematical statement, which undermines the soundness guarantee of the proving system.

How to Resolve

Break the cycle by introducing a fresh signal for each “iteration” of the recurrence. The pattern is to materialize the dependency chain as a sequence of distinct signals, so that the witness generator can compute each in turn.

// Vulnerable: a depends on itself through a one-step cycle
template Accumulate() {
    signal input start;
    signal a;
    signal output result;

    a <-- start;
    a <-- a + 1;       // self-reference — cycle on `a`
    result <== a;
}
// Fixed: each step uses a distinct signal
template Accumulate() {
    signal input start;
    signal a0;
    signal a1;
    signal output result;

    a0 <== start;
    a1 <== a0 + 1;
    result <== a1;
}

For loops that legitimately need a recurrence, declare an array of intermediate signals indexed by iteration:

template Sum(N) {
    signal input xs[N];
    signal acc[N + 1];
    signal output total;

    acc[0] <== 0;
    for (var i = 0; i < N; i++) {
        acc[i + 1] <== acc[i] + xs[i];
    }
    total <== acc[N];
}

Sample Sigvex Output

{
  "detector": "feedback-loop",
  "severity": "high",
  "confidence": 0.95,
  "title": "Cyclic signal dependency in template `Accumulate`",
  "template": "Accumulate",
  "description": "Signal `a` depends on itself: a -> a. Witness generation cannot proceed deterministically when a signal participates in its own computation.",
  "recommendation": "Replace the self-referential assignment with a sequence of distinct signals (one per iteration) and constrain each step explicitly."
}

Detection Methodology

For each template, the detector builds a directed graph in which nodes are signals and edges go from each signal on the right-hand side of an assignment to the signal on the left-hand side. For pure constraints (===), edges are inserted in both directions, since the relationship is symmetric. The graph is then traversed with depth-first search using visited and on-stack marks; any back edge corresponds to a cycle. Each distinct cycle is reported once.

Limitations

  • Cycles that pass through component boundaries (where the loop closes through a sub-component’s input and output) are detected only if the parent template wires both ends.
  • Cycles induced through array indexing with non-literal indices are conservatively over-approximated, which may produce false positives when the indices are statically known to differ.
  • The detector reports the existence of a cycle, not the minimum cycle length — long cycles are reported with the same severity as one-step self-loops.

References