Skip to main content
Sigvex

Unassigned Output

Detects output signals that are declared but never assigned, leaving the value undefined and open to proof forgery.

Unassigned Output

Overview

Remediation Guide: How to Fix Unassigned Outputs

The unassigned output detector flags output signals that are declared in a template but never appear on the left-hand side of any assignment — neither <== nor <--. Such an output has no defined value at all: the witness generator assigns it nothing, and no constraint restricts what the prover may put there.

This is the strictest case in a family of output problems. The unconstrained output detector covers outputs assigned via <-- without a constraint; this detector covers outputs with no assignment whatsoever, which most often indicates an incomplete template — a computation that was sketched out and never wired to its result.

Why This Is an Issue

Whatever the circuit was supposed to compute, an unassigned output computes nothing. The prover supplies the value directly in the witness, and the verifier — seeing a valid proof — accepts it as the circuit’s result. If the output feeds a parent template, the corruption propagates upward; if it is a public output checked on chain, the prover controls the claimed result outright.

Because the signal is missing from every constraint, this is not a subtle soundness gap that requires a crafted witness to exploit. Any witness with any value in that slot verifies.

How to Resolve

Wire the output to the computation that defines it, using a constraining assignment.

// Before: output declared, never assigned
template Sum(n) {
    signal input values[n];
    signal output total;
    // ... total never assigned
}

// After: output bound to the computation
template Sum(n) {
    signal input values[n];
    signal output total;

    signal partial[n];
    partial[0] <== values[0];
    for (var i = 1; i < n; i++) {
        partial[i] <== partial[i - 1] + values[i];
    }
    total <== partial[n - 1];
}

If the output is genuinely unneeded, delete the declaration rather than leaving it dangling.

Examples

Vulnerable Code

template CheckAndReport() {
    signal input balance;
    signal input threshold;
    signal output ok;

    // Comparator instantiated, but its result never reaches `ok`
    component cmp = GreaterEqThan(64);
    cmp.in[0] <== balance;
    cmp.in[1] <== threshold;
    // MISSING: ok <== cmp.out;
}

Fixed Code

template CheckAndReport() {
    signal input balance;
    signal input threshold;
    signal output ok;

    component cmp = GreaterEqThan(64);
    cmp.in[0] <== balance;
    cmp.in[1] <== threshold;
    ok <== cmp.out;
}

Sample Sigvex Output

HIGH  unassigned-output
  Output signal `ok` in template `CheckAndReport` is declared but never
  assigned. The output value is undefined, which may allow proof forgery.

  Template: CheckAndReport
  Signal:   ok
  Confidence: 0.90

Detection Methodology

For each template, the detector collects the set of signals appearing on the left-hand side of any assignment (constrained or unconstrained). Each declared output signal is checked against this set; outputs never assigned are reported at 0.90 confidence. The check is name-based with array-element awareness, so out[0] <== x marks the array output out as assigned.

Limitations

  • Outputs assigned only through mechanisms the parser does not model (for example, unusual macro-generated code) may be flagged despite being wired.
  • The detector does not judge whether the assignment, once present, is constraining — an output assigned via <-- passes this check and is instead reported by the unconstrained output detector.
  • Analysis is per template; an output that a parent deliberately ignores is still (correctly) flagged in the template that declares it.

References