1#![allow(clippy::arithmetic_side_effects)] mod generate_trace;
3
4pub use generate_trace::*;
5
6use crypto_primitives::{ConstSemiring, FixedSemiring, Semiring, boolean::Boolean};
7use num_traits::Zero;
8use rand::{
9 distr::{Distribution, StandardUniform},
10 prelude::*,
11};
12use std::marker::PhantomData;
13use zinc_poly::{
14 EvaluatablePolynomial,
15 mle::{DenseMultilinearExtension, MultilinearExtensionRand},
16 univariate::{
17 binary::BinaryPoly, dense::DensePolynomial,
18 dynamic::over_fixed_semiring::DynamicPolynomialFS,
19 },
20};
21use zinc_uair::{
22 ConstraintBuilder, PublicColumnLayout, ShiftSpec, TotalColumnLayout, TraceRow, Uair,
23 UairSignature, UairTrace,
24 ideal::{DegreeOneIdeal, ImpossibleIdeal},
25};
26use zinc_utils::from_ref::FromRef;
27
28#[derive(Clone, Debug)]
29pub struct TestUairSimpleMultiplication<R>(PhantomData<R>);
30
31impl<R> Uair for TestUairSimpleMultiplication<R>
32where
33 R: Semiring + 'static,
34{
35 type Ideal = ImpossibleIdeal; type Scalar = DensePolynomial<R, 32>;
37
38 fn signature() -> UairSignature {
39 let total = TotalColumnLayout::new(0, 3, 0);
40 let shifts = (0..3).map(|i| ShiftSpec::new(i, 1)).collect();
41 UairSignature::new(total, PublicColumnLayout::default(), shifts, vec![])
42 }
43
44 fn constrain_general<B, FromR, MulByScalar, IFromR>(
45 b: &mut B,
46 up: TraceRow<B::Expr>,
47 down: TraceRow<B::Expr>,
48 _from_ref: FromR,
49 _mbs: MulByScalar,
50 _ideal_from_ref: IFromR,
51 ) where
52 B: ConstraintBuilder,
53 {
54 let up = up.arbitrary_poly;
55 let down = down.arbitrary_poly;
56
57 b.assert_zero(up[0].clone() * &up[1] - &down[0]);
58 b.assert_zero(up[1].clone() * &up[2] - &down[1]);
59 b.assert_zero(up[0].clone() * &up[2] - &down[2]);
60 }
61}
62
63impl<R> GenerateRandomTrace<32> for TestUairSimpleMultiplication<R>
64where
65 R: FixedSemiring + From<i8> + 'static,
66 StandardUniform: Distribution<R>,
67{
68 type PolyCoeff = R;
69 type Int = R;
70
71 fn generate_random_trace<Rng: RngCore + ?Sized>(
72 num_vars: usize,
73 rng: &mut Rng,
74 ) -> UairTrace<'static, R, R, 32> {
75 let mut a: Vec<DynamicPolynomialFS<R>> =
76 vec![DynamicPolynomialFS::new(vec![R::from(rng.random::<i8>())])];
77 let mut b: Vec<DynamicPolynomialFS<R>> = vec![DynamicPolynomialFS::new(vec![
78 R::zero(),
79 R::from(rng.random::<i8>()),
80 ])];
81 let mut c: Vec<DynamicPolynomialFS<R>> = vec![DynamicPolynomialFS::new(vec![
82 R::zero(),
83 R::from(rng.random::<i8>()),
84 ])];
85
86 for i in 1..1 << num_vars {
87 let prev_a = a[i - 1].clone();
88 let prev_b = b[i - 1].clone();
89 let prev_c = c[i - 1].clone();
90
91 a.push(prev_a.clone() * &prev_b);
92 b.push(prev_b * &prev_c);
93 c.push(prev_a * prev_c);
94 }
95
96 let arbitrary_poly = vec![
97 a.into_iter()
98 .map(|x| {
99 assert!(
100 x.degree() < Some(32),
101 "degree bound exceeded: {}",
102 x.degree().expect("if the degree is large it's not None")
103 );
104 DensePolynomial::new(x.coeffs)
105 })
106 .collect(),
107 b.into_iter()
108 .map(|x| {
109 assert!(
110 x.degree() < Some(32),
111 "degree bound exceeded: {}",
112 x.degree().expect("if the degree is large it's not None"),
113 );
114 DensePolynomial::new(x.coeffs)
115 })
116 .collect(),
117 c.into_iter()
118 .map(|x| {
119 assert!(
120 x.degree() < Some(32),
121 "degree bound exceeded: {}",
122 x.degree().expect("if the degree is large it's not None"),
123 );
124 DensePolynomial::new(x.coeffs)
125 })
126 .collect(),
127 ]
128 .into();
129 UairTrace {
130 arbitrary_poly,
131 ..Default::default()
132 }
133 }
134}
135
136#[derive(Clone, Debug)]
137pub struct TestUairNoMultiplication<R>(PhantomData<R>);
138
139impl<R> Uair for TestUairNoMultiplication<R>
140where
141 R: ConstSemiring + From<i32> + 'static,
142{
143 type Ideal = DegreeOneIdeal<R>;
144 type Scalar = DensePolynomial<R, 32>;
145
146 fn signature() -> UairSignature {
147 let total = TotalColumnLayout::new(0, 3, 0);
148 UairSignature::new(total, PublicColumnLayout::default(), vec![], vec![])
149 }
150
151 fn constrain_general<B, FromR, MulByScalar, IFromR>(
152 b: &mut B,
153 up: TraceRow<B::Expr>,
154 _down: TraceRow<B::Expr>,
155 _from_ref: FromR,
156 _mbs: MulByScalar,
157 ideal_from_ref: IFromR,
158 ) where
159 B: ConstraintBuilder,
160 IFromR: Fn(&Self::Ideal) -> B::Ideal,
161 {
162 let up = up.arbitrary_poly;
163
164 b.assert_in_ideal(
165 up[0].clone() + &up[1] - &up[2],
166 &ideal_from_ref(&DegreeOneIdeal::new(R::from(2))),
167 );
168 }
169}
170
171impl<R> GenerateRandomTrace<32> for TestUairNoMultiplication<R>
172where
173 R: ConstSemiring + From<i32> + 'static,
174{
175 type PolyCoeff = R;
176 type Int = R;
177
178 fn generate_random_trace<Rng: rand::RngCore + ?Sized>(
179 num_vars: usize,
180 rng: &mut Rng,
181 ) -> UairTrace<'static, R, R, 32> {
182 let a: DenseMultilinearExtension<DensePolynomial<R, 32>> =
183 DenseMultilinearExtension::rand(num_vars, rng)
184 .into_iter()
185 .map(|x: u32| {
186 DensePolynomial::from_ref(&DensePolynomial::<Boolean, _>::from(
187 BinaryPoly::<32>::from(x),
188 ))
189 })
190 .collect();
191
192 let b: DenseMultilinearExtension<_> = DenseMultilinearExtension::rand(num_vars, rng)
193 .into_iter()
194 .map(|x: u32| {
195 DensePolynomial::from_ref(&DensePolynomial::<Boolean, _>::from(
196 BinaryPoly::<32>::from(x),
197 ))
198 })
199 .collect();
200
201 let c = a.clone() + b.clone();
202
203 UairTrace {
204 arbitrary_poly: vec![a, b, c].into(),
205 ..Default::default()
206 }
207 }
208}
209
210#[derive(Clone, Debug)]
211pub struct TestUairScalarMultiplications<R>(PhantomData<R>);
212
213impl<R> Uair for TestUairScalarMultiplications<R>
214where
215 R: ConstSemiring + From<i8> + 'static,
216{
217 type Ideal = DegreeOneIdeal<R>;
218 type Scalar = DensePolynomial<R, 32>;
219
220 fn signature() -> UairSignature {
221 let total = TotalColumnLayout::new(0, 3, 0);
222 UairSignature::new(total, PublicColumnLayout::default(), vec![], vec![])
223 }
224
225 fn constrain_general<B, FromR, MulByScalar, IFromR>(
226 b: &mut B,
227 up: TraceRow<B::Expr>,
228 _down: TraceRow<B::Expr>,
229 from_ref: FromR,
230 mbs: MulByScalar,
231 ideal_from_ref: IFromR,
232 ) where
233 B: ConstraintBuilder,
234 IFromR: Fn(&Self::Ideal) -> B::Ideal,
235 FromR: Fn(&DensePolynomial<R, 32>) -> B::Expr,
236 MulByScalar: Fn(&B::Expr, &DensePolynomial<R, 32>) -> Option<B::Expr>,
237 {
238 let up = up.arbitrary_poly;
239
240 b.assert_in_ideal(
241 mbs(
242 &up[0],
243 &DensePolynomial::new([R::from(-1), R::from(0), R::from(1)]),
244 )
245 .expect("arithmetic overflow")
246 + &up[1]
247 - &up[2]
248 + from_ref(&DensePolynomial::new([
249 R::from(1),
250 R::from(2),
251 R::from(3),
252 R::from(4),
253 ])),
254 &ideal_from_ref(&DegreeOneIdeal::new(R::from(2))),
255 );
256 }
257}
258
259#[derive(Clone, Debug)]
260pub struct BinaryDecompositionUair<R>(PhantomData<R>);
261
262impl<R> Uair for BinaryDecompositionUair<R>
263where
264 R: ConstSemiring + From<u32> + 'static,
265{
266 type Ideal = DegreeOneIdeal<R>;
267 type Scalar = DensePolynomial<R, 32>;
268
269 fn signature() -> UairSignature {
270 let total = TotalColumnLayout::new(1, 0, 1);
271 UairSignature::new(total, PublicColumnLayout::default(), vec![], vec![])
272 }
273
274 fn constrain_general<B, FromR, MulByScalar, IFromR>(
275 b: &mut B,
276 up: TraceRow<B::Expr>,
277 _down: TraceRow<B::Expr>,
278 _from_ref: FromR,
279 _mbs: MulByScalar,
280 ideal_from_ref: IFromR,
281 ) where
282 B: ConstraintBuilder,
283 FromR: Fn(&Self::Scalar) -> B::Expr,
284 MulByScalar: Fn(&B::Expr, &Self::Scalar) -> Option<B::Expr>,
285 IFromR: Fn(&Self::Ideal) -> B::Ideal,
286 {
287 let int_col = &up.int[0];
288 let binary_poly_col = &up.binary_poly[0];
289
290 b.assert_in_ideal(
291 binary_poly_col.clone() - int_col,
292 &ideal_from_ref(&DegreeOneIdeal::new(R::from(2))),
293 );
294 }
295}
296
297impl<R> GenerateRandomTrace<32> for BinaryDecompositionUair<R>
298where
299 R: ConstSemiring + From<u32> + 'static,
300{
301 type PolyCoeff = R;
302 type Int = R;
303
304 fn generate_random_trace<Rng: rand::RngCore + ?Sized>(
305 num_vars: usize,
306 rng: &mut Rng,
307 ) -> UairTrace<'static, R, R, 32> {
308 let int_col_u32: DenseMultilinearExtension<u32> =
309 DenseMultilinearExtension::rand(num_vars, rng);
310
311 let binary_poly_col: DenseMultilinearExtension<BinaryPoly<32>> =
312 int_col_u32.iter().map(|i| BinaryPoly::from(*i)).collect();
313
314 let int_col = int_col_u32.into_iter().map(R::from).collect();
315
316 UairTrace {
317 binary_poly: vec![binary_poly_col].into(),
318 arbitrary_poly: vec![].into(),
319 int: vec![int_col].into(),
320 }
321 }
322}
323
324#[derive(Clone, Debug)]
325pub struct BigLinearUair<R>(PhantomData<R>);
326
327impl<R> Uair for BigLinearUair<R>
328where
329 R: ConstSemiring + From<u32> + 'static,
330{
331 type Ideal = DegreeOneIdeal<R>;
332 type Scalar = DensePolynomial<R, 32>;
333
334 fn signature() -> UairSignature {
335 let total = TotalColumnLayout::new(16, 0, 1);
336 let shifts = (0..16).map(|i| ShiftSpec::new(i, 1)).collect();
337 UairSignature::new(total, PublicColumnLayout::default(), shifts, vec![])
338 }
339
340 fn constrain_general<B, FromR, MulByScalar, IFromR>(
341 b: &mut B,
342 up: TraceRow<B::Expr>,
343 down: TraceRow<B::Expr>,
344 _from_ref: FromR,
345 _mbs: MulByScalar,
346 ideal_from_ref: IFromR,
347 ) where
348 B: ConstraintBuilder,
349 FromR: Fn(&Self::Scalar) -> B::Expr,
350 MulByScalar: Fn(&B::Expr, &Self::Scalar) -> Option<B::Expr>,
351 IFromR: Fn(&Self::Ideal) -> B::Ideal,
352 {
353 let one_ideal = DegreeOneIdeal::new(R::from(1));
354 let two_ideal = DegreeOneIdeal::new(R::from(2));
355
356 let sum_of_binary_polys = up.binary_poly[1..]
357 .iter()
358 .fold(up.binary_poly[0].clone(), |acc, next| acc + next);
359
360 b.assert_in_ideal(
363 sum_of_binary_polys - &up.int[0],
364 &ideal_from_ref(&one_ideal),
365 );
366
367 b.assert_in_ideal(
369 down.binary_poly[0].clone() - &up.int[0],
370 &ideal_from_ref(&two_ideal),
371 );
372
373 up.binary_poly[1..]
376 .iter()
377 .zip(&down.binary_poly[1..])
378 .for_each(|(up, down)| {
379 b.assert_in_ideal(up.clone() - down, &ideal_from_ref(&one_ideal));
380 });
381 }
382}
383
384impl<R> GenerateRandomTrace<32> for BigLinearUair<R>
385where
386 R: ConstSemiring + From<u32> + 'static,
387{
388 type PolyCoeff = R;
389 type Int = R;
390
391 fn generate_random_trace<Rng: rand::RngCore + ?Sized>(
392 num_vars: usize,
393 rng: &mut Rng,
394 ) -> UairTrace<'static, R, R, 32> {
395 fn random_binary_poly_with_popcount(
397 popcount: u32,
398 rng: &mut (impl rand::RngCore + ?Sized),
399 ) -> BinaryPoly<32> {
400 let mut positions: [u8; 32] =
401 core::array::from_fn(|i| u8::try_from(i).expect("can't fail"));
402 for i in 0..popcount as usize {
403 let j = i + rng.next_u32() as usize % (32 - i);
404 positions.swap(i, j);
405 }
406 let mut value: u32 = 0;
407 for &pos in &positions[..popcount as usize] {
408 value |= 1u32 << pos;
409 }
410 BinaryPoly::from(value)
411 }
412
413 let mut binary_poly_cols: Vec<DenseMultilinearExtension<BinaryPoly<32>>> =
414 vec![(0..(1 << num_vars)).map(|_| BinaryPoly::zero()).collect(); 16];
415 let mut int_col: DenseMultilinearExtension<Self::Int> =
416 (0..(1 << num_vars)).map(|_| R::ZERO).collect();
417
418 binary_poly_cols.iter_mut().for_each(|col| {
419 col[0] = rng.random();
420 });
421
422 for i in 0..(1 << num_vars) - 1 {
423 let int: u32 = binary_poly_cols
424 .iter()
425 .map(|col| col[i].evaluate_at_point(&1_u32).expect("should be fine"))
426 .sum();
427 int_col[i] = R::from(int);
428
429 binary_poly_cols[0][i + 1] = BinaryPoly::from(int);
430 binary_poly_cols[1..].iter_mut().for_each(|col| {
431 let popcount = col[i].evaluate_at_point(&1_u32).expect("should be fine");
432 col[i + 1] = random_binary_poly_with_popcount(popcount, rng);
433 });
434 }
435
436 let len = int_col.len();
437
438 int_col[len - 1] = R::from(
439 binary_poly_cols
440 .iter()
441 .map(|col| {
442 col[len - 1]
443 .evaluate_at_point(&1_u32)
444 .expect("should be fine")
445 })
446 .sum::<u32>(),
447 );
448
449 UairTrace {
450 binary_poly: binary_poly_cols.into(),
451 arbitrary_poly: vec![].into(),
452 int: vec![int_col].into(),
453 }
454 }
455}
456
457#[derive(Clone, Debug)]
458pub struct BigLinearUairWithPublicInput<R>(PhantomData<R>);
459
460impl<R> Uair for BigLinearUairWithPublicInput<R>
461where
462 R: ConstSemiring + From<u32> + 'static,
463{
464 type Ideal = <BigLinearUair<R> as Uair>::Ideal;
465 type Scalar = <BigLinearUair<R> as Uair>::Scalar;
466
467 fn signature() -> UairSignature {
468 let total = TotalColumnLayout::new(16, 0, 1);
469 let public = PublicColumnLayout::new(4, 0, 0);
470 let shifts = (0..16).map(|i| ShiftSpec::new(i, 1)).collect();
471 UairSignature::new(total, public, shifts, vec![])
472 }
473
474 fn constrain_general<B, FromR, MulByScalar, IFromR>(
475 b: &mut B,
476 up: TraceRow<B::Expr>,
477 down: TraceRow<B::Expr>,
478 from_ref: FromR,
479 mbs: MulByScalar,
480 ideal_from_ref: IFromR,
481 ) where
482 B: ConstraintBuilder,
483 FromR: Fn(&Self::Scalar) -> B::Expr,
484 MulByScalar: Fn(&B::Expr, &Self::Scalar) -> Option<B::Expr>,
485 IFromR: Fn(&Self::Ideal) -> B::Ideal,
486 {
487 BigLinearUair::<R>::constrain_general(b, up, down, from_ref, mbs, ideal_from_ref)
488 }
489}
490
491impl<R> GenerateRandomTrace<32> for BigLinearUairWithPublicInput<R>
492where
493 R: ConstSemiring + From<u32> + 'static,
494{
495 type PolyCoeff = <BigLinearUair<R> as GenerateRandomTrace<32>>::PolyCoeff;
496 type Int = <BigLinearUair<R> as GenerateRandomTrace<32>>::Int;
497
498 fn generate_random_trace<Rng: RngCore + ?Sized>(
499 num_vars: usize,
500 rng: &mut Rng,
501 ) -> UairTrace<'static, Self::PolyCoeff, Self::Int, 32> {
502 BigLinearUair::<R>::generate_random_trace(num_vars, rng)
503 }
504}
505
506#[derive(Clone, Debug)]
521pub struct ShaProxy<R>(PhantomData<R>);
522
523impl<R> Uair for ShaProxy<R>
524where
525 R: ConstSemiring + From<u32> + 'static,
526{
527 type Ideal = DegreeOneIdeal<R>;
528 type Scalar = DensePolynomial<R, 32>;
529
530 fn signature() -> UairSignature {
531 let total = TotalColumnLayout::new(14, 0, 4);
533 let shifts = vec![ShiftSpec::new(0, 1), ShiftSpec::new(4, 4)];
536 UairSignature::new(total, PublicColumnLayout::default(), shifts, vec![])
537 }
538
539 fn constrain_general<B, FromR, MulByScalar, IFromR>(
540 b: &mut B,
541 up: TraceRow<B::Expr>,
542 down: TraceRow<B::Expr>,
543 _from_ref: FromR,
544 mbs: MulByScalar,
545 ideal_from_ref: IFromR,
546 ) where
547 B: ConstraintBuilder,
548 FromR: Fn(&Self::Scalar) -> B::Expr,
549 MulByScalar: Fn(&B::Expr, &Self::Scalar) -> Option<B::Expr>,
550 IFromR: Fn(&Self::Ideal) -> B::Ideal,
551 {
552 let one_ideal = ideal_from_ref(&DegreeOneIdeal::new(R::ONE));
553 let two_ideal = ideal_from_ref(&DegreeOneIdeal::new(R::from(2)));
554 let x_scalar = DensePolynomial::<R, 32>::new([R::ZERO, R::from(1)]);
556
557 b.assert_in_ideal(
563 down.binary_poly[0].clone()
564 - &up.binary_poly[1]
565 - &up.binary_poly[2]
566 - &up.binary_poly[3]
567 - &up.int[0]
568 - &up.int[1]
569 - &up.int[2],
570 &two_ideal,
571 );
572
573 b.assert_in_ideal(
575 down.binary_poly[1].clone()
576 - &up.binary_poly[5]
577 - &up.binary_poly[6]
578 - &up.binary_poly[7]
579 - &up.int[1]
580 - &up.int[2]
581 - &up.int[3],
582 &two_ideal,
583 );
584
585 b.assert_in_ideal(up.binary_poly[8].clone() - &up.int[0], &two_ideal);
587
588 b.assert_in_ideal(up.binary_poly[9].clone() - &up.int[1], &two_ideal);
590
591 b.assert_in_ideal(
593 up.binary_poly[10].clone()
594 - &mbs(&up.binary_poly[11], &x_scalar).expect("mul-by-X overflow"),
595 &one_ideal,
596 );
597
598 b.assert_in_ideal(
600 up.binary_poly[12].clone()
601 - &mbs(&up.binary_poly[13], &x_scalar).expect("mul-by-X overflow"),
602 &one_ideal,
603 );
604 }
605}
606
607impl<R> GenerateRandomTrace<32> for ShaProxy<R>
608where
609 R: ConstSemiring + From<u32> + 'static,
610{
611 type PolyCoeff = R;
612 type Int = R;
613
614 #[allow(clippy::needless_range_loop)]
615 fn generate_random_trace<Rng: rand::RngCore + ?Sized>(
616 num_vars: usize,
617 rng: &mut Rng,
618 ) -> UairTrace<'static, R, R, 32> {
619 fn random_binary_poly_with_popcount(
621 popcount: u32,
622 rng: &mut (impl rand::RngCore + ?Sized),
623 ) -> BinaryPoly<32> {
624 let mut positions: [u8; 32] =
625 core::array::from_fn(|i| u8::try_from(i).expect("can't fail"));
626 for i in 0..popcount as usize {
627 let j = i + rng.next_u32() as usize % (32 - i);
628 positions.swap(i, j);
629 }
630 let mut value: u32 = 0;
631 for &pos in &positions[..popcount as usize] {
632 value |= 1_u32 << pos;
633 }
634 BinaryPoly::from(value)
635 }
636
637 const SMALL_MASK: u32 = (1 << 28) - 1;
642 const INT_MAX_EXCL: u32 = 32;
644
645 let len = 1 << num_vars;
646
647 let mut bp_cols: Vec<DenseMultilinearExtension<BinaryPoly<32>>> =
648 vec![(0..len).map(|_| BinaryPoly::zero()).collect(); 14];
649 let mut int_cols: Vec<DenseMultilinearExtension<R>> =
650 vec![(0..len).map(|_| R::ZERO).collect(); 4];
651
652 bp_cols[0][0] = BinaryPoly::from(rng.next_u32() & SMALL_MASK);
659 for k in 0..4.min(len) {
660 bp_cols[4][k] = BinaryPoly::from(rng.next_u32() & SMALL_MASK);
661 }
662
663 for i in 0..len {
664 for k in 1..=3 {
666 bp_cols[k][i] = BinaryPoly::from(rng.next_u32() & SMALL_MASK);
667 }
668
669 let c2_target_oob = i + 4 >= len;
678
679 for k in 5..=7 {
680 bp_cols[k][i] = if c2_target_oob {
681 BinaryPoly::zero()
682 } else {
683 BinaryPoly::from(rng.next_u32() & SMALL_MASK)
684 };
685 }
686
687 let int_vals: [u32; 4] = if c2_target_oob {
691 [rng.next_u32() % INT_MAX_EXCL, 0, 0, 0]
692 } else {
693 [
694 rng.next_u32() % INT_MAX_EXCL,
695 rng.next_u32() % INT_MAX_EXCL,
696 rng.next_u32() % INT_MAX_EXCL,
697 rng.next_u32() % INT_MAX_EXCL,
698 ]
699 };
700 for (k, v) in int_vals.iter().enumerate() {
701 int_cols[k][i] = R::from(*v);
702 }
703
704 bp_cols[8][i] = BinaryPoly::from(int_vals[0]);
709 bp_cols[9][i] = BinaryPoly::from(int_vals[1]);
710
711 let bp11: BinaryPoly<32> = rng.random();
713 let popcount11 = bp11
714 .evaluate_at_point(&1_u32)
715 .expect("popcount eval should fit in u32");
716 bp_cols[11][i] = bp11;
717 bp_cols[10][i] = random_binary_poly_with_popcount(popcount11, rng);
718
719 let bp13: BinaryPoly<32> = rng.random();
721 let popcount13 = bp13
722 .evaluate_at_point(&1_u32)
723 .expect("popcount eval should fit in u32");
724 bp_cols[13][i] = bp13;
725 bp_cols[12][i] = random_binary_poly_with_popcount(popcount13, rng);
726
727 let eval_at_2 = |bp: &BinaryPoly<32>| -> u32 {
733 bp.evaluate_at_point(&2_u32)
734 .expect("28-bit binary poly eval at 2 fits in u32")
735 };
736
737 if i + 1 < len {
739 let s1 = eval_at_2(&bp_cols[1][i])
740 + eval_at_2(&bp_cols[2][i])
741 + eval_at_2(&bp_cols[3][i])
742 + int_vals[0]
743 + int_vals[1]
744 + int_vals[2];
745 bp_cols[0][i + 1] = BinaryPoly::from(s1);
746 }
747
748 if i + 4 < len {
750 let s2 = eval_at_2(&bp_cols[5][i])
751 + eval_at_2(&bp_cols[6][i])
752 + eval_at_2(&bp_cols[7][i])
753 + int_vals[1]
754 + int_vals[2]
755 + int_vals[3];
756 bp_cols[4][i + 4] = BinaryPoly::from(s2);
757 }
758 }
759
760 UairTrace {
761 binary_poly: bp_cols.into(),
762 arbitrary_poly: vec![].into(),
763 int: int_cols.into(),
764 }
765 }
766}
767
768#[derive(Clone, Debug)]
772pub struct TestUairMixedShifts<R>(PhantomData<R>);
773
774impl<R> Uair for TestUairMixedShifts<R>
775where
776 R: Semiring + 'static,
777{
778 type Ideal = ImpossibleIdeal;
779 type Scalar = DensePolynomial<R, 32>;
780
781 fn signature() -> UairSignature {
782 let total = TotalColumnLayout::new(0, 3, 0);
783 let shifts = vec![
784 ShiftSpec::new(0, 1), ShiftSpec::new(1, 2), ];
787 UairSignature::new(total, PublicColumnLayout::default(), shifts, vec![])
788 }
789
790 fn constrain_general<B, FromR, MulByScalar, IFromR>(
794 builder: &mut B,
795 up: TraceRow<B::Expr>,
796 down: TraceRow<B::Expr>,
797 _from_ref: FromR,
798 _mbs: MulByScalar,
799 _ideal_from_ref: IFromR,
800 ) where
801 B: ConstraintBuilder,
802 {
803 let up = up.arbitrary_poly;
804 let down = down.arbitrary_poly;
805
806 builder.assert_zero(down[0].clone() - &up[0] - &up[1]);
807 builder.assert_zero(up[2].clone() - &down[1]);
808 }
809}
810
811impl<R> GenerateRandomTrace<32> for TestUairMixedShifts<R>
812where
813 R: FixedSemiring + From<i8> + 'static,
814 StandardUniform: Distribution<R>,
815{
816 type PolyCoeff = R;
817 type Int = R;
818
819 fn generate_random_trace<Rng: rand::RngCore + ?Sized>(
821 num_vars: usize,
822 rng: &mut Rng,
823 ) -> UairTrace<'static, R, R, 32> {
824 let n = 1 << num_vars;
825
826 let b_col: Vec<DynamicPolynomialFS<R>> = (0..n)
828 .map(|_| DynamicPolynomialFS::new(vec![R::from(rng.random::<i8>())]))
829 .collect();
830
831 let mut a_col: Vec<DynamicPolynomialFS<R>> =
833 vec![DynamicPolynomialFS::new(vec![R::from(rng.random::<i8>())])];
834 for i in 0..n - 1 {
835 a_col.push(a_col[i].clone() + &b_col[i]);
836 }
837
838 let mut c_col: Vec<DynamicPolynomialFS<R>> = Vec::with_capacity(n);
840 for i in 0..n {
841 if i + 2 < n {
842 c_col.push(b_col[i + 2].clone());
843 } else {
844 c_col.push(DynamicPolynomialFS::zero());
845 }
846 }
847
848 let to_mle = |col: Vec<DynamicPolynomialFS<R>>| -> DenseMultilinearExtension<DensePolynomial<R, 32>> {
849 col.into_iter()
850 .map(|x| DensePolynomial::new(x.coeffs))
851 .collect()
852 };
853
854 UairTrace {
855 arbitrary_poly: vec![to_mle(a_col), to_mle(b_col), to_mle(c_col)].into(),
856 ..Default::default()
857 }
858 }
859}
860
861#[cfg(test)]
862mod tests {
863 use crypto_primitives::crypto_bigint_int::Int;
864 use zinc_uair::{
865 collect_scalars::collect_scalars,
866 constraint_counter::count_constraints,
867 degree_counter::{count_constraint_degrees, count_max_degree},
868 };
869
870 use super::*;
871
872 const LIMBS: usize = 4;
873
874 #[test]
875 fn test_constraint_degrees() {
876 fn assert_uair_shape<U: Uair>(expected_degrees: &[usize]) {
877 assert_eq!(count_constraints::<U>(), expected_degrees.len());
878 assert_eq!(count_constraint_degrees::<U>(), expected_degrees);
879 assert_eq!(
880 count_max_degree::<U>(),
881 *expected_degrees.iter().max().unwrap()
882 );
883 }
884
885 assert_uair_shape::<TestUairSimpleMultiplication<Int<LIMBS>>>(&[2, 2, 2]);
886 assert_uair_shape::<TestUairNoMultiplication<Int<LIMBS>>>(&[1]);
887 assert_uair_shape::<TestUairScalarMultiplications<Int<LIMBS>>>(&[1]);
888 assert_uair_shape::<BinaryDecompositionUair<u32>>(&[1]);
889 assert_uair_shape::<BigLinearUair<u32>>(&[1; 17]);
890 assert_uair_shape::<TestUairMixedShifts<Int<LIMBS>>>(&[1, 1]);
891 }
892
893 #[test]
894 fn test_air_scalar_multiplications_correct_collect_scalars() {
895 assert_eq!(
896 collect_scalars::<TestUairScalarMultiplications<Int<LIMBS>>>(),
897 (vec![
898 DensePolynomial::new([Int::from_i8(-1), Int::from_i8(0), Int::from_i8(1)]),
899 DensePolynomial::new([
900 Int::from_i8(1),
901 Int::from_i8(2),
902 Int::from_i8(3),
903 Int::from_i8(4),
904 ])
905 ]
906 .into_iter()
907 .collect())
908 );
909 }
910}