@@ -26,13 +26,18 @@ pub struct HintSpaceProviderCols<T> {
2626 pub value : T ,
2727 pub multiplicity : T ,
2828 /// Inverse of multiplicity when nonzero; 0 for padding rows.
29- /// Used to derive a virtual boolean `is_non_padding = multiplicity * mult_inv`.
3029 pub mult_inv : T ,
3130 /// Boolean: 1 if hint_id changes between this row and the next non-padding row.
3231 pub hint_id_changed : T ,
3332 /// When hint_id_changed = 1: inverse of (next.hint_id - hint_id), proving they differ.
3433 /// When hint_id_changed = 0: unused (zero).
3534 pub diff_hint_id_inv : T ,
35+ /// Boolean: 1 if this row is not a padding row (multiplicity > 0).
36+ pub curr_is_non_padding : T ,
37+ /// Boolean: 1 if the next row is not a padding row.
38+ pub next_is_non_padding : T ,
39+ /// Boolean: curr_is_non_padding * next_is_non_padding.
40+ pub both_non_padding : T ,
3641}
3742
3843pub const NUM_HINT_SPACE_PROVIDER_COLS : usize = size_of :: < HintSpaceProviderCols < u8 > > ( ) ;
@@ -59,54 +64,65 @@ impl<AB: InteractionBuilder> Air<AB> for HintSpaceProviderAir {
5964 let next = main. row_slice ( 1 ) ;
6065 let next: & HintSpaceProviderCols < AB :: Var > = ( * next) . borrow ( ) ;
6166
62- // Derive virtual boolean `is_non_padding` from multiplicity.
63- let curr_is_non_padding: AB :: Expr = curr. multiplicity * curr. mult_inv ;
64- let next_is_non_padding: AB :: Expr = next. multiplicity * next. mult_inv ;
65-
66- // is_non_padding must be boolean.
67- builder. assert_zero ( curr_is_non_padding. clone ( ) * ( curr_is_non_padding. clone ( ) - AB :: Expr :: ONE ) ) ;
68- // multiplicity = 0 when is_non_padding = 0 (padding rows can't provide to the bus).
69- builder. assert_zero ( ( AB :: Expr :: ONE - curr_is_non_padding. clone ( ) ) * curr. multiplicity ) ;
67+ // curr_is_non_padding is boolean and tied to multiplicity via mult_inv.
68+ builder. assert_bool ( curr. curr_is_non_padding ) ;
69+ builder. assert_eq (
70+ curr. curr_is_non_padding ,
71+ curr. multiplicity * curr. mult_inv ,
72+ ) ;
73+ // Padding rows must have multiplicity = 0.
74+ builder. assert_zero (
75+ ( AB :: Expr :: ONE - curr. curr_is_non_padding ) * curr. multiplicity ,
76+ ) ;
7077
7178 builder. assert_bool ( curr. hint_id_changed ) ;
7279
73- // Non-padding rows must appear before padding rows (is_non_padding is non-increasing) .
80+ // Tie next_is_non_padding and both_non_padding columns to their definitions .
7481 builder
7582 . when_transition ( )
76- . when ( next_is_non_padding. clone ( ) )
77- . assert_one ( curr_is_non_padding. clone ( ) ) ;
83+ . assert_eq ( curr. next_is_non_padding , next. curr_is_non_padding ) ;
84+ builder. when_transition ( ) . assert_eq (
85+ curr. both_non_padding ,
86+ curr. curr_is_non_padding * curr. next_is_non_padding ,
87+ ) ;
88+
89+ // Non-padding rows must appear before padding rows (non-increasing).
90+ builder
91+ . when_transition ( )
92+ . when ( curr. next_is_non_padding )
93+ . assert_one ( curr. curr_is_non_padding ) ;
7894
7995 // Uniqueness of (hint_id, offset) among non-padding rows.
8096 // Rows are sorted by (hint_id, offset). For consecutive non-padding rows:
8197 // - Same hint_id (hint_id_changed=0): offset must increase by exactly 1.
8298 // - Different hint_id (hint_id_changed=1): hint_id must actually differ
8399 // (proven via inverse), and the new block starts at offset 0.
84- // Since offsets within a hint_id form a contiguous 0,1,2,...,N-1 sequence,
85- // this prevents any duplicate (hint_id, offset) pairs.
86- let both_non_padding: AB :: Expr = curr_is_non_padding * next_is_non_padding;
87100 let d_id: AB :: Expr = next. hint_id - curr. hint_id ;
88101
89102 // hint_id_changed = 0 => same hint_id, offset increases by 1
90103 builder
91104 . when_transition ( )
92- . when ( both_non_padding . clone ( ) )
105+ . when ( curr . both_non_padding )
93106 . when_ne ( curr. hint_id_changed , AB :: Expr :: ONE )
94107 . assert_zero ( d_id. clone ( ) ) ;
95108 builder
96109 . when_transition ( )
97- . when ( both_non_padding . clone ( ) )
110+ . when ( curr . both_non_padding )
98111 . when_ne ( curr. hint_id_changed , AB :: Expr :: ONE )
99112 . assert_eq ( next. offset , curr. offset + AB :: Expr :: ONE ) ;
100113
101- // hint_id_changed = 1 => hint_id actually differs, and new block starts at offset 0
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).
102117 builder
103118 . when_transition ( )
104- . when ( both_non_padding. clone ( ) )
105- . when ( curr. hint_id_changed )
106- . assert_one ( d_id * curr. diff_hint_id_inv ) ;
119+ . when ( curr. both_non_padding )
120+ . assert_eq ( d_id * curr. diff_hint_id_inv , curr. hint_id_changed ) ;
121+
122+ // hint_id_changed = 1 => new block starts at offset 0
107123 builder
108124 . when_transition ( )
109- . when ( both_non_padding)
125+ . when ( curr . both_non_padding )
110126 . when ( curr. hint_id_changed )
111127 . assert_zero ( next. offset ) ;
112128
@@ -175,6 +191,11 @@ impl<F: PrimeField32> HintSpaceProviderChip<F> {
175191 cols. value = * value;
176192 cols. multiplicity = * multiplicity;
177193 cols. mult_inv = multiplicity. try_inverse ( ) . unwrap ( ) ;
194+ cols. curr_is_non_padding = F :: ONE ;
195+ cols. next_is_non_padding =
196+ if n + 1 < num_non_padding_rows { F :: ONE } else { F :: ZERO } ;
197+ cols. both_non_padding =
198+ if n + 1 < num_non_padding_rows { F :: ONE } else { F :: ZERO } ;
178199
179200 // Fill auxiliary columns for the uniqueness constraint.
180201 if n + 1 < num_non_padding_rows {
0 commit comments