Skip to main content
Sigvex

Feedback Loop Remediation

How to fix circular signal dependencies that create unsatisfiable or ambiguous constraint systems.

Feedback Loop Remediation

Overview

Related Detector: Feedback Loop

A feedback loop occurs when signal A’s constraint references signal B, and signal B’s constraint references signal A (directly or transitively). This circular dependency makes the constraint system ambiguous – it may have zero solutions, multiple solutions, or solutions that depend on solver ordering. The fix is to restructure the constraint graph to be acyclic.

Before (Vulnerable)

template Oscillator() {
    signal a;
    signal b;

    // Circular dependency: a depends on b, b depends on a
    a <== 1 - b;
    b <== 1 - a;
}

After (Fixed – Break the Cycle with an Input)

template Selector() {
    signal input choice;  // External input breaks the cycle
    signal a;
    signal b;

    a <== choice;
    b <== 1 - a;  // Acyclic: b depends on a, a depends on choice
}

After (Fixed – Use an Intermediate Witness)

When the relationship is genuinely mutual, provide one value as a witness and constrain the other:

template InverseRelation() {
    signal input x;
    signal a;
    signal b;

    a <== x;
    b <-- 1 - a;       // Witness computation
    a + b === 1;        // Constraint verifies the relationship
}

Alternative Mitigations

1. Topological Reordering

Analyze the dependency graph and reorder signal definitions so that each signal only references previously defined signals:

// Identify the "root" signal that should be determined by inputs
// and derive all other signals from it in a directed chain.
signal step1;
step1 <== input_value;
signal step2;
step2 <== step1 * 2;
signal step3;
step3 <== step2 + step1;  // Acyclic chain

2. Fixed-Point Extraction

If the cycle represents a legitimate fixed-point equation (e.g., a = f(a)), compute the fixed point in witness generation and constrain it:

signal a;
a <-- computeFixedPoint();  // Solve a = f(a) off-chain
a === f(a);                  // Verify the fixed point holds

Common Mistakes

Hiding cycles behind components: If template A instantiates template B and B instantiates A, the circular dependency is harder to spot but equally dangerous. Audit the full instantiation graph.

Assuming the compiler detects all cycles: The Circom compiler catches some circular dependencies but may miss cycles that span multiple templates or involve indirect dependencies through arrays.

Breaking cycles with unconstrained assignments: Replacing one <== with <-- breaks the cycle syntactically but leaves the signal unconstrained. Always add an explicit === constraint when using <-- to break a cycle.

References