Unconstrained Output Remediation
How to fix unconstrained output signals by adding constraining assignments or explicit constraints.
Unconstrained Output Remediation
Overview
Related Detector: Unconstrained Output
An unconstrained output signal has no <== assignment and no === constraint binding it. The prover can set it to any value, making the circuit’s result meaningless. The fix is straightforward: assign the output via <== or add a === constraint that ties the output to the circuit’s computation.
Recommended Fix
Before (Vulnerable — Output Assigned via <--)
template Broken() {
signal input a;
signal output result;
// Unconstrained assignment -- no constraint generated
result <-- a * a;
}
After (Fixed — Constraining Assignment)
template Fixed() {
signal input a;
signal output result;
// <== generates the constraint: result == a * a
result <== a * a;
}
Before (Vulnerable — Output Never Assigned)
template Incomplete() {
signal input preimage;
signal output hash;
// hash is declared but never assigned or constrained
// The prover can claim any hash value
}
After (Fixed — Output Computed and Constrained)
template Complete() {
signal input preimage;
signal output hash;
component h = Poseidon(1);
h.inputs[0] <== preimage;
hash <== h.out; // output is constrained via <==
}
Alternative Mitigations
1. Separate Computation and Constraint
When the output computation requires <-- (non-quadratic), add a === constraint:
template SafeInverse() {
signal input x;
signal output inv;
inv <-- 1 / x; // witness computation (division)
inv * x === 1; // constraint: inv * x == 1
}
2. Indirect Constraint via RHS
The output is considered constrained if it appears on the RHS of any === or <==:
template IndirectConstraint() {
signal input a;
signal output b;
signal check;
b <-- a * a;
check <== b + 1; // b appears in RHS of <==, so b is constrained
}
This works because the R1CS equation for check <== b + 1 includes b as a variable. However, this pattern is less clear than directly constraining the output and should be used only when necessary.
Common Mistakes
Assuming <-- generates a constraint: the <-- operator performs witness assignment only. It tells the prover how to compute the value but adds nothing to the R1CS. The verifier never sees it.
Leaving outputs for “later”: a template with declared but unassigned outputs compiles without error in Circom. The constraints for other signals are still generated, but the output is a free variable. This is easy to miss in templates under development.
Confusing template outputs with main circuit outputs: even if a template is only used internally (never as the top-level circuit), its outputs must be constrained. When a parent template reads component.out, the value must be determined by the sub-circuit’s constraints.
Wiring output to component via <--: if you write hash <-- hasher.out instead of hash <== hasher.out, the output receives the correct value during witness generation but has no constraint linking it to the component’s output. Always use <== for component output wiring.