Skip to main content
Sigvex

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.

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.

References