Field Overflow
Detects arithmetic that may wrap the finite field prime, producing field-correct but semantically wrong results for circuits modelling bounded integers.
Field Overflow
Overview
The field overflow detector finds arithmetic operations whose operands are not bounded below the square root of the field prime and whose result is used as if it represented an integer. Circom operates over the BN254 (alt-BN128) scalar field, which has order p ≈ 2^254. Every signal is an element of this field, and every +, -, and * is implicitly taken modulo p. Circuits that model balances, prices, counters, Merkle indices, or any other bounded integer domain assume the arithmetic behaves like unsigned integer arithmetic, and that assumption holds only while the operands stay well below p.
The detector tracks three patterns. The first is multiplication of two signals neither of which has a bit-width bound below ~126 bits: such a multiplication may produce a result above 2^252, which is close enough to p that wrapping is feasible. The second is subtraction where the minuend is not known to be at least as large as the subtrahend: in the field, 5 - 10 produces p - 5, a 254-bit value that passes any unguarded comparison as “large”. The third is repeated addition inside a loop whose iteration count is not bounded, which can accumulate past p.
Why This Is an Issue
A circuit that enforces balance >= amount; newBalance <== balance - amount; looks correct by inspection. In the field, however, the subtraction always succeeds, and if amount > balance the result is p - (amount - balance), a value in the upper half of the field. Without a bit-width bound on newBalance, a later LessThan comparison may classify this huge number as “less than the maximum allowed balance” and the transfer proceeds. The on-chain state machine then credits the attacker with an enormous balance.
The same issue appears in rollup circuits that track cumulative fees, in voting circuits that tally yes/no counts, and in any circuit that represents amounts as unconstrained field elements. The verifier cannot distinguish a legitimate large value from a wrapped small value; that distinction lives entirely in the absent range check.
How to Resolve
Bound every operand of integer-style arithmetic to a bit width k small enough that all intermediate results stay below p. A practical rule is to keep operands under 2^126 so their products stay under 2^252.
// Vulnerable: unconstrained operands, subtraction wraps
template Transfer() {
signal input balance;
signal input amount;
signal output newBalance;
newBalance <== balance - amount;
}
// Fixed: bounded operands and an explicit underflow check
template Transfer() {
signal input balance;
signal input amount;
signal output newBalance;
component bB = Num2Bits(64); // balance fits in 64 bits
bB.in <== balance;
component bA = Num2Bits(64);
bA.in <== amount;
// amount <= balance
component leq = LessEqThan(64);
leq.in[0] <== amount;
leq.in[1] <== balance;
leq.out === 1;
newBalance <== balance - amount;
component bN = Num2Bits(64);
bN.in <== newBalance; // newBalance still fits
}
Examples
Sample Sigvex Output
{
"detector": "field-overflow",
"severity": "high",
"confidence": 0.75,
"title": "Unbounded multiplication of `a` and `b` may wrap the field prime",
"template": "Multiply",
"line": 7,
"operator": "*",
"operands": ["a", "b"],
"description": "Signals `a` and `b` are multiplied without any bit-width bound on either operand. The product may exceed p ≈ 2^254, producing a wrapped result that is mathematically valid in the field but wrong for integer semantics.",
"recommendation": "Apply `Num2Bits(k)` to each operand with k ≤ 126 before the multiplication, or use a dedicated bounded-multiplication template."
}
Detection Methodology
The detector collects bit-width bounds for every signal by looking for applications of Num2Bits, LessThan, Bits2Num, and similar well-known components, plus direct boolean constraints (x * (x - 1) === 0). Each bounded signal is tagged with the width k such that the signal is known to fit in [0, 2^k).
It then walks every arithmetic operation and computes an upper bound on the result from the operand bounds, using the rules k(a+b) = max(k(a), k(b)) + 1, k(a*b) = k(a) + k(b), and k(a-b) = k(a) + 1 (with an additional underflow flag for subtraction). Operations whose result exceeds a tunable fraction of 254 bits, or whose operands lack any bound at all, are reported. Subtractions without an explicit a >= b check trigger an underflow-specific finding at higher confidence.
Limitations
- Bounds propagated across template boundaries are tracked only when the parent explicitly applies a bounding component to the passed signal.
- Custom range-check implementations that do not look like
Num2Bitsare not recognised, so their outputs are treated as unbounded. - Constant folding is limited to literal operands; expressions like
x + 0are simplified butx + (y - y)is not.
Related Detectors
- Missing Range Check — signals consumed by bit-width-sensitive components without bounds
- Unsafe Comparison — comparators applied outside their intended range
- Nondeterministic Witness — nondeterministic operators in
<--
References
- Circom Documentation: Signals
- iden3/circomlib: bitify.circom
- BN254 (alt-BN128) Curve Parameters
- Bowe, S., Gabizon, A., Miers, I. “Scalable Multi-party Computation for zk-SNARK Parameters.” IACR ePrint 2017/1050.