Unused and Dead Signals Remediation
How to fix unused public inputs, unassigned signals, and dead signals in ZK circuits.
Unused and Dead Signals Remediation
Overview
Related Detector: Unused and Dead Signals
Three patterns fall under this detector: unused public inputs (declared but not appearing in any constraint), unassigned signals (declared but never given a value), and dead signals (assigned but never referenced in constraints). Each weakens the circuit in different ways. The fix depends on the specific pattern.
Recommended Fix
Unused Public Input (Before / After)
// Before: Public input not constrained
template UnsafeVerifier() {
signal input timestamp; // Public input, never constrained
signal input data;
signal output hash;
component h = Poseidon(1);
h.inputs[0] <== data;
hash <== h.out;
// timestamp has no effect on proof validity
}
// After: Include public input in constraints
template SafeVerifier() {
signal input timestamp;
signal input data;
signal output hash;
component h = Poseidon(2);
h.inputs[0] <== data;
h.inputs[1] <== timestamp; // Now part of the hash computation
hash <== h.out;
}
Unassigned Signal (Before / After)
// Before: Signal declared but never assigned
template Broken() {
signal input x;
signal intermediate; // Never assigned -- defaults to 0 or undefined
signal output y;
y <== x * 2;
}
// After: Remove or assign the signal
template Fixed() {
signal input x;
signal output y;
y <== x * 2; // intermediate removed -- it was dead code
}
Dead Signal (Before / After)
// Before: Signal assigned but never used in constraints
template Wasteful() {
signal input a, b;
signal output sum;
signal product;
sum <== a + b;
product <== a * b; // Computed but never used
}
// After: Remove dead signal or use it
template Clean() {
signal input a, b;
signal output sum;
sum <== a + b; // product removed
}
Alternative Mitigations
1. Connect Unused Inputs to a Commitment
If a public input must exist in the interface but is not directly used in the main computation, include it in a commitment hash:
// Hash all public inputs together to bind them to the proof
component inputHash = Poseidon(numInputs);
inputHash.inputs[0] <== input1;
inputHash.inputs[1] <== input2;
// ...
inputCommitment === inputHash.out;
2. Document Intentionally Unused Signals
If a signal is reserved for future use, add a trivial constraint and a comment:
// Reserved for protocol v2 -- constrain to zero for now
signal input reserved;
reserved === 0;
Common Mistakes
Removing public inputs without updating the verifier: If you remove a public input from the circuit, the verifier contract and all proof-generation code must be updated to match the new interface.
Assuming dead signals are harmless: Dead signals waste constraint space, but more importantly, they often indicate incomplete circuit logic. A signal that was intended to be constrained but is not represents a missing security check.
Confusing unused component outputs with dead signals: A component like Num2Bits constrains its input through internal bit decomposition. Even if you never read the bit outputs, the constraint on the input is still active. This is different from a dead signal, which has no constraint effect.