Unchecked Component Remediation
How to fix component instances with disconnected inputs or outputs that do not contribute to circuit security.
Unchecked Component Remediation
Overview
Related Detector: Unchecked Component
An unchecked component is a template instance whose inputs or outputs are not properly wired to the rest of the circuit. This includes outputs never read, outputs wired via <-- instead of <==, or inputs left unconnected. The component’s internal constraints exist in the R1CS but are disconnected from the proof’s security properties. The fix is to ensure all component I/O uses constrained wiring.
Recommended Fix
Before (Vulnerable — Output Wired via <--)
template UnsafeVerifier() {
signal input a, b;
signal output commitment;
component hash = Poseidon(2);
hash.inputs[0] <== a;
hash.inputs[1] <== b;
commitment <-- hash.out; // BROKEN: unconstrained wiring
}
After (Fixed — Constrained Output Wiring)
template SafeVerifier() {
signal input a, b;
signal output commitment;
component hash = Poseidon(2);
hash.inputs[0] <== a;
hash.inputs[1] <== b;
commitment <== hash.out; // CORRECT: constrained wiring
}
Before (Vulnerable — Output Never Read)
template UnsafeTransfer() {
signal input amount;
// Range check instantiated but output ignored
component rangeCheck = Num2Bits(64);
rangeCheck.in <== amount;
// rangeCheck.out is never read -- but this is actually OK for Num2Bits
// because the constraint is on the input decomposition.
// However, for components where the output IS the security property:
component lt = LessThan(64);
lt.in[0] <== amount;
lt.in[1] <== 1000;
// lt.out is never checked! The comparison happens but is not enforced.
}
After (Fixed — Output Consumed in Constraint)
template SafeTransfer() {
signal input amount;
component rangeCheck = Num2Bits(64);
rangeCheck.in <== amount;
component lt = LessThan(64);
lt.in[0] <== amount;
lt.in[1] <== 1000;
lt.out === 1; // CORRECT: comparison result is enforced
}
Alternative Mitigations
1. Inline the Component’s Logic
If you only need the constraint and not the output signal, some components (like Num2Bits) provide the constraint through their internal structure. Verify that the component’s security property comes from its internal constraints, not from consuming its output.
2. Assert Output in Wrapper Template
Create a wrapper that enforces output consumption:
template AssertLessThan(n) {
signal input in[2];
component lt = LessThan(n);
lt.in[0] <== in[0];
lt.in[1] <== in[1];
lt.out === 1; // Always enforced
}
Common Mistakes
Assuming instantiation alone provides security: Creating a component instance adds its constraints to the R1CS, but those constraints only protect signals that are connected to the component via <==. Disconnected components are dead code.
Checking the wrong output: Some components have multiple outputs. Constraining one output but ignoring another may leave part of the security property unenforced.
Confusing Num2Bits with LessThan: Num2Bits(n) constrains its input to [0, 2^n) through its internal bit decomposition — the output bits are a byproduct. LessThan(n) produces a boolean output that must be explicitly checked. Different components have different constraint models.
References
- Circom Documentation: Templates and Components
- circomlib GitHub repository — reference component interfaces