mina_tree/proofs/public_input/
plonk_checks.rs

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// Result of `plonk_derive`
54#[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    /// <https://github.com/MinaProtocol/mina/blob/0b63498e271575dbffe2b31f3ab8be293490b1ac/src/lib/pickles_types/shifted_value.ml#L121>
161    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 } // TODO: This can be a constant
168    }
169}
170
171#[derive(Clone, Debug)]
172pub struct ShiftedValue<F: Field> {
173    pub shifted: F,
174}
175
176// impl<F: Field + FpExt> std::fmt::Debug for ShiftedValue<F> {
177//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178//         f.debug_struct("ShiftedValue")
179//             .field("shifted", &{
180//                 let mut bytes = self.shifted.to_bytes();
181//                 bytes.reverse();
182//                 hex::encode(bytes)
183//             })
184//             .finish()
185//     }
186// }
187
188impl<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    /// Creates without shifting
202    pub fn new(field: F) -> Self {
203        Self { shifted: field }
204    }
205
206    // /// <https://github.com/MinaProtocol/mina/blob/0b63498e271575dbffe2b31f3ab8be293490b1ac/src/lib/pickles_types/shifted_value.ml#L127>
207    // pub fn of_field(field: F, shift: &Shift<F>) -> Self {
208    //     Self {
209    //         shifted: (field - shift.c) * shift.scale,
210    //     }
211    // }
212
213    // /// <https://github.com/MinaProtocol/mina/blob/0b63498e271575dbffe2b31f3ab8be293490b1ac/src/lib/pickles_types/shifted_value.ml#L131>
214    // #[allow(unused)]
215    // pub fn to_field(&self, shift: &Shift<F>) -> F {
216    //     self.shifted + self.shifted + shift.c
217    // }
218}
219
220/// <https://github.com/MinaProtocol/mina/blob/0b63498e271575dbffe2b31f3ab8be293490b1ac/src/lib/pickles/plonk_checks/plonk_checks.ml#L218>
221pub const PERM_ALPHA0: usize = 21;
222
223pub const NPOWERS_OF_ALPHA: usize = PERM_ALPHA0 + 3;
224
225/// <https://github.com/MinaProtocol/mina/blob/0b63498e271575dbffe2b31f3ab8be293490b1ac/src/lib/pickles/plonk_checks/plonk_checks.ml#L141>
226pub fn powers_of_alpha<F: FieldWitness>(alpha: F) -> Box<[F; NPOWERS_OF_ALPHA]> {
227    // The OCaml code computes until alpha^71, but we don't need that much here
228    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    // <https://github.com/MinaProtocol/mina/blob/0b63498e271575dbffe2b31f3ab8be293490b1ac/src/lib/pickles/plonk_checks/plonk_checks.ml#L397>
261    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    // Shift values
275    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
292// TODO: De-duplicate with `derive_plonk`
293pub 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    // use kimchi::circuits::gate::GateType;
300
301    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            // We decompose this way because of OCaml evaluation order
313            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    // <https://github.com/MinaProtocol/mina/blob/0b63498e271575dbffe2b31f3ab8be293490b1ac/src/lib/pickles/plonk_checks/plonk_checks.ml#L46>
321
322    // let minimal_for_scalar = MinimalForScalar {
323    //     alpha: minimal.alpha,
324    //     beta: minimal.beta,
325    //     gamma: minimal.gamma,
326    // };
327
328    // We decompose this way because of OCaml evaluation order
329    // use GateType::{CompleteAdd, EndoMul, EndoMulScalar, VarBaseMul};
330    // let endomul_scalar = scalars::compute(Some(EndoMulScalar), &minimal_for_scalar, evals, w);
331    // let endomul = scalars::compute(Some(EndoMul), &minimal_for_scalar, evals, w);
332    // let complete_add = scalars::compute(Some(CompleteAdd), &minimal_for_scalar, evals, w);
333    // let vbmul = scalars::compute(Some(VarBaseMul), &minimal_for_scalar, evals, w);
334
335    let zeta_to_srs_length = *env.zeta_to_srs_length.get(w);
336
337    // Shift values
338    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.vbmul.shifted, actual.vbmul.shifted, w),
363        // field::equal(plonk.complete_add.shifted, actual.complete_add.shifted, w),
364        // field::equal(plonk.endomul.shifted, actual.endomul.shifted, w),
365        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    // let value = 1 << log2_size;
375    // let domain = Domain::<Fq>::new(value).unwrap();
376    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(); // Never fail, `p_eval0` is non-empty
406
407    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    // This method `Variable::evaluate` is private in proof-systems :(
503    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    // TODO: Use cvar instead
614    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    /// This function use to look like following
708    /// but `x.value` has changed to accept list of challenges
709    ///
710    /// let v = x.value(ctx.constants);
711    /// if let ConstantExpr::Mul(_, _) = x {
712    ///    ctx.w.exists_no_check(v);
713    /// };
714    /// v
715    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        /// cache may contain their own caches
791        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        // Each cached expression may contain their own caches
831        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                    // No features for `Fp`:
861                    // <https://github.com/MinaProtocol/mina/blob/4af0c229548bc96d76678f11b6842999de5d3b0b/src/lib/crypto/kimchi_bindings/stubs/src/linearization.rs>
862                    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        // Moved internally
904        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        // We evaluate the cached expressions first
931        let mut cached_exprs = Cached::default();
932        extract_caches(term, &mut cached_exprs);
933        eval_cache(&cached_exprs, &mut ctx);
934
935        // Eval the rest
936        eval(term, &mut ctx)
937    }
938}
939
940// TODO: De-duplicate with `ft_eval0`
941pub 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(); // Never fail, `p_eval0` is non-empty
968    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            // We decompose this way because of OCaml evaluation order
976            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    // We decompose this way because of OCaml evaluation order
994    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]
1041    // fn test_derive_plonk() {
1042    //     let f = |s| Fp::from_str(s).unwrap();
1043
1044    //     let shift = Shift::<Fp>::create();
1045
1046    //     assert_eq!(
1047    //         shift.scale.to_decimal(),
1048    //         "14474011154664524427946373126085988481681528240970780357977338382174983815169"
1049    //     );
1050    //     assert_eq!(
1051    //         shift.c.to_decimal(),
1052    //         "28948022309329048855892746252171976963271935850878721303774115239606597189632"
1053    //     );
1054
1055    //     let env = ScalarsEnv {
1056    //         zk_polynomial: f(
1057    //             "14952139847623627632005961777062011953737808795126043460542801649311194043823",
1058    //         ),
1059    //         zeta_to_n_minus_1: f(
1060    //             "19992360331803571005450582443613456929944945612236598221548387723463412653399",
1061    //         ),
1062    //         srs_length_log2: 16,
1063    //     };
1064    //     let evals = ProofEvaluations {
1065    //         w: [
1066    //             [
1067    //                 f("6289128557598946688693552667439393426405656688717900311656646754749459718720"),
1068    //                 f("24608814230259595932281883624655184390098983971078865819273833140490521621830")
1069    //             ],
1070    //             [
1071    //                 f("13310290304507948299374922965790520471341407062194282657371827929238607707213"),
1072    //                 f("3420090439313334404412034509907098823576869881992393349685606692932938435891")
1073    //             ],
1074    //             [
1075    //                 f("1004170231282419143892553232264207846476468710370262861712645500613730936589"),
1076    //                 f("15358892959463725686065496984540565674655335654104110656651674381972851539319")
1077    //             ],
1078    //             [
1079    //                 f("14235022520036276432047767790226929227376034059765238012530447329202034471184"),
1080    //                 f("17857867215014615248343791853134492935501050027762182094448303054841389536812")
1081    //             ],
1082    //             [
1083    //                 f("217270972334916519789201407484219134254562938054977741353993356478916733888"),
1084    //                 f("23568343258930877322113518854070923552760559626728211948238368157503758958395")
1085    //             ],
1086    //             [
1087    //                 f("20724860985472235937562434615223247979133698324833013342416503003953673114269"),
1088    //                 f("14230270274902068449746409862542021948616341843532980943017138764071240362770")
1089    //             ],
1090    //             [
1091    //                 f("12691818116679147143874935777710847698261799259714916958466576430559681885637"),
1092    //                 f("22289834256183911112986280165603148282177788815718258800419577474647368509415")
1093    //             ],
1094    //             [
1095    //                 f("24411935077464468269852858502666516692114510902574937532215362000430706390980"),
1096    //                 f("27001187352494410500619676382138528783481518663974608697374943352528963038629")
1097    //             ],
1098    //             [
1099    //                 f("6360373480154519328489111512851611048061657527472483396338572585132690211046"),
1100    //                 f("23582754696949565264224763776269416039258289346076169583123362754778051868081")
1101    //             ],
1102    //             [
1103    //                 f("24503282241957787061015546660977633360122189424659418210184685296100040089763"),
1104    //                 f("1245945804906356625120959596915782469823165927957553537115988479791001371335")
1105    //             ],
1106    //             [
1107    //                 f("9672494201562279236249240210670811621086234303424478665245302624553285994017"),
1108    //                 f("6320456619637925667340696511492775125069306866183434852057714045546561260366")
1109    //             ],
1110    //             [
1111    //                 f("5210254176721326039284483791950162878174035416676107991148934478856221337173"),
1112    //                 f("815800705957329676302064392632236617950232059455550504081221846882909342121")
1113    //             ],
1114    //             [
1115    //                 f("19271726941980627641895844001132986442331047173347430774843986312900315111686"),
1116    //                 f("20110056132616893796657499795094959354081479634362692363043782275259311243305")
1117    //             ],
1118    //             [
1119    //                 f("146495953729640570485828778213514297261651081782921764417219235177307212109"),
1120    //                 f("13510051022748561152196281174320974475938901566632935888293972337640213044221")
1121    //             ],
1122    //             [
1123    //                 f("3518198406204532554527858244346702174770464435733756876385351085395310209176"),
1124    //                 f("15550522660048386236921180860694193742171605550559912660860741398328060104279")
1125    //             ],
1126    //         ],
1127    //         z: [
1128    //             f("21931198523501459183153033195666929989603803804227249746439702940933841410354"),
1129    //             f("6022590485205496790548288878896488149850531459551034305056017859745758834601")
1130    //         ],
1131    //         s: [
1132    //             [
1133    //                 f("19085168690544843887043640753077042378743448700110307869084708762711870533936"),
1134    //                 f("22751786545661378548233843701817138109179015540519447359893734727224759557557")
1135    //             ],
1136    //             [
1137    //                 f("12809849114708285788093252766158738590831386282299128882364744994146092851397"),
1138    //                 f("7775219585837024399019590773584893518283271570804407455680225353290518221037")
1139    //             ],
1140    //             [
1141    //                 f("1321451461746223492813831865923699791759758119520679872448309408329685991365"),
1142    //                 f("24250442489736828467922579478325926748516761175781497417627070248911524273876")
1143    //             ],
1144    //             [
1145    //                 f("14126289132628522291939529284539290777701180720493062389536649540269039839059"),
1146    //                 f("9881670171615426333925133765399382666579015176251728331663175340398678714235")
1147    //             ],
1148    //             [
1149    //                 f("5478696824111960874152427151223870065214252944402491332456328657031528756061"),
1150    //                 f("1377997571297099342686120389926615964800209763625383648915112029912281716920")
1151    //             ],
1152    //             [
1153    //                 f("80056797119034209825231055487115732036243341031547669733590224790110901245"),
1154    //                 f("23954132758389233312829853625715975217840278999681825436710260294777706132878")
1155    //             ],
1156    //         ],
1157    //         generic_selector: [
1158    //             f("21284673669227882794919992407172292293537574509733523059283928016664652610788"),
1159    //             f("21551467950274947783566469754812261989391861302105687694024368348901050138933")
1160    //         ],
1161    //         poseidon_selector: [
1162    //             f("24296813547347387275700638062514083772319473069038997762814161750820112396767"),
1163    //             f("7410307958356630828270235926030893251108745125559587079838321820967330039556")
1164    //         ],
1165    //         lookup: None,
1166    //     };
1167
1168    //     let minimal = PlonkMinimal {
1169    //         alpha: f(
1170    //             "27274897876953793245985525242013286410205575357216365244783619058623821516088",
1171    //         ),
1172    //         beta: f("325777784653629264882054754458727445360"),
1173    //         gamma: f("279124633756639571190538225494418257063"),
1174    //         zeta: f(
1175    //             "23925312945193845374476523404515906217333838946294378278132006013444902630130",
1176    //         ),
1177    //         joint_combiner: None,
1178    //         alpha_bytes: [0; 2], // unused here
1179    //         beta_bytes: [0; 2],  // unused here
1180    //         gamma_bytes: [0; 2], // unused here
1181    //         zeta_bytes: [0; 2],  // unused here
1182    //     };
1183
1184    //     let plonk = derive_plonk(&env, &evals, &minimal);
1185
1186    //     let InCircuit {
1187    //         alpha,
1188    //         beta,
1189    //         gamma,
1190    //         zeta,
1191    //         zeta_to_domain_size,
1192    //         zeta_to_srs_length,
1193    //         poseidon_selector,
1194    //         vbmul,
1195    //         complete_add,
1196    //         endomul,
1197    //         endomul_scalar,
1198    //         perm,
1199    //         generic,
1200    //     } = plonk;
1201
1202    //     // OCAML RESULTS
1203    //     assert_eq!(
1204    //         alpha.to_decimal(),
1205    //         "27274897876953793245985525242013286410205575357216365244783619058623821516088"
1206    //     );
1207    //     assert_eq!(beta.to_decimal(), "325777784653629264882054754458727445360");
1208    //     assert_eq!(
1209    //         gamma.to_decimal(),
1210    //         "279124633756639571190538225494418257063"
1211    //     );
1212    //     assert_eq!(
1213    //         zeta.to_decimal(),
1214    //         "23925312945193845374476523404515906217333838946294378278132006013444902630130"
1215    //     );
1216    //     assert_eq!(
1217    //         zeta_to_srs_length.shifted.to_decimal(),
1218    //         "24470191320566309930671664347892716946699561362620499174841813006278375362221"
1219    //     );
1220    //     assert_eq!(
1221    //         zeta_to_domain_size.shifted.to_decimal(),
1222    //         "24470191320566309930671664347892716946699561362620499174841813006278375362221"
1223    //     );
1224    //     assert_eq!(
1225    //         poseidon_selector.shifted.to_decimal(),
1226    //         "12148406773673693637850319031257041886205296850050918587497361637781741418736"
1227    //     );
1228    //     assert_eq!(
1229    //         perm.shifted.to_decimal(),
1230    //         "23996362553795482752052846938731572092036494641475074785875316421561912520951"
1231    //     );
1232
1233    //     assert_eq!(
1234    //         complete_add.shifted.to_decimal(),
1235    //         "25922772146036107832349768103654536495710983785678446578187694836830607595414",
1236    //     );
1237    //     assert_eq!(
1238    //         vbmul.shifted.to_decimal(),
1239    //         "28215784507806213626095422113975086465418154941914519667351031525311316574704",
1240    //     );
1241    //     assert_eq!(
1242    //         endomul.shifted.to_decimal(),
1243    //         "13064695703283280169401378869933492390852015768221327952370116298712909203140",
1244    //     );
1245    //     assert_eq!(
1246    //         endomul_scalar.shifted.to_decimal(),
1247    //         "28322098634896565337442184680409326841751743190638136318667225694677236113253",
1248    //     );
1249
1250    //     let generic_str = generic.map(|f| f.shifted.to_decimal());
1251    //     assert_eq!(
1252    //         generic_str,
1253    //         [
1254    //             "25116347989278465825406369329672134628495875811368961593709583152878995340915",
1255    //             "17618575433463997772293149459805685194929916900861150219895942521921398894881",
1256    //             "6655145152253974149687461482895260235716263846628561034776194726990989073959",
1257    //             "502085115641209571946276616132103923283794670716551136946603512678550688647",
1258    //             "7575395148088980464707243648576550930395639510870089332970629398518203372685",
1259    //             "21591522414682662643970257021199453095415105586384819070332842809147686271113",
1260    //             "14582646640831982687840973829828098048854370025529688934744615822786127402465",
1261    //             "10362430492736117968781217307611623989612409477947926377298532264348521777487",
1262    //             "12100020790372419869711605834757334981877832164856370998628117306965052030192",
1263    //         ]
1264    //     );
1265    // }
1266
1267    #[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}