@@ -13,7 +13,9 @@ use zinc_poly::{
1313 univariate:: dynamic:: over_field:: DynamicPolynomialF ,
1414 utils:: { ArithErrors as PolyArithErrors , build_eq_x_r_vec} ,
1515} ;
16- use zinc_uair:: { ColumnLayout , ConstraintBuilder , TraceRow , Uair , ideal:: ImpossibleIdeal } ;
16+ use zinc_uair:: {
17+ BitOp , BitOpSpec , ColumnLayout , ConstraintBuilder , TraceRow , Uair , ideal:: ImpossibleIdeal ,
18+ } ;
1719use zinc_utils:: {
1820 cfg_into_iter, cfg_iter, from_ref:: FromRef , inner_transparent_field:: InnerTransparentField ,
1921} ;
@@ -57,21 +59,55 @@ where
5759 // `all_rows[row_idx][constraint_idx]` is a `DynamicPolynomialF<F>`:
5860 // the combined polynomial value of constraint `constraint_idx` at
5961 // trace row `row_idx`.
62+ let binary_poly_end = uair_sig. total_cols ( ) . num_binary_poly_cols ( ) ;
63+ let bit_op_down_offset = uair_sig
64+ . shifts ( )
65+ . iter ( )
66+ . take_while ( |spec| spec. source_col ( ) < binary_poly_end)
67+ . count ( ) ;
68+ let bit_op_cell_width = uair_sig. binary_poly_cell_width ( ) ;
69+
6070 let mut all_rows: Vec < Vec < DynamicPolynomialF < F > > > = cfg_into_iter ! ( 0 ..num_rows - 1 )
6171 . map ( |row_idx| {
6272 let up = & trace_matrix[ row_idx] ;
6373
64- let down: Vec < DynamicPolynomialF < F > > = uair_sig
65- . shifts ( )
66- . iter ( )
67- . map ( |spec| {
68- if row_idx + spec. shift_amount ( ) < num_rows {
69- trace_matrix[ row_idx + spec. shift_amount ( ) ] [ spec. source_col ( ) ] . clone ( )
70- } else {
71- DynamicPolynomialF :: zero ( ) // zero padding
72- }
73- } )
74- . collect ( ) ;
74+ // Build the down row in the canonical order:
75+ // [shifted_binary, bit_op_binary, shifted_arbitrary, shifted_int]
76+ // (cf. `UairSignature::with_bit_op_specs`). Splicing bit-op
77+ // virtuals into the binary_poly slice keeps `down` consistent
78+ // with `down_layout`; appending at the tail would misalign
79+ // constraints on mixed-type shift UAIRs.
80+ let mut down: Vec < DynamicPolynomialF < F > > =
81+ Vec :: with_capacity ( uair_sig. shifts ( ) . len ( ) + uair_sig. bit_op_specs ( ) . len ( ) ) ;
82+
83+ let mut shifts_iter = uair_sig. shifts ( ) . iter ( ) ;
84+ for _ in 0 ..bit_op_down_offset {
85+ let spec = shifts_iter. next ( ) . expect ( "offset within shifts range" ) ;
86+ if row_idx + spec. shift_amount ( ) < num_rows {
87+ down. push (
88+ trace_matrix[ row_idx + spec. shift_amount ( ) ] [ spec. source_col ( ) ] . clone ( ) ,
89+ ) ;
90+ } else {
91+ down. push ( DynamicPolynomialF :: zero ( ) ) ;
92+ }
93+ }
94+
95+ for spec in uair_sig. bit_op_specs ( ) {
96+ let cell_width = bit_op_cell_width
97+ . expect ( "bit_op_specs nonempty implies binary_poly_cell_width is set" ) ;
98+ let source = & trace_matrix[ row_idx] [ spec. source_col ( ) ] ;
99+ down. push ( apply_bit_op_to_poly ( source, spec, cell_width, field_cfg) ) ;
100+ }
101+
102+ for spec in shifts_iter {
103+ if row_idx + spec. shift_amount ( ) < num_rows {
104+ down. push (
105+ trace_matrix[ row_idx + spec. shift_amount ( ) ] [ spec. source_col ( ) ] . clone ( ) ,
106+ ) ;
107+ } else {
108+ down. push ( DynamicPolynomialF :: zero ( ) ) ;
109+ }
110+ }
75111
76112 evaluate_constraints_for_row :: < F , U > (
77113 up,
@@ -276,7 +312,7 @@ where
276312
277313 // Evaluate down (only shifted columns, per-spec shift amount).
278314 let sorted_shifts = uair_sig. shifts ( ) ;
279- let down_evals : Vec < DynamicPolynomialF < F > > = cfg_iter ! ( sorted_shifts)
315+ let shift_down_evals : Vec < DynamicPolynomialF < F > > = cfg_iter ! ( sorted_shifts)
280316 . map ( |spec| {
281317 let col = & trace_matrix[ spec. source_col ( ) ] ;
282318 let coeffs: Vec < F > = ( 0 ..max_num_coeffs)
@@ -286,6 +322,33 @@ where
286322 } )
287323 . collect :: < Result < Vec < _ > , EvaluationError > > ( ) ?;
288324
325+ // Evaluate bit-op virtuals from the already-computed up evaluations:
326+ // bit-ops act coefficient-wise, so MLE-eval-of-op at the same point is
327+ // op-of-MLE-eval on the source coefficient vector.
328+ let binary_poly_end = uair_sig. total_cols ( ) . num_binary_poly_cols ( ) ;
329+ let bit_op_down_offset = uair_sig
330+ . shifts ( )
331+ . iter ( )
332+ . take_while ( |spec| spec. source_col ( ) < binary_poly_end)
333+ . count ( ) ;
334+ let bit_op_down_evals: Vec < DynamicPolynomialF < F > > = uair_sig
335+ . bit_op_specs ( )
336+ . iter ( )
337+ . map ( |spec| {
338+ let cell_width = uair_sig
339+ . binary_poly_cell_width ( )
340+ . expect ( "bit_op_specs nonempty implies binary_poly_cell_width is set" ) ;
341+ apply_bit_op_to_poly ( & up_evals[ spec. source_col ( ) ] , spec, cell_width, field_cfg)
342+ } )
343+ . collect ( ) ;
344+
345+ // Splice into the canonical down ordering; see UairSignature docs.
346+ let mut down_evals: Vec < DynamicPolynomialF < F > > =
347+ Vec :: with_capacity ( shift_down_evals. len ( ) + bit_op_down_evals. len ( ) ) ;
348+ down_evals. extend_from_slice ( & shift_down_evals[ ..bit_op_down_offset] ) ;
349+ down_evals. extend ( bit_op_down_evals) ;
350+ down_evals. extend_from_slice ( & shift_down_evals[ bit_op_down_offset..] ) ;
351+
289352 // Apply UAIR constraints to the evaluated trace values
290353 let mut constraint_builder = CombinedPolyRowBuilder :: new ( num_constraints) ;
291354
@@ -311,6 +374,18 @@ where
311374 Ok ( combined_evaluations)
312375}
313376
377+ fn apply_bit_op_to_poly < F : PrimeField > (
378+ source : & DynamicPolynomialF < F > ,
379+ spec : & BitOpSpec ,
380+ cell_width : usize ,
381+ field_cfg : & F :: Config ,
382+ ) -> DynamicPolynomialF < F > {
383+ match spec. op ( ) {
384+ BitOp :: Rot ( c) => source. rotate_right_with_width ( c, cell_width, field_cfg) ,
385+ BitOp :: ShR ( c) => source. shr_with_width ( c, cell_width, field_cfg) ,
386+ }
387+ }
388+
314389pub struct CombinedPolyRowBuilder < F : PrimeField > {
315390 combined_evaluations : Vec < DynamicPolynomialF < F > > ,
316391}
0 commit comments