Unsafe Comparison Remediation
How to fix comparison operators in unconstrained assignments by using constrained circomlib comparator components.
Unsafe Comparison Remediation
Overview
Related Detector: Unsafe Comparison
Comparison operators (<, >, <=, >=, ==, !=) in unconstrained assignments (<--) produce prover-controlled boolean values. Finite field arithmetic has no natural ordering, so comparisons require bit decomposition to implement correctly. The fix is to replace unconstrained comparisons with circomlib comparator components (LessThan, IsEqual, GreaterThan) that generate proper constraints.
Recommended Fix
Before (Vulnerable)
template UnsafeMax() {
signal input a;
signal input b;
signal output out;
signal isGreater;
isGreater <-- a > b ? 1 : 0; // Prover controls this value
out <-- isGreater == 1 ? a : b;
}
After (Fixed — circomlib Comparator)
template SafeMax() {
signal input a;
signal input b;
signal output out;
// Range check inputs (required for LessThan correctness)
component aCheck = Num2Bits(252);
aCheck.in <== a;
component bCheck = Num2Bits(252);
bCheck.in <== b;
// Constrained comparison
component lt = LessThan(252);
lt.in[0] <== b;
lt.in[1] <== a;
// lt.out === 1 when a > b
// Constrained selection
signal diff;
diff <== a - b;
out <== b + lt.out * diff;
}
After (Fixed — Equality Check)
// Before: unconstrained equality
signal isEqual;
isEqual <-- a == b ? 1 : 0;
// After: constrained equality via IsEqual
component eq = IsEqual();
eq.in[0] <== a;
eq.in[1] <== b;
signal isEqual;
isEqual <== eq.out; // Properly constrained
Alternative Mitigations
1. Constrained IsZero for Inequality
For != checks, use the IsZero component on the difference:
component iz = IsZero();
iz.in <== a - b;
// iz.out === 1 when a == b, 0 when a != b
signal isNotEqual;
isNotEqual <== 1 - iz.out;
2. Range-Based Comparison for Small Values
For values known to be small, use smaller bit widths for efficiency:
// If a and b are known to be < 2^64
component lt = LessThan(64);
lt.in[0] <== a;
lt.in[1] <== b;
3. Batch Comparisons
When multiple comparisons share operands, reuse the bit decomposition:
// Decompose once, compare multiple times
component aBits = Num2Bits(64);
aBits.in <== a;
// Use aBits.out for multiple downstream comparisons
Common Mistakes
Forgetting range checks on comparator inputs: LessThan(n) produces correct results only when both inputs are less than 2^n. Without range checks, values near the field prime produce incorrect comparison results.
Using <-- with comparator output: Writing result <-- lt.out defeats the purpose. Always use result <== lt.out to maintain the constraint chain.
Comparing field elements directly: Field elements are in a cyclic group modulo a prime. The expression a > b during witness generation uses integer comparison, which is meaningless for values near the field prime. Always use bit-decomposition-based comparators.