1use std::rc::Rc;
2
3use ark_ff::{Field, One};
4use ark_poly::Radix2EvaluationDomain;
5use kimchi::{
6 circuits::expr::RowOffset,
7 curve::KimchiCurve,
8 proof::{PointEvaluations, ProofEvaluations},
9};
10use mina_curves::pasta::{Fp, Fq};
11
12use crate::{
13 proofs::{
14 field::{field, Boolean, FieldWitness},
15 public_input::plonk_checks::scalars::MinimalForScalar,
16 step::step_verifier::PlonkDomain,
17 to_field_elements::ToFieldElements,
18 witness::Witness,
19 wrap::{wrap_verifier::PlonkWithField, AllFeatureFlags},
20 },
21 scan_state::transaction_logic::local_state::LazyValue,
22};
23
24#[derive(Clone, Debug)]
25pub struct PlonkMinimal<F: FieldWitness, const NLIMB: usize = 2> {
26 pub alpha: F,
27 pub beta: F,
28 pub gamma: F,
29 pub zeta: F,
30 pub joint_combiner: Option<F>,
31 pub alpha_bytes: [u64; NLIMB],
32 pub beta_bytes: [u64; NLIMB],
33 pub gamma_bytes: [u64; NLIMB],
34 pub zeta_bytes: [u64; NLIMB],
35 pub joint_combiner_bytes: Option<[u64; NLIMB]>,
36 pub feature_flags: crate::proofs::step::FeatureFlags<bool>,
37}
38
39type TwoFields = [Fp; 2];
40
41pub struct ScalarsEnv<F: FieldWitness> {
42 pub zk_polynomial: F,
43 pub zeta_to_n_minus_1: F,
44 pub srs_length_log2: u64,
45 pub domain: Rc<dyn PlonkDomain<F>>,
46 pub omega_to_minus_zk_rows: F,
47 pub feature_flags: Option<AllFeatureFlags<F>>,
48 pub unnormalized_lagrange_basis: Option<Box<dyn Fn(RowOffset, &mut Witness<F>) -> F>>,
49 pub vanishes_on_zero_knowledge_and_previous_rows: F,
50 pub zeta_to_srs_length: LazyValue<F, Witness<F>>,
51}
52
53#[derive(Debug)]
55pub struct InCircuit<F: FieldWitness> {
56 pub alpha: F,
57 pub beta: F,
58 pub gamma: F,
59 pub zeta: F,
60 pub zeta_to_domain_size: F::Shifting,
61 pub zeta_to_srs_length: F::Shifting,
62 pub perm: F::Shifting,
63 pub lookup: Option<F>,
64 pub feature_flags: crate::proofs::step::FeatureFlags<bool>,
65}
66
67pub trait ShiftingValue<F: Field> {
68 type MyShift;
69 fn shift() -> Self::MyShift;
70 fn of_field(field: F) -> Self;
71 fn shifted_to_field(&self) -> F;
72 fn shifted_raw(&self) -> F;
73 fn of_raw(shifted: F) -> Self;
74}
75
76impl ShiftingValue<Fp> for ShiftedValue<Fp> {
77 type MyShift = Shift<Fp>;
78
79 fn shift() -> Self::MyShift {
80 type MyShift = Shift<Fp>;
81
82 cache_one! {MyShift, {
83 let c = (0..255).fold(Fp::one(), |accum, _| accum + accum) + Fp::one();
84
85 let scale: Fp = 2.into();
86 let scale = scale.inverse().unwrap();
87
88 Shift { c, scale }
89 }}
90 }
91
92 fn of_field(field: Fp) -> Self {
93 let shift = Self::shift();
94 Self {
95 shifted: (field - shift.c) * shift.scale,
96 }
97 }
98
99 fn shifted_to_field(&self) -> Fp {
100 let shift = Self::shift();
101 self.shifted + self.shifted + shift.c
102 }
103
104 fn shifted_raw(&self) -> Fp {
105 self.shifted
106 }
107
108 fn of_raw(shifted: Fp) -> Self {
109 Self { shifted }
110 }
111}
112
113impl ShiftingValue<Fq> for ShiftedValue<Fq> {
114 type MyShift = ShiftFq;
115
116 fn shift() -> Self::MyShift {
117 cache_one! {ShiftFq, {
118 ShiftFq {
119 shift: (0..255).fold(Fq::one(), |accum, _| accum + accum),
120 }
121 }}
122 }
123
124 fn of_field(field: Fq) -> Self {
125 let shift = Self::shift();
126 Self {
127 shifted: field - shift.shift,
128 }
129 }
130
131 fn shifted_to_field(&self) -> Fq {
132 let shift = Self::shift();
133 self.shifted + shift.shift
134 }
135
136 fn shifted_raw(&self) -> Fq {
137 self.shifted
138 }
139
140 fn of_raw(shifted: Fq) -> Self {
141 Self { shifted }
142 }
143}
144
145#[derive(Clone, Debug)]
146pub struct ShiftFq {
147 shift: Fq,
148}
149
150#[derive(Clone, Debug)]
151pub struct Shift<F: Field> {
152 c: F,
153 scale: F,
154}
155
156impl<F> Shift<F>
157where
158 F: Field + From<i32>,
159{
160 pub fn create() -> Self {
162 let c = (0..255).fold(F::one(), |accum, _| accum + accum) + F::one();
163
164 let scale: F = 2.into();
165 let scale = scale.inverse().unwrap();
166
167 Self { c, scale } }
169}
170
171#[derive(Clone, Debug)]
172pub struct ShiftedValue<F: Field> {
173 pub shifted: F,
174}
175
176impl<F: FieldWitness, F2: FieldWitness + ToFieldElements<F>> ToFieldElements<F>
189 for ShiftedValue<F2>
190{
191 fn to_field_elements(&self, fields: &mut Vec<F>) {
192 let Self { shifted } = self;
193 shifted.to_field_elements(fields);
194 }
195}
196
197impl<F> ShiftedValue<F>
198where
199 F: Field,
200{
201 pub fn new(field: F) -> Self {
203 Self { shifted: field }
204 }
205
206 }
219
220pub const PERM_ALPHA0: usize = 21;
222
223pub const NPOWERS_OF_ALPHA: usize = PERM_ALPHA0 + 3;
224
225pub fn powers_of_alpha<F: FieldWitness>(alpha: F) -> Box<[F; NPOWERS_OF_ALPHA]> {
227 let mut alphas = Box::new([F::one(); NPOWERS_OF_ALPHA]);
229
230 alphas[1] = alpha;
231 for i in 2..alphas.len() {
232 alphas[i] = alpha * alphas[i - 1];
233 }
234
235 alphas
236}
237
238pub fn derive_plonk<F: FieldWitness, const NLIMB: usize>(
239 env: &ScalarsEnv<F>,
240 evals: &ProofEvaluations<PointEvaluations<F>>,
241 minimal: &PlonkMinimal<F, NLIMB>,
242) -> InCircuit<F> {
243 let PlonkMinimal {
244 alpha,
245 beta,
246 gamma,
247 joint_combiner,
248 feature_flags: actual_feature_flags,
249 ..
250 } = minimal;
251
252 let zkp = env.zk_polynomial;
253 let powers_of_alpha = powers_of_alpha(*alpha);
254 let alpha_pow = |i: usize| powers_of_alpha[i];
255 let w0 = evals.w.map(|point| point.zeta);
256
257 let beta = *beta;
258 let gamma = *gamma;
259
260 let perm = evals.s.iter().enumerate().fold(
262 evals.z.zeta_omega * beta * alpha_pow(PERM_ALPHA0) * zkp,
263 |accum, (index, elem)| accum * (gamma + (beta * elem.zeta) + w0[index]),
264 );
265 let perm = -perm;
266
267 let zeta_to_domain_size = env.zeta_to_n_minus_1 + F::one();
268
269 let zeta_to_srs_length = {
270 let mut unused_w = Witness::empty();
271 *env.zeta_to_srs_length.get(&mut unused_w)
272 };
273
274 let shift = |f: F| F::Shifting::of_field(f);
276
277 InCircuit {
278 alpha: minimal.alpha,
279 beta: minimal.beta,
280 gamma: minimal.gamma,
281 zeta: minimal.zeta,
282 zeta_to_domain_size: shift(zeta_to_domain_size),
283 zeta_to_srs_length: shift(zeta_to_srs_length),
284 perm: shift(perm),
285 lookup: joint_combiner
286 .as_ref()
287 .map(|joint_combiner| *joint_combiner),
288 feature_flags: actual_feature_flags.clone(),
289 }
290}
291
292pub fn derive_plonk_checked<F: FieldWitness>(
294 env: &ScalarsEnv<F>,
295 evals: &ProofEvaluations<PointEvaluations<F>>,
296 minimal: &PlonkWithField<F>,
297 w: &mut Witness<F>,
298) -> InCircuit<F> {
299 let zkp = env.zk_polynomial;
302 let powers_of_alpha = powers_of_alpha(minimal.alpha);
303 let alpha_pow = |i: usize| powers_of_alpha[i];
304 let w0 = evals.w.map(|point| point.zeta);
305
306 let beta = minimal.beta;
307 let gamma = minimal.gamma;
308
309 let perm = evals.s.iter().enumerate().fold(
310 field::muls(&[evals.z.zeta_omega, beta, alpha_pow(PERM_ALPHA0), zkp], w),
311 |accum, (index, elem)| {
312 let beta_elem = field::mul(beta, elem.zeta, w);
314 field::mul(accum, gamma + beta_elem + w0[index], w)
315 },
316 );
317 let perm = -perm;
318
319 let zeta_to_domain_size = env.zeta_to_n_minus_1 + F::one();
320 let zeta_to_srs_length = *env.zeta_to_srs_length.get(w);
336
337 let shift = |f: F| F::Shifting::of_field(f);
339
340 InCircuit {
341 alpha: minimal.alpha,
342 beta: minimal.beta,
343 gamma: minimal.gamma,
344 zeta: minimal.zeta,
345 zeta_to_domain_size: shift(zeta_to_domain_size),
346 zeta_to_srs_length: shift(zeta_to_srs_length),
347 perm: shift(perm),
348 lookup: None,
349 feature_flags: crate::proofs::step::FeatureFlags::empty_bool(),
350 }
351}
352
353pub fn checked<F: FieldWitness>(
354 env: &ScalarsEnv<F>,
355 evals: &ProofEvaluations<PointEvaluations<F>>,
356 plonk: &PlonkWithField<F>,
357 w: &mut Witness<F>,
358) -> Boolean {
359 let actual = derive_plonk_checked(env, evals, plonk, w);
360
361 let list = [
362 field::equal(plonk.perm.shifted, actual.perm.shifted_raw(), w),
366 ];
367
368 Boolean::all(&list[..], w)
369}
370
371pub fn make_shifts<F: FieldWitness>(
372 domain: &Radix2EvaluationDomain<F>,
373) -> kimchi::circuits::polynomials::permutation::Shifts<F> {
374 kimchi::circuits::polynomials::permutation::Shifts::new(domain)
377}
378
379pub fn ft_eval0<F: FieldWitness, const NLIMB: usize>(
380 env: &ScalarsEnv<F>,
381 evals: &ProofEvaluations<PointEvaluations<F>>,
382 minimal: &PlonkMinimal<F, NLIMB>,
383 p_eval0: &[F],
384) -> F {
385 const PLONK_TYPES_PERMUTS_MINUS_1_N: usize = 6;
386
387 let e0_s: Vec<_> = evals.s.iter().map(|s| s.zeta).collect();
388 let zkp = env.zk_polynomial;
389 let powers_of_alpha = powers_of_alpha(minimal.alpha);
390 let alpha_pow = |i: usize| powers_of_alpha[i];
391
392 let zeta1m1 = env.zeta_to_n_minus_1;
393
394 let mut unused_w = Witness::empty();
395 let p_eval0 = p_eval0
396 .iter()
397 .copied()
398 .rfold(None, |acc, p_eval0| match acc {
399 None => Some(p_eval0),
400 Some(acc) => {
401 let zeta1 = *env.zeta_to_srs_length.get(&mut unused_w);
402 Some(p_eval0 + (zeta1 * acc))
403 }
404 })
405 .unwrap(); let w0: Vec<_> = evals.w.iter().map(|w| w.zeta).collect();
408
409 let ft_eval0 = {
410 let a0 = alpha_pow(PERM_ALPHA0);
411 let w_n = w0[PLONK_TYPES_PERMUTS_MINUS_1_N];
412 let init = (w_n + minimal.gamma) * evals.z.zeta_omega * a0 * zkp;
413 e0_s.iter().enumerate().fold(init, |acc, (i, s)| {
414 ((minimal.beta * s) + w0[i] + minimal.gamma) * acc
415 })
416 };
417
418 let shifts = env.domain.shifts();
419 let ft_eval0 = ft_eval0 - p_eval0;
420
421 let ft_eval0 = ft_eval0
422 - shifts.iter().enumerate().fold(
423 alpha_pow(PERM_ALPHA0) * zkp * evals.z.zeta,
424 |acc, (i, s)| acc * (minimal.gamma + (minimal.beta * minimal.zeta * s) + w0[i]),
425 );
426
427 let nominator =
428 (zeta1m1 * alpha_pow(PERM_ALPHA0 + 1) * (minimal.zeta - env.omega_to_minus_zk_rows)
429 + (zeta1m1 * alpha_pow(PERM_ALPHA0 + 2) * (minimal.zeta - F::one())))
430 * (F::one() - evals.z.zeta);
431
432 let denominator = (minimal.zeta - env.omega_to_minus_zk_rows) * (minimal.zeta - F::one());
433 let ft_eval0 = ft_eval0 + (nominator / denominator);
434
435 let minimal = MinimalForScalar {
436 alpha: minimal.alpha,
437 beta: minimal.beta,
438 gamma: minimal.gamma,
439 lookup: minimal.joint_combiner,
440 };
441 let mut w = Witness::empty();
442 let constant_term = scalars::compute(None, &minimal, evals, env, &mut w);
443
444 ft_eval0 - constant_term
445}
446
447fn get_feature_flag<F: FieldWitness>(
448 feature_flags: &AllFeatureFlags<F>,
449 feature: &kimchi::circuits::expr::FeatureFlag,
450 w: &mut Witness<F>,
451) -> Option<Boolean> {
452 use kimchi::circuits::{expr::FeatureFlag::*, lookup::lookups::LookupPattern};
453
454 match feature {
455 RangeCheck0 => Some(feature_flags.features.range_check0),
456 RangeCheck1 => Some(feature_flags.features.range_check1),
457 ForeignFieldAdd => Some(feature_flags.features.foreign_field_add),
458 ForeignFieldMul => Some(feature_flags.features.foreign_field_mul),
459 Xor => Some(feature_flags.features.xor),
460 Rot => Some(feature_flags.features.rot),
461 LookupTables => Some(*feature_flags.lookup_tables.get(w)),
462 RuntimeLookupTables => Some(feature_flags.features.runtime_tables),
463 TableWidth(3) => Some(*feature_flags.table_width_3.get(w)),
464 TableWidth(2) => Some(*feature_flags.table_width_at_least_2.get(w)),
465 TableWidth(i) if *i <= 1 => Some(*feature_flags.table_width_at_least_1.get(w)),
466 TableWidth(_) => None,
467 LookupsPerRow(4) => Some(*feature_flags.lookups_per_row_4.get(w)),
468 LookupsPerRow(i) if *i <= 3 => Some(*feature_flags.lookups_per_row_3.get(w)),
469 LookupsPerRow(_) => None,
470 LookupPattern(LookupPattern::Lookup) => Some(feature_flags.features.lookup),
471 LookupPattern(LookupPattern::Xor) => Some(*feature_flags.lookup_pattern_xor.get(w)),
472 LookupPattern(LookupPattern::RangeCheck) => {
473 Some(*feature_flags.lookup_pattern_range_check.get(w))
474 }
475 LookupPattern(LookupPattern::ForeignFieldMul) => {
476 Some(feature_flags.features.foreign_field_mul)
477 }
478 }
479}
480
481mod scalars {
482 use std::collections::BTreeMap;
483
484 use kimchi::{
485 circuits::{
486 berkeley_columns::{BerkeleyChallengeTerm, Column},
487 constraints::FeatureFlags,
488 expr::{
489 CacheId, ConstantExpr, ConstantExprInner, Constants, Expr, ExprError, ExprInner,
490 Operations, Variable,
491 },
492 gate::{CurrOrNext, GateType},
493 lookup::lookups::{LookupFeatures, LookupPatterns},
494 },
495 proof::PointEvaluations,
496 };
497
498 use crate::proofs::transaction::endos;
499
500 use super::*;
501
502 fn var_evaluate<F: FieldWitness>(
504 v: &Variable<Column>,
505 evals: &ProofEvaluations<PointEvaluations<F>>,
506 ) -> Result<F, ExprError<Column>> {
507 let point_evaluations = {
508 use kimchi::circuits::lookup::lookups::LookupPattern;
509 use Column::*;
510
511 match v.col {
512 Witness(i) => Ok(evals.w[i]),
513 Z => Ok(evals.z),
514 LookupSorted(i) => {
515 evals.lookup_sorted[i].ok_or(ExprError::MissingIndexEvaluation(v.col))
516 }
517 LookupAggreg => evals
518 .lookup_aggregation
519 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
520 LookupTable => evals
521 .lookup_table
522 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
523 LookupRuntimeTable => evals
524 .runtime_lookup_table
525 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
526 Index(GateType::Poseidon) => Ok(evals.poseidon_selector),
527 Index(GateType::Generic) => Ok(evals.generic_selector),
528 Index(GateType::CompleteAdd) => Ok(evals.complete_add_selector),
529 Index(GateType::VarBaseMul) => Ok(evals.mul_selector),
530 Index(GateType::EndoMul) => Ok(evals.emul_selector),
531 Index(GateType::EndoMulScalar) => Ok(evals.endomul_scalar_selector),
532 Index(GateType::RangeCheck0) => evals
533 .range_check0_selector
534 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
535 Index(GateType::RangeCheck1) => evals
536 .range_check1_selector
537 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
538 Index(GateType::ForeignFieldAdd) => evals
539 .foreign_field_add_selector
540 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
541 Index(GateType::ForeignFieldMul) => evals
542 .foreign_field_mul_selector
543 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
544 Index(GateType::Xor16) => evals
545 .xor_selector
546 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
547 Index(GateType::Rot64) => evals
548 .rot_selector
549 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
550 Permutation(i) => Ok(evals.s[i]),
551 Coefficient(i) => Ok(evals.coefficients[i]),
552 Column::LookupKindIndex(LookupPattern::Xor) => evals
553 .xor_lookup_selector
554 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
555 Column::LookupKindIndex(LookupPattern::Lookup) => evals
556 .lookup_gate_lookup_selector
557 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
558 Column::LookupKindIndex(LookupPattern::RangeCheck) => evals
559 .range_check_lookup_selector
560 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
561 Column::LookupKindIndex(LookupPattern::ForeignFieldMul) => evals
562 .foreign_field_mul_lookup_selector
563 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
564 Column::LookupRuntimeSelector => evals
565 .runtime_lookup_table_selector
566 .ok_or(ExprError::MissingIndexEvaluation(v.col)),
567 Index(_) => Err(ExprError::MissingIndexEvaluation(v.col)),
568 }
569 }?;
570 match v.row {
571 CurrOrNext::Curr => Ok(point_evaluations.zeta),
572 CurrOrNext::Next => Ok(point_evaluations.zeta_omega),
573 }
574 }
575
576 fn pow<F: FieldWitness>(x: F, n: u64, w: &mut Witness<F>) -> F {
577 if n == 0 {
578 F::one()
579 } else if n == 1 {
580 x
581 } else {
582 let y = pow(field::square(x, w), n / 2, w);
583 if n % 2 == 0 {
584 y
585 } else {
586 field::mul(x, y, w)
587 }
588 }
589 }
590
591 fn pow_const<F: FieldWitness>(x: F, n: u64) -> F {
592 if n == 0 {
593 F::one()
594 } else if n == 1 {
595 x
596 } else {
597 (0..n - 1).fold(x, |acc, _| x * acc)
598 }
599 }
600
601 pub struct EvalContext<'a, F: FieldWitness> {
602 pub evals: &'a ProofEvaluations<PointEvaluations<F>>,
603 pub constants: &'a Constants<F>,
604 pub cache: BTreeMap<CacheId, F>,
605 pub env: &'a ScalarsEnv<F>,
606 pub w: &'a mut Witness<F>,
607 pub alpha: F,
608 pub beta: F,
609 pub gamma: F,
610 pub joint_combiner: Option<F>,
611 }
612
613 fn is_const<F: FieldWitness>(e: &Expr<ConstantExpr<F, BerkeleyChallengeTerm>, Column>) -> bool {
615 match e {
616 Expr::Atom(ExprInner::Constant(Operations::Atom(ConstantExprInner::Constant(_)))) => {
617 true
618 }
619 Expr::Pow(x, _) => is_const(x),
620 _ => false,
621 }
622 }
623
624 pub fn eval<F: FieldWitness>(
625 e: &Expr<ConstantExpr<F, BerkeleyChallengeTerm>, Column>,
626 ctx: &mut EvalContext<F>,
627 ) -> F {
628 use Operations;
629 match e {
630 Operations::Atom(ExprInner::Cell(variable)) => {
631 var_evaluate(variable, ctx.evals).unwrap_or_else(|_| F::zero())
632 }
633 Operations::Atom(ExprInner::VanishesOnZeroKnowledgeAndPreviousRows) => {
634 ctx.env.vanishes_on_zero_knowledge_and_previous_rows
635 }
636 Operations::Atom(ExprInner::UnnormalizedLagrangeBasis(row_offset)) => {
637 let unnormalized_lagrange_basis =
638 ctx.env.unnormalized_lagrange_basis.as_ref().unwrap();
639 unnormalized_lagrange_basis(*row_offset, ctx.w)
640 }
641 Operations::Atom(ExprInner::Constant(c)) => sub_eval(c, ctx),
642 Operations::Pow(x, p) => {
643 let p = *p;
644 let v = eval(x, ctx);
645
646 if is_const(x) {
647 pow_const(v, p)
648 } else {
649 pow(v, p, ctx.w)
650 }
651 }
652 Operations::Add(x, y) => {
653 let y = eval(y, ctx);
654 let x = eval(x, ctx);
655 x + y
656 }
657 Operations::Mul(x, y) => {
658 let is_x_const = is_const(x);
659 let is_y_const = is_const(y);
660 let y = eval(y, ctx);
661 let x = eval(x, ctx);
662 if is_x_const || is_y_const {
663 x * y
664 } else {
665 field::mul(x, y, ctx.w)
666 }
667 }
668 Operations::Sub(x, y) => {
669 let y = eval(y, ctx);
670 let x = eval(x, ctx);
671 x - y
672 }
673 Operations::Double(x) => {
674 let v = eval(x, ctx);
675 v.double()
676 }
677 Operations::Square(x) => {
678 let is_x_const = is_const(x);
679 let x = eval(x, ctx);
680 if is_x_const {
681 x * x
682 } else {
683 field::mul(x, x, ctx.w)
684 }
685 }
686 Operations::Cache(id, _e) => ctx.cache.get(id).copied().unwrap(),
687 Operations::IfFeature(feature, e1, e2) => match ctx.env.feature_flags.as_ref() {
688 None => eval(e2, ctx),
689 Some(feature_flags) => {
690 let is_feature_enabled = match get_feature_flag(feature_flags, feature, ctx.w) {
691 None => return eval(e2, ctx),
692 Some(enabled) => enabled,
693 };
694
695 let on_false = eval(e2, ctx);
696 let on_true = eval(e1, ctx);
697
698 ctx.w.exists_no_check(match is_feature_enabled {
699 Boolean::True => on_true,
700 Boolean::False => on_false,
701 })
702 }
703 },
704 }
705 }
706
707 pub fn sub_eval<F: FieldWitness>(
716 e: &Operations<ConstantExprInner<F, BerkeleyChallengeTerm>>,
717 ctx: &mut EvalContext<F>,
718 ) -> F {
719 use Operations;
720 match e {
721 Operations::Atom(a) => match a {
722 ConstantExprInner::Challenge(term) => match term {
723 BerkeleyChallengeTerm::Alpha => ctx.alpha,
724 BerkeleyChallengeTerm::Beta => ctx.beta,
725 BerkeleyChallengeTerm::Gamma => ctx.gamma,
726 BerkeleyChallengeTerm::JointCombiner => ctx.joint_combiner.expect("Unexcepted"),
727 },
728 ConstantExprInner::Constant(constant_term) => match constant_term {
729 kimchi::circuits::expr::ConstantTerm::EndoCoefficient => {
730 ctx.constants.endo_coefficient
731 }
732 kimchi::circuits::expr::ConstantTerm::Mds { row, col } => {
733 ctx.constants.mds[*row][*col]
734 }
735 kimchi::circuits::expr::ConstantTerm::Literal(literal) => *literal,
736 },
737 },
738 Operations::Pow(x, p) => {
739 let p = *p;
740 let v = sub_eval(x, ctx);
741
742 pow(v, p, ctx.w)
743 }
744 Operations::Add(x, y) => {
745 let y = sub_eval(y, ctx);
746 let x = sub_eval(x, ctx);
747 x + y
748 }
749 Operations::Mul(x, y) => {
750 let y = sub_eval(y, ctx);
751 let x = sub_eval(x, ctx);
752 field::mul(x, y, ctx.w)
753 }
754 Operations::Sub(x, y) => {
755 let x = sub_eval(x, ctx);
756 let y = sub_eval(y, ctx);
757 x - y
758 }
759 Operations::Double(x) => {
760 let x = sub_eval(x, ctx);
761 x.double()
762 }
763 Operations::Square(x) => {
764 let x = sub_eval(x, ctx);
765 field::mul(x, x, ctx.w)
766 }
767 Operations::Cache(id, _e) => ctx.cache.get(id).copied().unwrap(),
768 Operations::IfFeature(feature, e1, e2) => match ctx.env.feature_flags.as_ref() {
769 None => sub_eval(e2, ctx),
770 Some(feature_flags) => {
771 let is_feature_enabled = match get_feature_flag(feature_flags, feature, ctx.w) {
772 None => return sub_eval(e2, ctx),
773 Some(enabled) => enabled,
774 };
775
776 let on_false = sub_eval(e2, ctx);
777 let on_true = sub_eval(e1, ctx);
778
779 ctx.w.exists_no_check(match is_feature_enabled {
780 Boolean::True => on_true,
781 Boolean::False => on_false,
782 })
783 }
784 },
785 }
786 }
787
788 #[derive(Default)]
789 pub struct Cached<F: FieldWitness> {
790 expr: BTreeMap<
792 CacheId,
793 (
794 Box<Cached<F>>,
795 Box<Expr<ConstantExpr<F, BerkeleyChallengeTerm>, Column>>,
796 ),
797 >,
798 }
799
800 #[inline(never)]
801 pub fn extract_caches<F: FieldWitness>(
802 e: &Expr<ConstantExpr<F, BerkeleyChallengeTerm>, Column>,
803 cache: &mut Cached<F>,
804 ) {
805 match e {
806 Operations::Atom(_) => {}
807 Operations::Pow(x, _) => extract_caches(x, cache),
808 Operations::Add(x, y) | Operations::Mul(x, y) | Operations::Sub(x, y) => {
809 extract_caches(y, cache);
810 extract_caches(x, cache);
811 }
812 Operations::Double(x) => extract_caches(x, cache),
813 Operations::Square(x) => extract_caches(x, cache),
814 Operations::Cache(id, e) => {
815 let mut cached = Cached::default();
816 extract_caches(e, &mut cached);
817 cache.expr.insert(*id, (Box::new(cached), e.clone()));
818 }
819 Operations::IfFeature(_, e1, e2) => {
820 if false {
821 extract_caches(e1, cache)
822 } else {
823 extract_caches(e2, cache)
824 }
825 }
826 }
827 }
828
829 fn eval_cache<F: FieldWitness>(cached_exprs: &Cached<F>, ctx: &mut EvalContext<F>) {
830 for (id, (cache, expr)) in &cached_exprs.expr {
832 let mut old_cache = std::mem::take(&mut ctx.cache);
833 eval_cache::<F>(cache, ctx);
834 old_cache.insert(*id, eval::<F>(expr, ctx));
835 ctx.cache = old_cache;
836 }
837 }
838
839 pub struct MinimalForScalar<F> {
840 pub alpha: F,
841 pub beta: F,
842 pub gamma: F,
843 pub lookup: Option<F>,
844 }
845
846 pub fn compute<F: FieldWitness>(
847 gate: Option<GateType>,
848 minimal: &MinimalForScalar<F>,
849 evals: &ProofEvaluations<PointEvaluations<F>>,
850 env: &ScalarsEnv<F>,
851 w: &mut Witness<F>,
852 ) -> F {
853 let (constant_term, index_terms) = &*{
854 type TermsMap<F> =
855 BTreeMap<Column, Expr<ConstantExpr<F, BerkeleyChallengeTerm>, Column>>;
856 type Const<F> = Expr<ConstantExpr<F, BerkeleyChallengeTerm>, Column>;
857 type Terms<F> = Rc<(Const<F>, TermsMap<F>)>;
858 cache! {
859 Terms::<F>, {
860 let is_fp = std::any::TypeId::of::<F>() == std::any::TypeId::of::<Fp>();
863
864 let features = if is_fp {
865 None
866 } else {
867 Some(FeatureFlags {
868 range_check0: false,
869 range_check1: false,
870 foreign_field_add: false,
871 foreign_field_mul: false,
872 xor: false,
873 rot: false,
874 lookup_features: LookupFeatures {
875 patterns: LookupPatterns {
876 xor: false,
877 lookup: false,
878 range_check: false,
879 foreign_field_mul: false,
880 },
881 joint_lookup_used: false,
882 uses_runtime_tables: false,
883 },
884 })
885 };
886
887 let evaluated_cols =
888 kimchi::linearization::linearization_columns::<F>(features.as_ref());
889 let (linearization, _powers_of_alpha) =
890 kimchi::linearization::constraints_expr::<F>(features.as_ref(), true);
891
892 let kimchi::circuits::expr::Linearization {
893 constant_term,
894 index_terms,
895 } = linearization.linearize(evaluated_cols).unwrap();
896
897 let index_terms = index_terms.into_iter().collect::<TermsMap<F>>();
898 Rc::new((constant_term, index_terms))
899 }
900 }
901 };
902
903 let constants = kimchi::circuits::expr::Constants::<F> {
905 endo_coefficient: {
906 let (base, _) = endos::<F>();
907 base
908 },
909 mds: &<F::OtherCurve>::sponge_params().mds,
910 zk_rows: 3,
911 };
912
913 let mut ctx = EvalContext {
914 evals,
915 constants: &constants,
916 cache: BTreeMap::new(),
917 env,
918 w,
919 alpha: minimal.alpha,
920 beta: minimal.beta,
921 gamma: minimal.gamma,
922 joint_combiner: minimal.lookup,
923 };
924
925 let term = match gate {
926 Some(gate) => index_terms.get(&Column::Index(gate)).unwrap(),
927 None => constant_term,
928 };
929
930 let mut cached_exprs = Cached::default();
932 extract_caches(term, &mut cached_exprs);
933 eval_cache(&cached_exprs, &mut ctx);
934
935 eval(term, &mut ctx)
937 }
938}
939
940pub fn ft_eval0_checked<F: FieldWitness, const NLIMB: usize>(
942 env: &ScalarsEnv<F>,
943 evals: &ProofEvaluations<PointEvaluations<F>>,
944 minimal: &PlonkMinimal<F, NLIMB>,
945 lookup: Option<F>,
946 p_eval0: &[F],
947 w: &mut Witness<F>,
948) -> F {
949 const PLONK_TYPES_PERMUTS_MINUS_1_N: usize = 6;
950
951 let e0_s: Vec<_> = evals.s.iter().map(|s| s.zeta).collect();
952 let zkp = env.zk_polynomial;
953 let powers_of_alpha = powers_of_alpha(minimal.alpha);
954 let alpha_pow = |i: usize| powers_of_alpha[i];
955
956 let zeta1m1 = env.zeta_to_n_minus_1;
957 let p_eval0 = p_eval0
958 .iter()
959 .copied()
960 .rfold(None, |acc, p_eval0| match acc {
961 None => Some(p_eval0),
962 Some(acc) => {
963 let zeta1 = *env.zeta_to_srs_length.get(w);
964 Some(p_eval0 + field::mul(zeta1, acc, w))
965 }
966 })
967 .unwrap(); let w0: Vec<_> = evals.w.iter().map(|w| w.zeta).collect();
969
970 let ft_eval0 = {
971 let a0 = alpha_pow(PERM_ALPHA0);
972 let w_n = w0[PLONK_TYPES_PERMUTS_MINUS_1_N];
973 let init = field::muls(&[(w_n + minimal.gamma), evals.z.zeta_omega, a0, zkp], w);
974 e0_s.iter().enumerate().fold(init, |acc, (i, s)| {
975 let beta_s = field::mul(minimal.beta, *s, w);
977 field::mul(beta_s + w0[i] + minimal.gamma, acc, w)
978 })
979 };
980
981 let shifts = env.domain.shifts();
982 let ft_eval0 = ft_eval0 - p_eval0;
983
984 let ft_eval0 = ft_eval0
985 - shifts.iter().enumerate().fold(
986 field::muls(&[alpha_pow(PERM_ALPHA0), zkp, evals.z.zeta], w),
987 |acc, (i, s)| {
988 let beta_zeta = field::mul(minimal.beta, minimal.zeta, w);
989 field::mul(acc, minimal.gamma + (beta_zeta * s) + w0[i], w)
990 },
991 );
992
993 let a = field::muls(
995 &[
996 zeta1m1,
997 alpha_pow(PERM_ALPHA0 + 2),
998 (minimal.zeta - F::one()),
999 ],
1000 w,
1001 );
1002 let b = field::muls(
1003 &[
1004 zeta1m1,
1005 alpha_pow(PERM_ALPHA0 + 1),
1006 (minimal.zeta - env.omega_to_minus_zk_rows),
1007 ],
1008 w,
1009 );
1010 let nominator = field::mul(a + b, F::one() - evals.z.zeta, w);
1011
1012 let denominator = field::mul(
1013 minimal.zeta - env.omega_to_minus_zk_rows,
1014 minimal.zeta - F::one(),
1015 w,
1016 );
1017 let ft_eval0 = ft_eval0 + field::div_by_inv(nominator, denominator, w);
1018
1019 let minimal = MinimalForScalar {
1020 alpha: minimal.alpha,
1021 beta: minimal.beta,
1022 gamma: minimal.gamma,
1023 lookup,
1024 };
1025 let constant_term = scalars::compute(None, &minimal, evals, env, w);
1026 ft_eval0 - constant_term
1027}
1028
1029#[cfg(test)]
1030mod tests {
1031 use std::str::FromStr;
1032
1033 use crate::FpExt;
1034
1035 use super::*;
1036
1037 #[cfg(target_family = "wasm")]
1038 use wasm_bindgen_test::wasm_bindgen_test as test;
1039
1040 #[test]
1268 fn test_alphas() {
1269 let n = Fp::from_str(
1270 "27274897876953793245985525242013286410205575357216365244783619058623821516088",
1271 )
1272 .unwrap();
1273
1274 let alphas: Box<[Fp; NPOWERS_OF_ALPHA]> = powers_of_alpha(n);
1275 let alphas_str: Vec<String> = alphas.iter().map(|f| f.to_decimal()).collect();
1276
1277 const OCAML_RESULT: &[&str] = &[
1278 "1",
1279 "27274897876953793245985525242013286410205575357216365244783619058623821516088",
1280 "5856243499679297994261942705106783326584825647279332525318074626467168425175",
1281 "26908526253468636093650206549302380737071523922183255477383956748755441012366",
1282 "21276200075660690362913766498168565850417909161152737384646582509540496229450",
1283 "3843731251681147173193384676587074004662025496739119332721571982426684387560",
1284 "12392606098341916760701161625583524765199435768082801118718099569066567820086",
1285 "5932489972119399045562481763112253944218195162891420406370178296693572483896",
1286 "1375846522483390900802414356841133463956126287864007136159978297384640659584",
1287 "5356524575738460513076981906288272723856440519543693179836517878630162813220",
1288 "23319398249603527452857836680743813857193813763032214190196631633251915644825",
1289 "10921184148344839491052929288136821627436657352065581423854521247501001908351",
1290 "13053560967285308651226207033123539702290413328361716005386653453569329750313",
1291 "8298101552564684053050013414211292674866114224797784754887740268228151928335",
1292 "715072795965317694491886715913315968459520650830405802156784401283709943505",
1293 "25198551493059869063561311792478884528738012039746184861146867788131566740666",
1294 "27161703551928606962685117055547438689494792119791879693135179256422752270728",
1295 "28799358614011589987311924793640447939591189984280570017428244220659375622447",
1296 "4488279652568453906961591843014473441709515392753701104095475657832824041646",
1297 "4641946865609115816676535679719511429699894348223929677606063307711524129548",
1298 "995093492640264169583875280706844374785298168266651011740457078469635678163",
1299 "17429526728376789811772110265115435172515921536052154380599101096979177652072",
1300 "22850194394147425267881995863659224768162140900074856912248813188202263996579",
1301 "26770317988143355422138083990683491489315652370177339626307684951664128480053",
1302 ];
1303
1304 assert_eq!(alphas_str, OCAML_RESULT);
1305 }
1306}