Skip to main content
Sigvex

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.

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.

References