Skip to content

Commit 26cde12

Browse files
Tuanlinh12312claude
andcommitted
fix(var_range): address review comments
- Use assert_bool for max_bits_delta constraint - Use when(next.value).assert_eq pattern instead of assert_zero(a * b) - Simplify comments and reference README for explanation - Replace "checksum" terminology with proper security explanation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d49802d commit 26cde12

File tree

2 files changed

+9
-25
lines changed

2 files changed

+9
-25
lines changed

crates/circuits/primitives/src/var_range/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ The constraints enforce the enumeration pattern by observing that `value + two_t
2727
2. Transitions: `max_bits` can only stay the same or increment by 1; `value` can only be 0 or increment by 1; `two_to_max_bits` doubles when `max_bits` increments; and `value + two_to_max_bits` increases by exactly 1 each row
2828
3. Last row: end at `[value=0, max_bits=range_max_bits+1, mult=0]` (dummy row to make trace height a power of 2)
2929

30-
The last-row constraint acts as a checksum—if the trace ever "cheats" (e.g., `value` continues past $2^{\mathtt{max\_bits}} - 1$ instead of wrapping to 0), it cannot reach the required final state.
30+
The last-row constraint ensures correctness: if `value` ever continues past $2^{\mathtt{max\_bits}} - 1$ instead of wrapping to 0, the monotonic sum constraint forces `value` to keep incrementing and it can never return to 0, making the required final state unreachable.
3131

3232
The functionality and usage of the chip are very similar to those of the [Range Checker](../range/README.md) chip.

crates/circuits/primitives/src/var_range/mod.rs

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -78,46 +78,30 @@ impl<AB: InteractionBuilder> Air<AB> for VariableRangeCheckerAir {
7878
let local: &VariableRangeCols<AB::Var> = (*local).borrow();
7979
let next: &VariableRangeCols<AB::Var> = (*next).borrow();
8080

81-
// First-row constraints: ensure we start at [0, 0] with two_to_max_bits = 1.
81+
// First row: start at [value=0, max_bits=0, two_to_max_bits=1]
8282
builder.when_first_row().assert_zero(local.value);
8383
builder.when_first_row().assert_zero(local.max_bits);
8484
builder.when_first_row().assert_one(local.two_to_max_bits);
8585

86-
// Transition constraints use a "monotonic sum" approach instead of selector-based branching.
87-
// The key insight is that (value + two_to_max_bits) equals (row_index + 1), forming a
88-
// strictly increasing sequence. Combined with the last-row constraint, this forces the
89-
// unique valid trace enumeration.
90-
91-
// Constraint 1: max_bits can only stay the same or increment by 1
92-
// (next.max_bits - local.max_bits) * (next.max_bits - local.max_bits - 1) = 0
86+
// Transition constraints (see README for explanation)
9387
let max_bits_delta = next.max_bits - local.max_bits;
88+
builder.when_transition().assert_bool(max_bits_delta.clone());
9489
builder
9590
.when_transition()
96-
.assert_zero(max_bits_delta.clone() * (max_bits_delta.clone() - AB::Expr::ONE));
97-
98-
// Constraint 2: value can only be 0 or increment by 1
99-
// next.value * (next.value - local.value - 1) = 0
100-
builder
101-
.when_transition()
102-
.assert_zero(next.value * (next.value - local.value - AB::Expr::ONE));
103-
104-
// Constraint 3: two_to_max_bits = 2^max_bits (inductive)
105-
// next.two_to_max_bits = local.two_to_max_bits * (1 + max_bits_delta)
91+
.when(next.value)
92+
.assert_eq(next.value, local.value + AB::Expr::ONE);
10693
builder.when_transition().assert_eq(
10794
next.two_to_max_bits,
10895
local.two_to_max_bits * (AB::Expr::ONE + max_bits_delta),
10996
);
110-
111-
// Constraint 4: (value + two_to_max_bits) increases by exactly 1 each row
112-
// local.value + local.two_to_max_bits + 1 = next.value + next.two_to_max_bits
11397
builder.when_transition().assert_eq(
11498
local.value + local.two_to_max_bits + AB::Expr::ONE,
11599
next.value + next.two_to_max_bits,
116100
);
117101

118-
// Last-row constraints: ensure we end at the dummy row [0, range_max_bits+1].
119-
// This acts as a "checksum" - if the trace ever cheats (e.g., value continues past
120-
// 2^max_bits - 1 instead of wrapping), it cannot reach this required final state.
102+
// Last row: end at [value=0, max_bits=range_max_bits+1, mult=0]
103+
// If value ever skips wrapping to 0, the monotonic sum constraint forces it to keep
104+
// incrementing, making this final state unreachable.
121105
builder.when_last_row().assert_zero(local.value);
122106
builder.when_last_row().assert_eq(
123107
local.max_bits,

0 commit comments

Comments
 (0)