Skip to main content
Sigvex

Unassigned Output Remediation

How to fix output signals that are declared but never assigned by wiring them to the circuit's computation with constraining assignments.

Unassigned Output Remediation

Overview

Related Detector: Unassigned Output

An output signal with no assignment has no defined value and no constraint: the prover writes whatever it wants into the witness slot, and the proof still verifies. This almost always means a template is incomplete — the computation exists but was never connected to the declared output. The fix is to wire the output with <==, or to delete the declaration if the output is no longer needed.

Before (Vulnerable)

template MerkleInclusion(depth) {
    signal input leaf;
    signal input pathElements[depth];
    signal input pathIndices[depth];
    signal output root;

    component hashers[depth];
    // ... hash chain built correctly ...

    // MISSING: root is never assigned — the prover chooses it,
    // so "inclusion" can be proven against any claimed root
}

After (Fixed)

template MerkleInclusion(depth) {
    signal input leaf;
    signal input pathElements[depth];
    signal input pathIndices[depth];
    signal output root;

    component hashers[depth];
    // ... hash chain built correctly ...

    // Bind the output to the last hash in the chain
    root <== hashers[depth - 1].out;
}

The <== operator both assigns the witness value and emits the R1CS constraint root == hashers[depth-1].out, so the verifier now rejects any proof where the claimed root differs from the computed one.

Alternative Mitigations

  • Constrain in the parent. If the child template cannot be modified (third-party library), the parent can pin the output: child.root === expectedRoot;. This works but hides the fix away from the defect — prefer fixing the template.
  • Remove dead outputs. If the output was superseded during a refactor, delete the declaration. A dangling output is not harmless documentation; it is a free variable in the constraint system.
  • Assert coverage in tests. Compile with inspection enabled and check the constraint count: a template whose output count exceeds its output-referencing constraints has an unbound output even if witness generation appears to work.

Common Mistakes

Mistake: Assigning with <--

root <-- hashers[depth - 1].out;  // WRONG: witness-only, no constraint

This silences witness-generation errors but changes nothing about soundness — the prover can still overwrite the value. Use <==, or pair <-- with an explicit ===.

Mistake: Assigning Only Part of an Array Output

signal output digest[2];
digest[0] <== hasher.out[0];
// WRONG: digest[1] never assigned — half the output is prover-controlled

Every element of an array output needs its own binding.

References