Skip to content

Commit c752ff5

Browse files
committed
piop: splice bit-op virtuals in ideal check
1 parent 5ff02f7 commit c752ff5

6 files changed

Lines changed: 252 additions & 48 deletions

File tree

piop/src/ideal_check.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,8 @@ mod tests {
357357
use rand::rng;
358358
use zinc_poly::univariate::{dense::DensePolynomial, dynamic::over_field::DynamicPolynomialF};
359359
use zinc_test_uair::{
360-
GenerateRandomTrace, TestUairNoMultiplication, TestUairSimpleMultiplication,
360+
GenerateRandomTrace, TestUairBitOpsMixedSplice, TestUairNoMultiplication,
361+
TestUairSimpleMultiplication,
361362
};
362363
use zinc_transcript::Blake3Transcript;
363364
use zinc_uair::{
@@ -457,5 +458,10 @@ mod tests {
457458
do_test::<TestUairSimpleMultiplication<Int<5>>, _, _, 32>(num_vars, |_ideal_over_ring| {
458459
IdealOrZero::<DegreeOneIdeal<_>>::zero()
459460
});
461+
462+
// Linear UAIR with bit-op virtuals and mixed down-row splicing.
463+
do_test::<TestUairBitOpsMixedSplice<Int<5>>, _, _, 32>(num_vars, |_ideal_over_ring| {
464+
IdealOrZero::<DegreeOneIdeal<_>>::zero()
465+
});
460466
}
461467
}

piop/src/ideal_check/combined_poly_builder.rs

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
};
1719
use 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+
314389
pub struct CombinedPolyRowBuilder<F: PrimeField> {
315390
combined_evaluations: Vec<DynamicPolynomialF<F>>,
316391
}

piop/src/test_utils.rs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use crate::{
1111
};
1212
use crypto_bigint::{Odd, modular::MontyParams};
1313
use crypto_primitives::{FromWithConfig, crypto_bigint_int::Int, crypto_bigint_monty::MontyField};
14-
use num_traits::Zero;
1514
use zinc_poly::univariate::{dense::DensePolynomial, dynamic::over_field::DynamicPolynomialF};
1615
use zinc_test_uair::GenerateRandomTrace;
1716
use zinc_transcript::traits::Transcript;
@@ -48,15 +47,6 @@ where
4847
+ IdealCheckProtocol,
4948
F: FromWithConfig<Int<5>>,
5049
{
51-
assert!(
52-
U::signature()
53-
.witness_cols()
54-
.num_binary_poly_cols()
55-
.is_zero()
56-
&& U::signature().witness_cols().num_int_cols().is_zero(),
57-
"the signature should be single typed"
58-
);
59-
6050
let field_cfg = test_config();
6151

6252
let num_constraints = count_constraints::<U>();
@@ -102,15 +92,6 @@ where
10292
+ IdealCheckProtocol,
10393
F: FromWithConfig<Int<5>>,
10494
{
105-
assert!(
106-
U::signature()
107-
.witness_cols()
108-
.num_binary_poly_cols()
109-
.is_zero()
110-
&& U::signature().witness_cols().num_int_cols().is_zero(),
111-
"the signature should be single typed"
112-
);
113-
11495
let field_cfg = test_config();
11596

11697
let num_constraints = count_constraints::<U>();

poly/src/univariate/dynamic/over_field.rs

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,26 @@ impl<F: PrimeField> DynamicPolynomialF<F> {
7373
/// `(i + c) mod D`. Missing coefficients are padded with zero before the
7474
/// rotation.
7575
pub fn rotate_right<const D: usize>(&self, c: usize, field_cfg: &F::Config) -> Self {
76+
self.rotate_right_with_width(c, D, field_cfg)
77+
}
78+
79+
/// Right-rotate the coefficient vector by `c` positions within a runtime
80+
/// width.
81+
///
82+
/// The output coefficient at position `i` is the input coefficient at
83+
/// `(i + c) mod width`. Missing coefficients are padded with zero before
84+
/// the rotation.
85+
pub fn rotate_right_with_width(&self, c: usize, width: usize, field_cfg: &F::Config) -> Self {
7686
assert!(
77-
c > 0 && c < D,
78-
"rotate_right count {c} out of range (must satisfy 0 < c < {D})",
87+
c > 0 && c < width,
88+
"rotate_right count {c} out of range (must satisfy 0 < c < {width})",
7989
);
8090

8191
let mut coeffs = self.coeffs.clone();
82-
coeffs.resize(D, F::zero_with_cfg(field_cfg));
92+
coeffs.resize(width, F::zero_with_cfg(field_cfg));
8393
Self {
84-
coeffs: (0..D)
85-
.map(|i| coeffs[rem!(add!(i, c), D)].clone())
94+
coeffs: (0..width)
95+
.map(|i| coeffs[rem!(add!(i, c), width)].clone())
8696
.collect(),
8797
}
8898
}
@@ -93,19 +103,29 @@ impl<F: PrimeField> DynamicPolynomialF<F> {
93103
/// `i + c`, or zero when that index is outside width `D`. Missing
94104
/// coefficients are padded with zero before the shift.
95105
pub fn shr<const D: usize>(&self, c: usize, field_cfg: &F::Config) -> Self {
106+
self.shr_with_width(c, D, field_cfg)
107+
}
108+
109+
/// Right-shift the coefficient vector by `c` positions within a runtime
110+
/// width.
111+
///
112+
/// The output coefficient at position `i` is the input coefficient at
113+
/// `i + c`, or zero when that index is outside `width`. Missing
114+
/// coefficients are padded with zero before the shift.
115+
pub fn shr_with_width(&self, c: usize, width: usize, field_cfg: &F::Config) -> Self {
96116
assert!(
97-
c > 0 && c < D,
98-
"shr count {c} out of range (must satisfy 0 < c < {D})",
117+
c > 0 && c < width,
118+
"shr count {c} out of range (must satisfy 0 < c < {width})",
99119
);
100120

101121
let zero = F::zero_with_cfg(field_cfg);
102122
let mut coeffs = self.coeffs.clone();
103-
coeffs.resize(D, zero.clone());
123+
coeffs.resize(width, zero.clone());
104124
Self {
105-
coeffs: (0..D)
125+
coeffs: (0..width)
106126
.map(|i| {
107127
let j = add!(i, c);
108-
if j < D {
128+
if j < width {
109129
coeffs[j].clone()
110130
} else {
111131
zero.clone()
@@ -870,6 +890,10 @@ mod tests {
870890
poly.rotate_right::<5>(2, &field_cfg),
871891
DynamicPolynomialF::new([f(3), f(0), f(0), f(1), f(2)])
872892
);
893+
assert_eq!(
894+
poly.rotate_right_with_width(2, 5, &field_cfg),
895+
DynamicPolynomialF::new([f(3), f(0), f(0), f(1), f(2)])
896+
);
873897
}
874898

875899
#[test]
@@ -881,6 +905,10 @@ mod tests {
881905
poly.shr::<5>(2, &field_cfg),
882906
DynamicPolynomialF::new([f(3), f(0), f(0), f(0), f(0)])
883907
);
908+
assert_eq!(
909+
poly.shr_with_width(2, 5, &field_cfg),
910+
DynamicPolynomialF::new([f(3), f(0), f(0), f(0), f(0)])
911+
);
884912
}
885913

886914
#[test]

0 commit comments

Comments
 (0)