Unused Signals
Detects declared signals that never appear in any constraint or assignment, indicating dead code or incomplete circuit logic.
Unused Signals
Overview
Remediation Guide: How to Fix Unused Signals
The unused signals detector reports inputs, intermediates, and outputs that are declared in a template but never referenced by any assignment or constraint. The signal exists in the circuit’s public interface (or in its memory layout) but has no effect on any other value.
For inputs in particular, this is a meaningful security finding: an unused input cannot influence the proof, which means the verifier’s expectation that the input “matters” is wrong.
Why This Is an Issue
When an input is unused, the prover is free to substitute any value for it. A verifier that supplies, say, a nonce or a nullifier as a public input is implicitly trusting the circuit to bind that input to the rest of the computation. If the circuit ignores the input entirely, the prover can replay an old proof under a fresh nonce — the proof remains valid because the nonce was never folded into the witness.
Unused outputs are usually a code smell rather than a vulnerability: they consume R1CS rows for no reason. Unused intermediates often signal that a refactor left dead computation behind, which can mask the real intent of the circuit during review.
How to Resolve
Decide whether the signal should influence the circuit or be removed. If it is meant to be part of the binding, add a constraint that links it to a downstream value.
// Vulnerable: nullifier is declared but never constrained
template Spend() {
signal input note;
signal input nullifier;
signal output commitment;
commitment <== Hash(note);
}
// Fixed: nullifier is bound by being part of the hash preimage
template Spend() {
signal input note;
signal input nullifier;
signal output commitment;
signal output nullifier_hash;
commitment <== Hash(note);
nullifier_hash <== Hash(nullifier);
}
If the signal genuinely is not needed, delete its declaration. Leaving an unused signal in place encourages downstream code to assume it carries meaning.
Sample Sigvex Output
{
"detector": "unused-public-input",
"severity": "medium",
"confidence": 0.95,
"title": "Unused input signal: nullifier",
"template": "Spend",
"line": 4,
"signal": "nullifier",
"description": "Input signal `nullifier` in template `Spend` is not referenced in any constraint or assignment. This input does not influence the circuit output, which may indicate a logic error or allow proof replay across different input values."
}
Detection Methodology
For each template, the detector collects every signal name that appears either on the left-hand side of an assignment or anywhere within an assignment’s right-hand expression. It then iterates the template’s declared input, intermediate, and output signals and reports any whose name does not appear in the collected set.
Inputs are reported at Medium severity with the unused-public-input identifier; outputs and intermediates are reported at Low severity because their security impact is limited to dead code.
Limitations
- A signal referenced only inside a string literal or comment is treated as unused, which is correct, but the detector cannot distinguish a deliberately disabled input from an oversight.
- Signals consumed only by sub-components through indirect wiring may be missed if the parent template never names them on either side of an assignment.
- The detector does not consider whether an unused output is consumed by an outer template, so a leaf template with an output that the parent reads will not be flagged.
Related Detectors
- Unconstrained Public Input — inputs that are referenced but never constrained
- Unconstrained Output — outputs lacking any binding
- Signal Mutation — signals overwritten without constraint propagation