@@ -6,6 +6,11 @@ use std::{
66} ;
77
88use openvm_circuit:: system:: memory:: offline_checker:: HintBus ;
9+ use openvm_circuit_primitives:: {
10+ is_less_than:: { IsLessThanIo , IsLtSubAir } ,
11+ var_range:: SharedVariableRangeCheckerChip ,
12+ SubAir , TraceSubRowGenerator ,
13+ } ;
914use openvm_circuit_primitives_derive:: AlignedBorrow ;
1015use openvm_stark_backend:: {
1116 config:: { StarkGenericConfig , Val } ,
@@ -17,6 +22,7 @@ use openvm_stark_backend::{
1722 rap:: { get_air_name, BaseAirWithPublicValues , PartitionedBaseAir } ,
1823 Chip , ChipUsageGetter ,
1924} ;
25+ pub const HINT_ID_LT_AUX_LEN : usize = 2 ;
2026
2127#[ derive( Default , AlignedBorrow , Copy , Clone ) ]
2228#[ repr( C ) ]
@@ -29,9 +35,8 @@ pub struct HintSpaceProviderCols<T> {
2935 pub mult_inv : T ,
3036 /// Boolean: 1 if hint_id changes between this row and the next non-padding row.
3137 pub hint_id_changed : T ,
32- /// When hint_id_changed = 1: inverse of (next.hint_id - hint_id), proving they differ.
33- /// When hint_id_changed = 0: unused (zero).
34- pub diff_hint_id_inv : T ,
38+ /// Auxiliary limbs for IsLtSubAir range check decomposition of (curr.hint_id < next.hint_id).
39+ pub hint_id_lt_aux : [ T ; HINT_ID_LT_AUX_LEN ] ,
3540 /// Boolean: 1 if this row is not a padding row (multiplicity > 0).
3641 pub curr_is_non_padding : T ,
3742 /// Boolean: 1 if the next row is not a padding row.
@@ -45,6 +50,7 @@ pub const NUM_HINT_SPACE_PROVIDER_COLS: usize = size_of::<HintSpaceProviderCols<
4550#[ derive( Clone , Debug ) ]
4651pub struct HintSpaceProviderAir {
4752 pub hint_bus : HintBus ,
53+ pub lt_air : IsLtSubAir ,
4854}
4955
5056impl < F : Field > BaseAirWithPublicValues < F > for HintSpaceProviderAir { }
@@ -95,29 +101,36 @@ impl<AB: InteractionBuilder> Air<AB> for HintSpaceProviderAir {
95101 // Uniqueness of (hint_id, offset) among non-padding rows.
96102 // Rows are sorted by (hint_id, offset). For consecutive non-padding rows:
97103 // - Same hint_id (hint_id_changed=0): offset must increase by exactly 1.
98- // - Different hint_id (hint_id_changed=1): hint_id must actually differ
99- // (proven via inverse ), and the new block starts at offset 0.
104+ // - Different hint_id (hint_id_changed=1): hint_id strictly increases
105+ // (proven via IsLtSubAir range-check ), and the new block starts at offset 0.
100106 let d_id: AB :: Expr = next. hint_id - curr. hint_id ;
101107
102108 // hint_id_changed = 0 => same hint_id, offset increases by 1
103109 builder
104110 . when_transition ( )
105111 . when ( curr. both_non_padding )
106112 . when_ne ( curr. hint_id_changed , AB :: Expr :: ONE )
107- . assert_zero ( d_id. clone ( ) ) ;
113+ . assert_zero ( d_id) ;
108114 builder
109115 . when_transition ( )
110116 . when ( curr. both_non_padding )
111117 . when_ne ( curr. hint_id_changed , AB :: Expr :: ONE )
112118 . assert_eq ( next. offset , curr. offset + AB :: Expr :: ONE ) ;
113119
114- // Combined inverse check: d_id * diff_hint_id_inv = hint_id_changed.
115- // When hint_id_changed = 0: trivially 0 = 0 (since d_id = 0 from above).
116- // When hint_id_changed = 1: proves d_id != 0 (hint_id actually changed).
117- builder
118- . when_transition ( )
119- . when ( curr. both_non_padding )
120- . assert_eq ( d_id * curr. diff_hint_id_inv , curr. hint_id_changed ) ;
120+ // hint_id_changed = 1 => hint_id strictly increases (curr.hint_id < next.hint_id)
121+ let lt_count: AB :: Expr = curr. hint_id_changed . into ( ) * curr. both_non_padding . into ( ) ;
122+ self . lt_air . eval (
123+ builder,
124+ (
125+ IsLessThanIo {
126+ x : curr. hint_id . into ( ) ,
127+ y : next. hint_id . into ( ) ,
128+ out : curr. hint_id_changed . into ( ) ,
129+ count : lt_count,
130+ } ,
131+ & curr. hint_id_lt_aux ,
132+ ) ,
133+ ) ;
121134
122135 // hint_id_changed = 1 => new block starts at offset 0
123136 builder
@@ -138,6 +151,7 @@ impl<AB: InteractionBuilder> Air<AB> for HintSpaceProviderAir {
138151
139152pub struct HintSpaceProviderChip < F > {
140153 pub air : HintSpaceProviderAir ,
154+ range_checker : SharedVariableRangeCheckerChip ,
141155 /// Maps (hint_id, offset) -> (value, multiplicity).
142156 /// Deduplicates keys and tracks how many times each is looked up.
143157 data : Mutex < HashMap < ( F , F ) , ( F , F ) > > ,
@@ -146,9 +160,21 @@ pub struct HintSpaceProviderChip<F> {
146160pub type SharedHintSpaceProviderChip < F > = Arc < HintSpaceProviderChip < F > > ;
147161
148162impl < F > HintSpaceProviderChip < F > {
149- pub fn new ( hint_bus : HintBus ) -> Self {
163+ pub fn new (
164+ hint_bus : HintBus ,
165+ range_checker : SharedVariableRangeCheckerChip ,
166+ hint_id_max_bits : usize ,
167+ ) -> Self {
168+ let lt_air = IsLtSubAir :: new ( range_checker. bus ( ) , hint_id_max_bits) ;
169+ assert_eq ! (
170+ lt_air. decomp_limbs, HINT_ID_LT_AUX_LEN ,
171+ "hint_id_max_bits={hint_id_max_bits} with range_max_bits={} requires {} limbs, but HINT_ID_LT_AUX_LEN={HINT_ID_LT_AUX_LEN}" ,
172+ range_checker. range_max_bits( ) ,
173+ lt_air. decomp_limbs
174+ ) ;
150175 Self {
151- air : HintSpaceProviderAir { hint_bus } ,
176+ air : HintSpaceProviderAir { hint_bus, lt_air } ,
177+ range_checker,
152178 data : Mutex :: new ( HashMap :: new ( ) ) ,
153179 }
154180 }
@@ -200,10 +226,16 @@ impl<F: PrimeField32> HintSpaceProviderChip<F> {
200226 // Fill auxiliary columns for the uniqueness constraint.
201227 if n + 1 < num_non_padding_rows {
202228 let next_hint_id = entries[ n + 1 ] . 0 . 0 ;
203- let d_id = next_hint_id - * hint_id;
204- if d_id != F :: ZERO {
205- cols. hint_id_changed = F :: ONE ;
206- cols. diff_hint_id_inv = d_id. try_inverse ( ) . unwrap ( ) ;
229+ if next_hint_id != * hint_id {
230+ // hint_id changes: fill IsLtSubAir aux columns
231+ self . air . lt_air . generate_subrow (
232+ (
233+ self . range_checker . as_ref ( ) ,
234+ hint_id. as_canonical_u32 ( ) ,
235+ next_hint_id. as_canonical_u32 ( ) ,
236+ ) ,
237+ ( & mut cols. hint_id_lt_aux , & mut cols. hint_id_changed ) ,
238+ ) ;
207239 } else {
208240 debug_assert_eq ! (
209241 entries[ n + 1 ] . 0 . 1 ,
@@ -213,7 +245,7 @@ impl<F: PrimeField32> HintSpaceProviderChip<F> {
213245 offset,
214246 entries[ n + 1 ] . 0 . 1
215247 ) ;
216- // hint_id_changed = 0, diff_hint_id_inv = 0 (defaults)
248+ // hint_id_changed = 0, hint_id_lt_aux = [0; ..] (defaults)
217249 }
218250 }
219251 // Last non-padding row: aux columns stay zero (no next non-padding row to compare)
0 commit comments