Skip to content

Commit 4a49ffe

Browse files
committed
Adjust hint constraints
1 parent efa8f0b commit 4a49ffe

File tree

3 files changed

+67
-24
lines changed

3 files changed

+67
-24
lines changed

extensions/native/circuit/src/extension/cuda.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,12 @@ impl VmProverExtension<GpuBabyBearPoseidon2Engine, DenseRecordArena, Native>
7979
let poseidon2 = NativePoseidon2ChipGpu::<1>::new(range_checker.clone(), timestamp_max_bits);
8080
inventory.add_executor_chip(poseidon2);
8181

82-
// HintSpaceProvider must be registered BEFORE NativeSumcheck because chips are
83-
// dispatched in reverse order: sumcheck runs first and populates the provider.
8482
let hint_air: &HintSpaceProviderAir = inventory.next_air::<HintSpaceProviderAir>()?;
85-
let cpu_chip = Arc::new(HintSpaceProviderChip::new(hint_air.hint_bus));
83+
let cpu_chip = Arc::new(HintSpaceProviderChip::new(
84+
hint_air.hint_bus,
85+
range_checker.clone(),
86+
timestamp_max_bits,
87+
));
8688
let provider_gpu = HintSpaceProviderChipGpu::new(cpu_chip.clone());
8789
inventory.add_periphery_chip(provider_gpu);
8890

extensions/native/circuit/src/extension/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use openvm_circuit::{
1414
},
1515
system::{memory::SharedMemoryHelper, SystemPort},
1616
};
17+
use openvm_circuit_primitives::is_less_than::IsLtSubAir;
1718
use openvm_circuit_derive::{AnyEnum, Executor, MeteredExecutor, PreflightExecutor};
1819
use openvm_instructions::{program::DEFAULT_PC_STEP, LocalOpcode, PhantomDiscriminant};
1920
use openvm_native_compiler::{
@@ -281,6 +282,10 @@ where
281282

282283
let hint_space_provider = HintSpaceProviderAir {
283284
hint_bus: hint_bridge.hint_bus(),
285+
lt_air: IsLtSubAir::new(
286+
range_checker,
287+
inventory.config().memory_config.timestamp_max_bits,
288+
),
284289
};
285290
inventory.add_air(hint_space_provider);
286291

@@ -368,7 +373,11 @@ where
368373
inventory.add_executor_chip(poseidon2);
369374

370375
let hint_bus = inventory.airs().system().hint_bridge.hint_bus();
371-
let hint_space_provider = Arc::new(HintSpaceProviderChip::new(hint_bus));
376+
let hint_space_provider = Arc::new(HintSpaceProviderChip::new(
377+
hint_bus,
378+
range_checker.clone(),
379+
timestamp_max_bits,
380+
));
372381

373382
inventory.next_air::<HintSpaceProviderAir>()?;
374383
inventory.add_periphery_chip(hint_space_provider.clone());

extensions/native/circuit/src/hint_space_provider.rs

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ use std::{
66
};
77

88
use 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+
};
914
use openvm_circuit_primitives_derive::AlignedBorrow;
1015
use 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)]
4651
pub struct HintSpaceProviderAir {
4752
pub hint_bus: HintBus,
53+
pub lt_air: IsLtSubAir,
4854
}
4955

5056
impl<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

139152
pub 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> {
146160
pub type SharedHintSpaceProviderChip<F> = Arc<HintSpaceProviderChip<F>>;
147161

148162
impl<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

Comments
 (0)