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            constraints::FeatureFlags,
487            expr::{CacheId, Column, ConstantExpr, Constants, Expr, ExprError, Op2, Variable},
488            gate::{CurrOrNext, GateType},
489            lookup::lookups::{LookupFeatures, LookupPatterns},
490        },
491        proof::PointEvaluations,
492    };
493
494    use crate::proofs::transaction::endos;
495
496    use super::*;
497
498    // This method `Variable::evaluate` is private in proof-systems :(
499    fn var_evaluate<F: FieldWitness>(
500        v: &Variable,
501        evals: &ProofEvaluations<PointEvaluations<F>>,
502    ) -> Result<F, ExprError> {
503        let point_evaluations = {
504            use kimchi::circuits::lookup::lookups::LookupPattern;
505            use Column::*;
506
507            match v.col {
508                Witness(i) => Ok(evals.w[i]),
509                Z => Ok(evals.z),
510                LookupSorted(i) => {
511                    evals.lookup_sorted[i].ok_or(ExprError::MissingIndexEvaluation(v.col))
512                }
513                LookupAggreg => evals
514                    .lookup_aggregation
515                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
516                LookupTable => evals
517                    .lookup_table
518                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
519                LookupRuntimeTable => evals
520                    .runtime_lookup_table
521                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
522                Index(GateType::Poseidon) => Ok(evals.poseidon_selector),
523                Index(GateType::Generic) => Ok(evals.generic_selector),
524                Index(GateType::CompleteAdd) => Ok(evals.complete_add_selector),
525                Index(GateType::VarBaseMul) => Ok(evals.mul_selector),
526                Index(GateType::EndoMul) => Ok(evals.emul_selector),
527                Index(GateType::EndoMulScalar) => Ok(evals.endomul_scalar_selector),
528                Index(GateType::RangeCheck0) => evals
529                    .range_check0_selector
530                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
531                Index(GateType::RangeCheck1) => evals
532                    .range_check1_selector
533                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
534                Index(GateType::ForeignFieldAdd) => evals
535                    .foreign_field_add_selector
536                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
537                Index(GateType::ForeignFieldMul) => evals
538                    .foreign_field_mul_selector
539                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
540                Index(GateType::Xor16) => evals
541                    .xor_selector
542                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
543                Index(GateType::Rot64) => evals
544                    .rot_selector
545                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
546                Permutation(i) => Ok(evals.s[i]),
547                Coefficient(i) => Ok(evals.coefficients[i]),
548                Column::LookupKindIndex(LookupPattern::Xor) => evals
549                    .xor_lookup_selector
550                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
551                Column::LookupKindIndex(LookupPattern::Lookup) => evals
552                    .lookup_gate_lookup_selector
553                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
554                Column::LookupKindIndex(LookupPattern::RangeCheck) => evals
555                    .range_check_lookup_selector
556                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
557                Column::LookupKindIndex(LookupPattern::ForeignFieldMul) => evals
558                    .foreign_field_mul_lookup_selector
559                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
560                Column::LookupRuntimeSelector => evals
561                    .runtime_lookup_table_selector
562                    .ok_or(ExprError::MissingIndexEvaluation(v.col)),
563                Index(_) => Err(ExprError::MissingIndexEvaluation(v.col)),
564            }
565        }?;
566        match v.row {
567            CurrOrNext::Curr => Ok(point_evaluations.zeta),
568            CurrOrNext::Next => Ok(point_evaluations.zeta_omega),
569        }
570    }
571
572    fn pow<F: FieldWitness>(x: F, n: u64, w: &mut Witness<F>) -> F {
573        if n == 0 {
574            F::one()
575        } else if n == 1 {
576            x
577        } else {
578            let y = pow(field::square(x, w), n / 2, w);
579            if n % 2 == 0 {
580                y
581            } else {
582                field::mul(x, y, w)
583            }
584        }
585    }
586
587    fn pow_const<F: FieldWitness>(x: F, n: u64) -> F {
588        if n == 0 {
589            F::one()
590        } else if n == 1 {
591            x
592        } else {
593            (0..n - 1).fold(x, |acc, _| x * acc)
594        }
595    }
596
597    pub struct EvalContext<'a, F: FieldWitness> {
598        pub evals: &'a ProofEvaluations<PointEvaluations<F>>,
599        pub constants: &'a Constants<F>,
600        pub cache: BTreeMap<CacheId, F>,
601        pub env: &'a ScalarsEnv<F>,
602        pub w: &'a mut Witness<F>,
603    }
604
605    // TODO: Use cvar instead
606    fn is_const<F: FieldWitness>(e: &Expr<ConstantExpr<F>>) -> bool {
607        use ConstantExpr::*;
608        match e {
609            Expr::Constant(c) => matches!(c, EndoCoefficient | Literal(_) | Mds { .. }),
610            Expr::BinOp(_, x, y) => is_const(x) && is_const(y),
611            Expr::Pow(x, _) => is_const(x),
612            _ => false,
613        }
614    }
615
616    pub fn eval<F: FieldWitness>(e: &Expr<ConstantExpr<F>>, ctx: &mut EvalContext<F>) -> F {
617        use Expr::*;
618        match e {
619            Double(x) => {
620                let v = eval(x, ctx);
621                v.double()
622            }
623            Constant(x) => {
624                let v = x.value(ctx.constants);
625                if let ConstantExpr::Mul(_, _) = x {
626                    ctx.w.exists_no_check(v);
627                };
628                v
629            }
630            Pow(x, p) => {
631                let p = *p;
632                let v = eval(x, ctx);
633
634                if is_const(x) {
635                    pow_const(v, p)
636                } else {
637                    pow(v, p, ctx.w)
638                }
639            }
640            BinOp(Op2::Mul, x, y) => {
641                let is_x_const = is_const(x);
642                let is_y_const = is_const(y);
643                let y = eval(y, ctx);
644                let x = eval(x, ctx);
645                if is_x_const || is_y_const {
646                    x * y
647                } else {
648                    field::mul(x, y, ctx.w)
649                }
650            }
651            Square(x) => {
652                let is_x_const = is_const(x);
653                let x = eval(x, ctx);
654                if is_x_const {
655                    x * x
656                } else {
657                    field::mul(x, x, ctx.w)
658                }
659            }
660            BinOp(Op2::Add, x, y) => {
661                let y = eval(y, ctx);
662                let x = eval(x, ctx);
663                x + y
664            }
665            BinOp(Op2::Sub, x, y) => {
666                let y = eval(y, ctx);
667                let x = eval(x, ctx);
668                x - y
669            }
670            VanishesOnZeroKnowledgeAndPreviousRows => {
671                ctx.env.vanishes_on_zero_knowledge_and_previous_rows
672            }
673            UnnormalizedLagrangeBasis(i) => {
674                let unnormalized_lagrange_basis =
675                    ctx.env.unnormalized_lagrange_basis.as_ref().unwrap();
676                unnormalized_lagrange_basis(*i, ctx.w)
677            }
678            Cell(v) => {
679                var_evaluate(v, ctx.evals).unwrap_or_else(|_| F::zero()) // TODO: Is that correct ?
680            }
681            Cache(id, _e) => {
682                ctx.cache.get(id).copied().unwrap() // Cached values were already computed
683            }
684            IfFeature(feature, e1, e2) => match ctx.env.feature_flags.as_ref() {
685                None => eval(e2, ctx),
686                Some(feature_flags) => {
687                    let is_feature_enabled = match get_feature_flag(feature_flags, feature, ctx.w) {
688                        None => return eval(e2, ctx),
689                        Some(enabled) => enabled,
690                    };
691
692                    let on_false = eval(e2, ctx);
693                    let on_true = eval(e1, ctx);
694
695                    ctx.w.exists_no_check(match is_feature_enabled {
696                        Boolean::True => on_true,
697                        Boolean::False => on_false,
698                    })
699                }
700            },
701        }
702    }
703
704    #[derive(Default)]
705    pub struct Cached<F: FieldWitness> {
706        /// cache may contain their own caches
707        expr: BTreeMap<CacheId, (Box<Cached<F>>, Box<Expr<ConstantExpr<F>>>)>,
708    }
709
710    #[inline(never)]
711    pub fn extract_caches<F: FieldWitness>(e: &Expr<ConstantExpr<F>>, cache: &mut Cached<F>) {
712        use Expr::*;
713        match e {
714            Double(x) => {
715                extract_caches(x, cache);
716            }
717            Constant(_x) => (),
718            Pow(x, _p) => {
719                extract_caches(x, cache);
720            }
721            BinOp(Op2::Mul, x, y) => {
722                extract_caches(y, cache);
723                extract_caches(x, cache);
724            }
725            Square(x) => {
726                extract_caches(x, cache);
727            }
728            BinOp(Op2::Add, x, y) => {
729                extract_caches(y, cache);
730                extract_caches(x, cache);
731            }
732            BinOp(Op2::Sub, x, y) => {
733                extract_caches(y, cache);
734                extract_caches(x, cache);
735            }
736            VanishesOnZeroKnowledgeAndPreviousRows => todo!(),
737            UnnormalizedLagrangeBasis(_i) => todo!(),
738            Cell(_v) => (),
739            Cache(id, e) => {
740                let mut cached = Cached::default();
741                extract_caches(e, &mut cached);
742                cache.expr.insert(*id, (Box::new(cached), e.clone()));
743            }
744            IfFeature(_feature, e1, e2) => {
745                if false {
746                    extract_caches(e1, cache)
747                } else {
748                    extract_caches(e2, cache)
749                }
750            }
751        }
752    }
753
754    fn eval_cache<F: FieldWitness>(cached_exprs: &Cached<F>, ctx: &mut EvalContext<F>) {
755        // Each cached expression may contain their own caches
756        for (id, (cache, expr)) in &cached_exprs.expr {
757            let mut old_cache = std::mem::take(&mut ctx.cache);
758            eval_cache::<F>(cache, ctx);
759            old_cache.insert(*id, eval::<F>(expr, ctx));
760            ctx.cache = old_cache;
761        }
762    }
763
764    pub struct MinimalForScalar<F> {
765        pub alpha: F,
766        pub beta: F,
767        pub gamma: F,
768        pub lookup: Option<F>,
769    }
770
771    pub fn compute<F: FieldWitness>(
772        gate: Option<GateType>,
773        minimal: &MinimalForScalar<F>,
774        evals: &ProofEvaluations<PointEvaluations<F>>,
775        env: &ScalarsEnv<F>,
776        w: &mut Witness<F>,
777    ) -> F {
778        let (constant_term, index_terms) = &*{
779            type TermsMap<F> = BTreeMap<Column, Expr<ConstantExpr<F>>>;
780            type Const<F> = Expr<ConstantExpr<F>>;
781            type Terms<F> = Rc<(Const<F>, TermsMap<F>)>;
782            cache! {
783                Terms::<F>, {
784                    // No features for `Fp`:
785                    // <https://github.com/MinaProtocol/mina/blob/4af0c229548bc96d76678f11b6842999de5d3b0b/src/lib/crypto/kimchi_bindings/stubs/src/linearization.rs>
786                    let is_fp = std::any::TypeId::of::<F>() == std::any::TypeId::of::<Fp>();
787
788                    let features = if is_fp {
789                        None
790                    } else {
791                        Some(FeatureFlags {
792                            range_check0: false,
793                            range_check1: false,
794                            foreign_field_add: false,
795                            foreign_field_mul: false,
796                            xor: false,
797                            rot: false,
798                            lookup_features: LookupFeatures {
799                                patterns: LookupPatterns {
800                                    xor: false,
801                                    lookup: false,
802                                    range_check: false,
803                                    foreign_field_mul: false,
804                                },
805                                joint_lookup_used: false,
806                                uses_runtime_tables: false,
807                            },
808                        })
809                    };
810
811                    let evaluated_cols =
812                        kimchi::linearization::linearization_columns::<F>(features.as_ref());
813                    let (linearization, _powers_of_alpha) =
814                        kimchi::linearization::constraints_expr::<F>(features.as_ref(), true);
815
816                    let kimchi::circuits::expr::Linearization {
817                        constant_term,
818                        index_terms,
819                    } = linearization.linearize(evaluated_cols).unwrap();
820
821                    let index_terms = index_terms.into_iter().collect::<TermsMap<F>>();
822                    Rc::new((constant_term, index_terms))
823                }
824            }
825        };
826
827        let constants = kimchi::circuits::expr::Constants::<F> {
828            alpha: minimal.alpha,
829            beta: minimal.beta,
830            gamma: minimal.gamma,
831            joint_combiner: minimal.lookup,
832            endo_coefficient: {
833                let (base, _) = endos::<F>();
834                base
835            },
836            mds: &<F::OtherCurve>::sponge_params().mds,
837            zk_rows: 3,
838        };
839
840        let mut ctx = EvalContext {
841            evals,
842            constants: &constants,
843            cache: BTreeMap::new(),
844            env,
845            w,
846        };
847
848        let term = match gate {
849            Some(gate) => index_terms.get(&Column::Index(gate)).unwrap(),
850            None => constant_term,
851        };
852
853        // We evaluate the cached expressions first
854        let mut cached_exprs = Cached::default();
855        extract_caches(term, &mut cached_exprs);
856        eval_cache(&cached_exprs, &mut ctx);
857
858        // Eval the rest
859        eval(term, &mut ctx)
860    }
861}
862
863// TODO: De-duplicate with `ft_eval0`
864pub fn ft_eval0_checked<F: FieldWitness, const NLIMB: usize>(
865    env: &ScalarsEnv<F>,
866    evals: &ProofEvaluations<PointEvaluations<F>>,
867    minimal: &PlonkMinimal<F, NLIMB>,
868    lookup: Option<F>,
869    p_eval0: &[F],
870    w: &mut Witness<F>,
871) -> F {
872    const PLONK_TYPES_PERMUTS_MINUS_1_N: usize = 6;
873
874    let e0_s: Vec<_> = evals.s.iter().map(|s| s.zeta).collect();
875    let zkp = env.zk_polynomial;
876    let powers_of_alpha = powers_of_alpha(minimal.alpha);
877    let alpha_pow = |i: usize| powers_of_alpha[i];
878
879    let zeta1m1 = env.zeta_to_n_minus_1;
880    let p_eval0 = p_eval0
881        .iter()
882        .copied()
883        .rfold(None, |acc, p_eval0| match acc {
884            None => Some(p_eval0),
885            Some(acc) => {
886                let zeta1 = *env.zeta_to_srs_length.get(w);
887                Some(p_eval0 + field::mul(zeta1, acc, w))
888            }
889        })
890        .unwrap(); // Never fail, `p_eval0` is non-empty
891    let w0: Vec<_> = evals.w.iter().map(|w| w.zeta).collect();
892
893    let ft_eval0 = {
894        let a0 = alpha_pow(PERM_ALPHA0);
895        let w_n = w0[PLONK_TYPES_PERMUTS_MINUS_1_N];
896        let init = field::muls(&[(w_n + minimal.gamma), evals.z.zeta_omega, a0, zkp], w);
897        e0_s.iter().enumerate().fold(init, |acc, (i, s)| {
898            // We decompose this way because of OCaml evaluation order
899            let beta_s = field::mul(minimal.beta, *s, w);
900            field::mul(beta_s + w0[i] + minimal.gamma, acc, w)
901        })
902    };
903
904    let shifts = env.domain.shifts();
905    let ft_eval0 = ft_eval0 - p_eval0;
906
907    let ft_eval0 = ft_eval0
908        - shifts.iter().enumerate().fold(
909            field::muls(&[alpha_pow(PERM_ALPHA0), zkp, evals.z.zeta], w),
910            |acc, (i, s)| {
911                let beta_zeta = field::mul(minimal.beta, minimal.zeta, w);
912                field::mul(acc, minimal.gamma + (beta_zeta * s) + w0[i], w)
913            },
914        );
915
916    // We decompose this way because of OCaml evaluation order
917    let a = field::muls(
918        &[
919            zeta1m1,
920            alpha_pow(PERM_ALPHA0 + 2),
921            (minimal.zeta - F::one()),
922        ],
923        w,
924    );
925    let b = field::muls(
926        &[
927            zeta1m1,
928            alpha_pow(PERM_ALPHA0 + 1),
929            (minimal.zeta - env.omega_to_minus_zk_rows),
930        ],
931        w,
932    );
933    let nominator = field::mul(a + b, F::one() - evals.z.zeta, w);
934
935    let denominator = field::mul(
936        minimal.zeta - env.omega_to_minus_zk_rows,
937        minimal.zeta - F::one(),
938        w,
939    );
940    let ft_eval0 = ft_eval0 + field::div_by_inv(nominator, denominator, w);
941
942    let minimal = MinimalForScalar {
943        alpha: minimal.alpha,
944        beta: minimal.beta,
945        gamma: minimal.gamma,
946        lookup,
947    };
948    let constant_term = scalars::compute(None, &minimal, evals, env, w);
949    ft_eval0 - constant_term
950}
951
952#[cfg(test)]
953mod tests {
954    use std::str::FromStr;
955
956    use crate::FpExt;
957
958    use super::*;
959
960    #[cfg(target_family = "wasm")]
961    use wasm_bindgen_test::wasm_bindgen_test as test;
962
963    // #[test]
964    // fn test_derive_plonk() {
965    //     let f = |s| Fp::from_str(s).unwrap();
966
967    //     let shift = Shift::<Fp>::create();
968
969    //     assert_eq!(
970    //         shift.scale.to_decimal(),
971    //         "14474011154664524427946373126085988481681528240970780357977338382174983815169"
972    //     );
973    //     assert_eq!(
974    //         shift.c.to_decimal(),
975    //         "28948022309329048855892746252171976963271935850878721303774115239606597189632"
976    //     );
977
978    //     let env = ScalarsEnv {
979    //         zk_polynomial: f(
980    //             "14952139847623627632005961777062011953737808795126043460542801649311194043823",
981    //         ),
982    //         zeta_to_n_minus_1: f(
983    //             "19992360331803571005450582443613456929944945612236598221548387723463412653399",
984    //         ),
985    //         srs_length_log2: 16,
986    //     };
987    //     let evals = ProofEvaluations {
988    //         w: [
989    //             [
990    //                 f("6289128557598946688693552667439393426405656688717900311656646754749459718720"),
991    //                 f("24608814230259595932281883624655184390098983971078865819273833140490521621830")
992    //             ],
993    //             [
994    //                 f("13310290304507948299374922965790520471341407062194282657371827929238607707213"),
995    //                 f("3420090439313334404412034509907098823576869881992393349685606692932938435891")
996    //             ],
997    //             [
998    //                 f("1004170231282419143892553232264207846476468710370262861712645500613730936589"),
999    //                 f("15358892959463725686065496984540565674655335654104110656651674381972851539319")
1000    //             ],
1001    //             [
1002    //                 f("14235022520036276432047767790226929227376034059765238012530447329202034471184"),
1003    //                 f("17857867215014615248343791853134492935501050027762182094448303054841389536812")
1004    //             ],
1005    //             [
1006    //                 f("217270972334916519789201407484219134254562938054977741353993356478916733888"),
1007    //                 f("23568343258930877322113518854070923552760559626728211948238368157503758958395")
1008    //             ],
1009    //             [
1010    //                 f("20724860985472235937562434615223247979133698324833013342416503003953673114269"),
1011    //                 f("14230270274902068449746409862542021948616341843532980943017138764071240362770")
1012    //             ],
1013    //             [
1014    //                 f("12691818116679147143874935777710847698261799259714916958466576430559681885637"),
1015    //                 f("22289834256183911112986280165603148282177788815718258800419577474647368509415")
1016    //             ],
1017    //             [
1018    //                 f("24411935077464468269852858502666516692114510902574937532215362000430706390980"),
1019    //                 f("27001187352494410500619676382138528783481518663974608697374943352528963038629")
1020    //             ],
1021    //             [
1022    //                 f("6360373480154519328489111512851611048061657527472483396338572585132690211046"),
1023    //                 f("23582754696949565264224763776269416039258289346076169583123362754778051868081")
1024    //             ],
1025    //             [
1026    //                 f("24503282241957787061015546660977633360122189424659418210184685296100040089763"),
1027    //                 f("1245945804906356625120959596915782469823165927957553537115988479791001371335")
1028    //             ],
1029    //             [
1030    //                 f("9672494201562279236249240210670811621086234303424478665245302624553285994017"),
1031    //                 f("6320456619637925667340696511492775125069306866183434852057714045546561260366")
1032    //             ],
1033    //             [
1034    //                 f("5210254176721326039284483791950162878174035416676107991148934478856221337173"),
1035    //                 f("815800705957329676302064392632236617950232059455550504081221846882909342121")
1036    //             ],
1037    //             [
1038    //                 f("19271726941980627641895844001132986442331047173347430774843986312900315111686"),
1039    //                 f("20110056132616893796657499795094959354081479634362692363043782275259311243305")
1040    //             ],
1041    //             [
1042    //                 f("146495953729640570485828778213514297261651081782921764417219235177307212109"),
1043    //                 f("13510051022748561152196281174320974475938901566632935888293972337640213044221")
1044    //             ],
1045    //             [
1046    //                 f("3518198406204532554527858244346702174770464435733756876385351085395310209176"),
1047    //                 f("15550522660048386236921180860694193742171605550559912660860741398328060104279")
1048    //             ],
1049    //         ],
1050    //         z: [
1051    //             f("21931198523501459183153033195666929989603803804227249746439702940933841410354"),
1052    //             f("6022590485205496790548288878896488149850531459551034305056017859745758834601")
1053    //         ],
1054    //         s: [
1055    //             [
1056    //                 f("19085168690544843887043640753077042378743448700110307869084708762711870533936"),
1057    //                 f("22751786545661378548233843701817138109179015540519447359893734727224759557557")
1058    //             ],
1059    //             [
1060    //                 f("12809849114708285788093252766158738590831386282299128882364744994146092851397"),
1061    //                 f("7775219585837024399019590773584893518283271570804407455680225353290518221037")
1062    //             ],
1063    //             [
1064    //                 f("1321451461746223492813831865923699791759758119520679872448309408329685991365"),
1065    //                 f("24250442489736828467922579478325926748516761175781497417627070248911524273876")
1066    //             ],
1067    //             [
1068    //                 f("14126289132628522291939529284539290777701180720493062389536649540269039839059"),
1069    //                 f("9881670171615426333925133765399382666579015176251728331663175340398678714235")
1070    //             ],
1071    //             [
1072    //                 f("5478696824111960874152427151223870065214252944402491332456328657031528756061"),
1073    //                 f("1377997571297099342686120389926615964800209763625383648915112029912281716920")
1074    //             ],
1075    //             [
1076    //                 f("80056797119034209825231055487115732036243341031547669733590224790110901245"),
1077    //                 f("23954132758389233312829853625715975217840278999681825436710260294777706132878")
1078    //             ],
1079    //         ],
1080    //         generic_selector: [
1081    //             f("21284673669227882794919992407172292293537574509733523059283928016664652610788"),
1082    //             f("21551467950274947783566469754812261989391861302105687694024368348901050138933")
1083    //         ],
1084    //         poseidon_selector: [
1085    //             f("24296813547347387275700638062514083772319473069038997762814161750820112396767"),
1086    //             f("7410307958356630828270235926030893251108745125559587079838321820967330039556")
1087    //         ],
1088    //         lookup: None,
1089    //     };
1090
1091    //     let minimal = PlonkMinimal {
1092    //         alpha: f(
1093    //             "27274897876953793245985525242013286410205575357216365244783619058623821516088",
1094    //         ),
1095    //         beta: f("325777784653629264882054754458727445360"),
1096    //         gamma: f("279124633756639571190538225494418257063"),
1097    //         zeta: f(
1098    //             "23925312945193845374476523404515906217333838946294378278132006013444902630130",
1099    //         ),
1100    //         joint_combiner: None,
1101    //         alpha_bytes: [0; 2], // unused here
1102    //         beta_bytes: [0; 2],  // unused here
1103    //         gamma_bytes: [0; 2], // unused here
1104    //         zeta_bytes: [0; 2],  // unused here
1105    //     };
1106
1107    //     let plonk = derive_plonk(&env, &evals, &minimal);
1108
1109    //     let InCircuit {
1110    //         alpha,
1111    //         beta,
1112    //         gamma,
1113    //         zeta,
1114    //         zeta_to_domain_size,
1115    //         zeta_to_srs_length,
1116    //         poseidon_selector,
1117    //         vbmul,
1118    //         complete_add,
1119    //         endomul,
1120    //         endomul_scalar,
1121    //         perm,
1122    //         generic,
1123    //     } = plonk;
1124
1125    //     // OCAML RESULTS
1126    //     assert_eq!(
1127    //         alpha.to_decimal(),
1128    //         "27274897876953793245985525242013286410205575357216365244783619058623821516088"
1129    //     );
1130    //     assert_eq!(beta.to_decimal(), "325777784653629264882054754458727445360");
1131    //     assert_eq!(
1132    //         gamma.to_decimal(),
1133    //         "279124633756639571190538225494418257063"
1134    //     );
1135    //     assert_eq!(
1136    //         zeta.to_decimal(),
1137    //         "23925312945193845374476523404515906217333838946294378278132006013444902630130"
1138    //     );
1139    //     assert_eq!(
1140    //         zeta_to_srs_length.shifted.to_decimal(),
1141    //         "24470191320566309930671664347892716946699561362620499174841813006278375362221"
1142    //     );
1143    //     assert_eq!(
1144    //         zeta_to_domain_size.shifted.to_decimal(),
1145    //         "24470191320566309930671664347892716946699561362620499174841813006278375362221"
1146    //     );
1147    //     assert_eq!(
1148    //         poseidon_selector.shifted.to_decimal(),
1149    //         "12148406773673693637850319031257041886205296850050918587497361637781741418736"
1150    //     );
1151    //     assert_eq!(
1152    //         perm.shifted.to_decimal(),
1153    //         "23996362553795482752052846938731572092036494641475074785875316421561912520951"
1154    //     );
1155
1156    //     assert_eq!(
1157    //         complete_add.shifted.to_decimal(),
1158    //         "25922772146036107832349768103654536495710983785678446578187694836830607595414",
1159    //     );
1160    //     assert_eq!(
1161    //         vbmul.shifted.to_decimal(),
1162    //         "28215784507806213626095422113975086465418154941914519667351031525311316574704",
1163    //     );
1164    //     assert_eq!(
1165    //         endomul.shifted.to_decimal(),
1166    //         "13064695703283280169401378869933492390852015768221327952370116298712909203140",
1167    //     );
1168    //     assert_eq!(
1169    //         endomul_scalar.shifted.to_decimal(),
1170    //         "28322098634896565337442184680409326841751743190638136318667225694677236113253",
1171    //     );
1172
1173    //     let generic_str = generic.map(|f| f.shifted.to_decimal());
1174    //     assert_eq!(
1175    //         generic_str,
1176    //         [
1177    //             "25116347989278465825406369329672134628495875811368961593709583152878995340915",
1178    //             "17618575433463997772293149459805685194929916900861150219895942521921398894881",
1179    //             "6655145152253974149687461482895260235716263846628561034776194726990989073959",
1180    //             "502085115641209571946276616132103923283794670716551136946603512678550688647",
1181    //             "7575395148088980464707243648576550930395639510870089332970629398518203372685",
1182    //             "21591522414682662643970257021199453095415105586384819070332842809147686271113",
1183    //             "14582646640831982687840973829828098048854370025529688934744615822786127402465",
1184    //             "10362430492736117968781217307611623989612409477947926377298532264348521777487",
1185    //             "12100020790372419869711605834757334981877832164856370998628117306965052030192",
1186    //         ]
1187    //     );
1188    // }
1189
1190    #[test]
1191    fn test_alphas() {
1192        let n = Fp::from_str(
1193            "27274897876953793245985525242013286410205575357216365244783619058623821516088",
1194        )
1195        .unwrap();
1196
1197        let alphas: Box<[Fp; NPOWERS_OF_ALPHA]> = powers_of_alpha(n);
1198        let alphas_str: Vec<String> = alphas.iter().map(|f| f.to_decimal()).collect();
1199
1200        const OCAML_RESULT: &[&str] = &[
1201            "1",
1202            "27274897876953793245985525242013286410205575357216365244783619058623821516088",
1203            "5856243499679297994261942705106783326584825647279332525318074626467168425175",
1204            "26908526253468636093650206549302380737071523922183255477383956748755441012366",
1205            "21276200075660690362913766498168565850417909161152737384646582509540496229450",
1206            "3843731251681147173193384676587074004662025496739119332721571982426684387560",
1207            "12392606098341916760701161625583524765199435768082801118718099569066567820086",
1208            "5932489972119399045562481763112253944218195162891420406370178296693572483896",
1209            "1375846522483390900802414356841133463956126287864007136159978297384640659584",
1210            "5356524575738460513076981906288272723856440519543693179836517878630162813220",
1211            "23319398249603527452857836680743813857193813763032214190196631633251915644825",
1212            "10921184148344839491052929288136821627436657352065581423854521247501001908351",
1213            "13053560967285308651226207033123539702290413328361716005386653453569329750313",
1214            "8298101552564684053050013414211292674866114224797784754887740268228151928335",
1215            "715072795965317694491886715913315968459520650830405802156784401283709943505",
1216            "25198551493059869063561311792478884528738012039746184861146867788131566740666",
1217            "27161703551928606962685117055547438689494792119791879693135179256422752270728",
1218            "28799358614011589987311924793640447939591189984280570017428244220659375622447",
1219            "4488279652568453906961591843014473441709515392753701104095475657832824041646",
1220            "4641946865609115816676535679719511429699894348223929677606063307711524129548",
1221            "995093492640264169583875280706844374785298168266651011740457078469635678163",
1222            "17429526728376789811772110265115435172515921536052154380599101096979177652072",
1223            "22850194394147425267881995863659224768162140900074856912248813188202263996579",
1224            "26770317988143355422138083990683491489315652370177339626307684951664128480053",
1225        ];
1226
1227        assert_eq!(alphas_str, OCAML_RESULT);
1228    }
1229}