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