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.
Recommended Fix
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.