mina_tree/proofs/
transaction.rs

1use std::{collections::HashMap, rc::Rc, str::FromStr, sync::Arc};
2
3use anyhow::Context;
4use ark_ec::{short_weierstrass::Projective, AffineRepr, CurveGroup, PrimeGroup};
5use ark_ff::{AdditiveGroup, BigInteger256, Field, PrimeField};
6use kimchi::{
7    circuits::{gate::CircuitGate, wires::COLUMNS},
8    groupmap::{BWParameters, GroupMap},
9    proof::RecursionChallenge,
10};
11use mina_curves::pasta::{Fp, Fq};
12use mina_p2p_messages::{
13    bigint::InvalidBigInt,
14    v2::{
15        self, ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1,
16        ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1,
17        CurrencyAmountStableV1, MinaBaseEpochLedgerValueStableV1, MinaBaseFeeExcessStableV1,
18        MinaBaseProtocolConstantsCheckedValueStableV1, MinaNumbersGlobalSlotSinceGenesisMStableV1,
19        MinaNumbersGlobalSlotSinceHardForkMStableV1,
20        MinaStateBlockchainStateValueStableV2LedgerProofStatement,
21        MinaStateBlockchainStateValueStableV2LedgerProofStatementSource,
22        MinaStateBlockchainStateValueStableV2SignedAmount,
23        MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1, SgnStableV1, SignedAmount,
24        TokenFeeExcess, UnsignedExtendedUInt32StableV1,
25        UnsignedExtendedUInt64Int64ForVersionTagsStableV1,
26    },
27};
28use mina_poseidon::constants::PlonkSpongeConstantsKimchi;
29use mina_signer::{CompressedPubKey, PubKey};
30use poly_commitment::commitment::CommitmentCurve;
31
32use crate::{
33    decompress_pk, gen_keypair,
34    proofs::{
35        self,
36        constants::{StepTransactionProof, WrapTransactionProof},
37        unfinalized::AllEvals,
38        util::sha256_sum,
39        wrap::{self, WrapParams},
40    },
41    scan_state::{
42        currency::{self, Sgn},
43        fee_excess::FeeExcess,
44        pending_coinbase,
45        scan_state::transaction_snark::{Registers, SokDigest, SokMessage, Statement},
46        transaction_logic::{local_state::LocalState, transaction_union_payload},
47    },
48    verifier::get_srs_mut,
49    Account, AppendToInputs, MyCow, ReceiptChainHash, TimingAsRecord, TokenId, TokenSymbol,
50};
51
52use super::{
53    constants::ProofConstants,
54    field::{field, Boolean, CircuitVar, FieldWitness, GroupAffine, ToBoolean},
55    public_input::messages::{dummy_ipa_step_sg, MessagesForNextWrapProof},
56    step,
57    step::{InductiveRule, OptFlag, StepProof},
58    to_field_elements::{ToFieldElements, ToFieldElementsDebug},
59    unfinalized::Unfinalized,
60    witness::Witness,
61    wrap::WrapProof,
62    ProverIndex,
63};
64
65pub trait Check<F: FieldWitness> {
66    fn check(&self, w: &mut Witness<F>);
67}
68
69struct FieldBitsIterator {
70    index: usize,
71    bigint: [u64; 4],
72}
73
74impl Iterator for FieldBitsIterator {
75    type Item = bool;
76
77    fn next(&mut self) -> Option<Self::Item> {
78        let index = self.index;
79        self.index += 1;
80
81        let limb_index = index / 64;
82        let bit_index = index % 64;
83
84        let limb = self.bigint.get(limb_index)?;
85        Some(limb & (1 << bit_index) != 0)
86    }
87}
88
89pub fn bigint_to_bits<const NBITS: usize>(bigint: BigInteger256) -> [bool; NBITS] {
90    let mut bits = FieldBitsIterator {
91        index: 0,
92        bigint: bigint.0,
93    }
94    .take(NBITS);
95    std::array::from_fn(|_| bits.next().unwrap())
96}
97
98pub fn field_to_bits<F, const NBITS: usize>(field: F) -> [bool; NBITS]
99where
100    F: Field + Into<BigInteger256>,
101{
102    let bigint: BigInteger256 = field.into();
103    bigint_to_bits(bigint)
104}
105
106/// Difference with `bigint_to_bits`: the number of bits isn't a constant
107fn bigint_to_bits2(bigint: BigInteger256, nbits: usize) -> Box<[bool]> {
108    FieldBitsIterator {
109        index: 0,
110        bigint: bigint.0,
111    }
112    .take(nbits)
113    .collect()
114}
115
116/// Difference with `field_to_bits`: the number of bits isn't a constant
117pub fn field_to_bits2<F>(field: F, nbits: usize) -> Box<[bool]>
118where
119    F: Field + Into<BigInteger256>,
120{
121    let bigint: BigInteger256 = field.into();
122    bigint_to_bits2(bigint, nbits)
123}
124
125fn bits_msb<F, const NBITS: usize>(field: F) -> [bool; NBITS]
126where
127    F: Field + Into<BigInteger256>,
128{
129    let mut bits = field_to_bits::<F, NBITS>(field);
130    bits.reverse();
131    bits
132}
133
134pub fn endos<F>() -> (F, <F as proofs::field::FieldWitness>::Scalar)
135where
136    F: FieldWitness,
137{
138    use poly_commitment::ipa::endos;
139
140    // Let's keep them in cache since they're used everywhere
141    cache!(
142        (F, <F as proofs::field::FieldWitness>::Scalar),
143        endos::<GroupAffine<F>>()
144    )
145}
146
147pub fn make_group<F>(x: F, y: F) -> GroupAffine<F>
148where
149    F: FieldWitness,
150{
151    if x == F::ZERO && y == F::ZERO {
152        GroupAffine::<F>::zero()
153    } else {
154        GroupAffine::<F>::new(x, y)
155    }
156}
157
158pub mod scalar_challenge {
159    use crate::proofs;
160
161    use super::*;
162
163    // TODO: `scalar` might be a `F::Scalar` here
164    // <https://github.com/MinaProtocol/mina/blob/357144819e7ce5f61109d23d33da627be28024c7/src/lib/pickles/scalar_challenge.ml#L12>
165    pub fn to_field_checked_prime<F, const NBITS: usize>(scalar: F, w: &mut Witness<F>) -> (F, F, F)
166    where
167        F: FieldWitness,
168    {
169        let zero = F::zero();
170        let one = F::one();
171        let neg_one = one.neg();
172
173        let a_array = [zero, zero, neg_one, one];
174        let a_func = |n: u64| a_array[n as usize];
175
176        let b_array = [neg_one, one, zero, zero];
177        let b_func = |n: u64| b_array[n as usize];
178
179        let bits_msb: [bool; NBITS] = bits_msb::<_, NBITS>(scalar);
180
181        let nybbles_per_row = 8;
182        let bits_per_row = 2 * nybbles_per_row;
183        assert_eq!((NBITS % bits_per_row), 0);
184        let rows = NBITS / bits_per_row;
185
186        // TODO: Use arrays when const feature allows it
187        // <https://github.com/rust-lang/rust/issues/76560>
188        let nybbles_by_row: Vec<Vec<u64>> = (0..rows)
189            .map(|i| {
190                (0..nybbles_per_row)
191                    .map(|j| {
192                        let bit = (bits_per_row * i) + (2 * j);
193                        let b0 = bits_msb[bit + 1] as u64;
194                        let b1 = bits_msb[bit] as u64;
195                        b0 + (2 * b1)
196                    })
197                    .collect()
198            })
199            .collect();
200
201        let two: F = 2u64.into();
202        let mut a = two;
203        let mut b = two;
204        let mut n = F::zero();
205
206        for nybbles_by_row in nybbles_by_row.iter().take(rows) {
207            let n0 = n;
208            let a0 = a;
209            let b0 = b;
210
211            let xs: Vec<F> = (0..nybbles_per_row)
212                .map(|j| w.exists(F::from(nybbles_by_row[j])))
213                .collect();
214
215            let n8: F = w.exists(xs.iter().fold(n0, |accum, x| accum.double().double() + x));
216
217            let a8: F = w.exists(
218                nybbles_by_row
219                    .iter()
220                    .fold(a0, |accum, x| accum.double() + a_func(*x)),
221            );
222
223            let b8: F = w.exists(
224                nybbles_by_row
225                    .iter()
226                    .fold(b0, |accum, x| accum.double() + b_func(*x)),
227            );
228
229            n = n8;
230            a = a8;
231            b = b8;
232        }
233
234        (a, b, n)
235    }
236
237    // TODO: `scalar` might be a `F::Scalar` here
238    pub fn to_field_checked<F, const NBITS: usize>(scalar: F, endo: F, w: &mut Witness<F>) -> F
239    where
240        F: FieldWitness,
241    {
242        let (a, b, _n) = to_field_checked_prime::<F, NBITS>(scalar, w);
243        (a * endo) + b
244    }
245
246    // TODO: Use `F::Scalar` instead of `F2`
247    pub fn endo<F, F2, const NBITS: usize>(
248        t: GroupAffine<F>,
249        scalar: F2,
250        w: &mut Witness<F>,
251    ) -> GroupAffine<F>
252    where
253        F: FieldWitness,
254        F2: FieldWitness,
255    {
256        endo_cvar::<F, F2, NBITS>(CircuitVar::Var(t), scalar, w)
257    }
258
259    // TODO: Remove
260    pub fn endo_cvar<F, F2, const NBITS: usize>(
261        t: CircuitVar<GroupAffine<F>>,
262        scalar: F2,
263        w: &mut Witness<F>,
264    ) -> GroupAffine<F>
265    where
266        F: FieldWitness,
267        F2: FieldWitness,
268    {
269        let bits: [bool; NBITS] = bits_msb::<F2, NBITS>(scalar);
270
271        let bits_per_row = 4;
272        let rows = NBITS / bits_per_row;
273
274        let GroupAffine::<F> { x: xt, y: yt, .. } = *t.value();
275        let (endo_base, _) = endos::<F>();
276
277        let mut acc = {
278            // The `exists` call is made by the `seal` call in OCaml
279            // Note: it's actually `Cvar.scale`
280            let tmp = match t {
281                CircuitVar::Var(_) => w.exists(xt * endo_base),
282                CircuitVar::Constant(_) => xt * endo_base,
283            };
284            let p = w.add_fast(*t.value(), make_group::<F>(tmp, yt));
285            w.add_fast(p, p)
286        };
287
288        let mut n_acc = F::zero();
289        for i in 0..rows {
290            let n_acc_prev = n_acc;
291            let b1 = w.exists(F::from(bits[i * bits_per_row]));
292            let b2 = w.exists(F::from(bits[(i * bits_per_row) + 1]));
293            let b3 = w.exists(F::from(bits[(i * bits_per_row) + 2]));
294            let b4 = w.exists(F::from(bits[(i * bits_per_row) + 3]));
295
296            let GroupAffine::<F> { x: xp, y: yp, .. } = acc;
297            let xq1 = w.exists((F::one() + ((endo_base - F::one()) * b1)) * xt);
298            let yq1 = w.exists((b2.double() - F::one()) * yt);
299            let s1 = w.exists((yq1 - yp) / (xq1 - xp));
300            let s1_squared = w.exists(s1.square());
301            let s2 = w.exists((yp.double() / (xp.double() + xq1 - s1_squared)) - s1);
302            let xr = w.exists(xq1 + s2.square() - s1_squared);
303            let yr = w.exists(((xp - xr) * s2) - yp);
304            let xq2 = w.exists((F::one() + ((endo_base - F::one()) * b3)) * xt);
305            let yq2 = w.exists((b4.double() - F::one()) * yt);
306            let s3 = w.exists((yq2 - yr) / (xq2 - xr));
307            let s3_squared = w.exists(s3.square());
308            let s4 = w.exists((yr.double() / (xr.double() + xq2 - s3_squared)) - s3);
309            let xs = w.exists(xq2 + s4.square() - s3_squared);
310            let ys = w.exists(((xr - xs) * s4) - yr);
311
312            acc = make_group::<F>(xs, ys);
313            n_acc =
314                w.exists((((n_acc_prev.double() + b1).double() + b2).double() + b3).double() + b4);
315        }
316
317        acc
318    }
319
320    // TODO: Use `F::Scalar` for `chal`
321    pub fn endo_inv<F, F2, const NBITS: usize>(
322        t: GroupAffine<F>,
323        chal: F2,
324        w: &mut Witness<F>,
325    ) -> GroupAffine<F>
326    where
327        F: FieldWitness,
328        F2: FieldWitness,
329    {
330        use crate::proofs::public_input::scalar_challenge::ScalarChallenge;
331        use ark_ff::One;
332
333        let (_, e) = endos::<F>();
334
335        let res = w.exists({
336            let chal = ScalarChallenge::from(chal).to_field(&e);
337            InnerCurve::<F>::of_affine(t)
338                .scale(<<F as proofs::field::FieldWitness>::Scalar>::one() / chal)
339        });
340        let _ = endo::<F, F2, NBITS>(res.to_affine(), chal, w);
341        res.to_affine()
342    }
343}
344
345pub fn add_fast<F>(
346    p1: GroupAffine<F>,
347    p2: GroupAffine<F>,
348    check_finite: Option<bool>,
349    w: &mut Witness<F>,
350) -> GroupAffine<F>
351where
352    F: FieldWitness,
353{
354    let GroupAffine::<F> { x: x1, y: y1, .. } = p1;
355    let GroupAffine::<F> { x: x2, y: y2, .. } = p2;
356    let check_finite = check_finite.unwrap_or(true);
357
358    let bool_to_field = |b: bool| if b { F::one() } else { F::zero() };
359
360    let same_x_bool = x1 == x2;
361    let _same_x = w.exists(bool_to_field(same_x_bool));
362
363    let _inf = if check_finite {
364        F::zero()
365    } else {
366        w.exists(bool_to_field(same_x_bool && y1 != y2))
367    };
368
369    let _inf_z = w.exists({
370        if y1 == y2 {
371            F::zero()
372        } else if same_x_bool {
373            (y2 - y1).inverse().unwrap()
374        } else {
375            F::zero()
376        }
377    });
378
379    let _x21_inv = w.exists({
380        if same_x_bool {
381            F::zero()
382        } else {
383            (x2 - x1).inverse().unwrap()
384        }
385    });
386
387    let s = w.exists({
388        if same_x_bool {
389            let x1_squared = x1.square();
390            (x1_squared + x1_squared + x1_squared) / (y1 + y1)
391        } else {
392            (y2 - y1) / (x2 - x1)
393        }
394    });
395
396    let x3 = w.exists(s.square() - (x1 + x2));
397    let y3 = w.exists(s * (x1 - x3) - y1);
398
399    make_group::<F>(x3, y3)
400}
401
402fn fold_map<T, Acc, U>(
403    iter: impl Iterator<Item = T>,
404    init: Acc,
405    mut fun: impl FnMut(Acc, T) -> (Acc, U),
406) -> (Acc, Vec<U>) {
407    let mut acc = Some(init);
408    let result = iter
409        .map(|x| {
410            let (new_acc, y) = fun(acc.take().unwrap(), x);
411            acc = Some(new_acc);
412            y
413        })
414        .collect::<Vec<_>>();
415    (acc.unwrap(), result)
416}
417
418pub mod plonk_curve_ops {
419    use crate::proofs::public_input::plonk_checks::ShiftingValue;
420
421    use super::*;
422
423    const BITS_PER_CHUNK: usize = 5;
424
425    // TODO: `scalar` is a `F::Scalar` here
426    pub fn scale_fast<F, F2, const NBITS: usize>(
427        base: GroupAffine<F>,
428        shifted_value: F2::Shifting,
429        w: &mut Witness<F>,
430    ) -> GroupAffine<F>
431    where
432        F: FieldWitness,
433        F2: FieldWitness,
434    {
435        let (r, _bits) = scale_fast_unpack::<F, F2, NBITS>(base, shifted_value, w);
436        r
437    }
438
439    // TODO: `scalar` is a `F::Scalar` here
440    // <https://github.com/openmina/mina/blob/8f83199a92faa8ff592b7ae5ad5b3236160e8c20/src/lib/pickles/plonk_curve_ops.ml#L140>
441    pub fn scale_fast_unpack<F, F2, const NBITS: usize>(
442        base: GroupAffine<F>,
443        shifted: F2::Shifting,
444        w: &mut Witness<F>,
445    ) -> (GroupAffine<F>, [bool; NBITS])
446    where
447        F: FieldWitness,
448        F2: FieldWitness,
449    {
450        let scalar = shifted.shifted_raw();
451        let GroupAffine::<F> {
452            x: x_base,
453            y: y_base,
454            ..
455        } = base;
456
457        let chunks: usize = NBITS / BITS_PER_CHUNK;
458        assert_eq!(NBITS % BITS_PER_CHUNK, 0);
459
460        let bits_msb: [bool; NBITS] = w.exists(bits_msb::<F2, NBITS>(scalar));
461        let mut acc = w.add_fast(base, base);
462        let mut n_acc = F::zero();
463
464        for chunk in 0..chunks {
465            let bs: [bool; BITS_PER_CHUNK] =
466                std::array::from_fn(|i| bits_msb[(chunk * BITS_PER_CHUNK) + i]);
467
468            let n_acc_prev = n_acc;
469
470            n_acc = w.exists(
471                bs.iter()
472                    .fold(n_acc_prev, |acc, b| acc.double() + F::from(*b)),
473            );
474
475            let (_, v) = fold_map(bs.iter(), acc, |acc, b| {
476                let GroupAffine::<F> {
477                    x: x_acc, y: y_acc, ..
478                } = acc;
479                let b: F = F::from(*b);
480
481                let s1: F =
482                    w.exists((y_acc - (y_base * (b.double() - F::one()))) / (x_acc - x_base));
483                let s1_squared = w.exists(s1.square());
484                let s2 = w.exists((y_acc.double() / (x_acc.double() + x_base - s1_squared)) - s1);
485
486                let x_res = w.exists(x_base + s2.square() - s1_squared);
487                let y_res = w.exists(((x_acc - x_res) * s2) - y_acc);
488                let acc = make_group(x_res, y_res);
489
490                (acc, (acc, s1))
491            });
492
493            let (mut accs, _slopes): (Vec<_>, Vec<_>) = v.into_iter().unzip();
494
495            accs.insert(0, acc);
496            acc = accs.last().cloned().unwrap();
497        }
498
499        let bits_lsb = {
500            let mut bits_msb = bits_msb;
501            bits_msb.reverse();
502            bits_msb
503        };
504
505        (acc, bits_lsb)
506    }
507}
508
509#[derive(Clone, Debug, PartialEq, Eq)]
510pub struct PlonkVerificationKeyEvals<F: FieldWitness> {
511    pub sigma: [InnerCurve<F>; 7],
512    pub coefficients: [InnerCurve<F>; 15],
513    pub generic: InnerCurve<F>,
514    pub psm: InnerCurve<F>,
515    pub complete_add: InnerCurve<F>,
516    pub mul: InnerCurve<F>,
517    pub emul: InnerCurve<F>,
518    pub endomul_scalar: InnerCurve<F>,
519}
520
521impl<'de> serde::Deserialize<'de> for PlonkVerificationKeyEvals<Fp> {
522    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
523    where
524        D: serde::Deserializer<'de>,
525    {
526        match v2::MinaBaseVerificationKeyWireStableV1WrapIndex::deserialize(deserializer) {
527            Ok(value) => value.try_into().map_err(serde::de::Error::custom),
528            Err(e) => Err(e),
529        }
530    }
531}
532
533impl serde::Serialize for PlonkVerificationKeyEvals<Fp> {
534    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
535    where
536        S: serde::Serializer,
537    {
538        let v: v2::MinaBaseVerificationKeyWireStableV1WrapIndex = self.into();
539        v.serialize(serializer)
540    }
541}
542
543// Here cvars are not used correctly, but it's just temporary
544#[derive(Clone, Debug)]
545pub struct CircuitPlonkVerificationKeyEvals<F: FieldWitness> {
546    pub sigma: [CircuitVar<GroupAffine<F>>; 7],
547    pub coefficients: [CircuitVar<GroupAffine<F>>; 15],
548    pub generic: CircuitVar<GroupAffine<F>>,
549    pub psm: CircuitVar<GroupAffine<F>>,
550    pub complete_add: CircuitVar<GroupAffine<F>>,
551    pub mul: CircuitVar<GroupAffine<F>>,
552    pub emul: CircuitVar<GroupAffine<F>>,
553    pub endomul_scalar: CircuitVar<GroupAffine<F>>,
554}
555
556impl CircuitPlonkVerificationKeyEvals<Fp> {
557    pub fn to_non_cvar(&self) -> PlonkVerificationKeyEvals<Fp> {
558        let Self {
559            sigma,
560            coefficients,
561            generic,
562            psm,
563            complete_add,
564            mul,
565            emul,
566            endomul_scalar,
567        } = self;
568
569        let c = |c: &CircuitVar<GroupAffine<Fp>>| InnerCurve::<Fp>::of_affine(*c.value());
570
571        PlonkVerificationKeyEvals::<Fp> {
572            sigma: sigma.each_ref().map(c),
573            coefficients: coefficients.each_ref().map(c),
574            generic: c(generic),
575            psm: c(psm),
576            complete_add: c(complete_add),
577            mul: c(mul),
578            emul: c(emul),
579            endomul_scalar: c(endomul_scalar),
580        }
581    }
582}
583
584impl PlonkVerificationKeyEvals<Fp> {
585    pub fn to_cvar(
586        &self,
587        cvar: impl Fn(GroupAffine<Fp>) -> CircuitVar<GroupAffine<Fp>>,
588    ) -> CircuitPlonkVerificationKeyEvals<Fp> {
589        let Self {
590            sigma,
591            coefficients,
592            generic,
593            psm,
594            complete_add,
595            mul,
596            emul,
597            endomul_scalar,
598        } = self;
599
600        let cvar = |c: &InnerCurve<Fp>| cvar(c.to_affine());
601
602        CircuitPlonkVerificationKeyEvals::<Fp> {
603            sigma: sigma.each_ref().map(cvar),
604            coefficients: coefficients.each_ref().map(cvar),
605            generic: cvar(generic),
606            psm: cvar(psm),
607            complete_add: cvar(complete_add),
608            mul: cvar(mul),
609            emul: cvar(emul),
610            endomul_scalar: cvar(endomul_scalar),
611        }
612    }
613
614    /// For debugging
615    #[allow(clippy::inherent_to_string)]
616    fn to_string(&self) -> String {
617        let Self {
618            sigma,
619            coefficients,
620            generic,
621            psm,
622            complete_add,
623            mul,
624            emul,
625            endomul_scalar,
626        } = self;
627
628        let mut string = String::with_capacity(1_000);
629
630        use crate::util::FpExt;
631
632        let mut inner_to_s = |c: &InnerCurve<Fp>| {
633            let GroupAffine::<Fp> { x, y, .. } = c.to_affine();
634            string.push_str(&format!("{}\n", x.to_decimal()));
635            string.push_str(&format!("{}\n", y.to_decimal()));
636        };
637
638        sigma.iter().for_each(&mut inner_to_s);
639        coefficients.iter().for_each(&mut inner_to_s);
640        inner_to_s(generic);
641        inner_to_s(psm);
642        inner_to_s(complete_add);
643        inner_to_s(mul);
644        inner_to_s(emul);
645        inner_to_s(endomul_scalar);
646
647        string.trim().to_string()
648    }
649
650    /// For debugging
651    fn from_string(s: &str) -> Self {
652        let mut s = s.lines();
653
654        let mut to_inner = || {
655            let a = s.next().unwrap();
656            let b = s.next().unwrap();
657
658            let a = Fp::from_str(a).unwrap();
659            let b = Fp::from_str(b).unwrap();
660
661            InnerCurve::<Fp>::of_affine(make_group(a, b))
662        };
663
664        Self {
665            sigma: std::array::from_fn(|_| to_inner()),
666            coefficients: std::array::from_fn(|_| to_inner()),
667            generic: to_inner(),
668            psm: to_inner(),
669            complete_add: to_inner(),
670            mul: to_inner(),
671            emul: to_inner(),
672            endomul_scalar: to_inner(),
673        }
674    }
675
676    pub fn rand() -> Self {
677        Self {
678            sigma: [
679                InnerCurve::rand(),
680                InnerCurve::rand(),
681                InnerCurve::rand(),
682                InnerCurve::rand(),
683                InnerCurve::rand(),
684                InnerCurve::rand(),
685                InnerCurve::rand(),
686            ],
687            coefficients: [
688                InnerCurve::rand(),
689                InnerCurve::rand(),
690                InnerCurve::rand(),
691                InnerCurve::rand(),
692                InnerCurve::rand(),
693                InnerCurve::rand(),
694                InnerCurve::rand(),
695                InnerCurve::rand(),
696                InnerCurve::rand(),
697                InnerCurve::rand(),
698                InnerCurve::rand(),
699                InnerCurve::rand(),
700                InnerCurve::rand(),
701                InnerCurve::rand(),
702                InnerCurve::rand(),
703            ],
704            generic: InnerCurve::rand(),
705            psm: InnerCurve::rand(),
706            complete_add: InnerCurve::rand(),
707            mul: InnerCurve::rand(),
708            emul: InnerCurve::rand(),
709            endomul_scalar: InnerCurve::rand(),
710        }
711    }
712}
713
714impl crate::ToInputs for PlonkVerificationKeyEvals<Fp> {
715    fn to_inputs(&self, inputs: &mut ::poseidon::hash::Inputs) {
716        let Self {
717            sigma,
718            coefficients,
719            generic,
720            psm,
721            complete_add,
722            mul,
723            emul,
724            endomul_scalar,
725        } = self;
726
727        let mut to_input = |v: &InnerCurve<Fp>| {
728            let GroupAffine::<Fp> { x, y, .. } = v.to_affine();
729            inputs.append(&x);
730            inputs.append(&y);
731        };
732
733        for s in sigma.iter() {
734            to_input(s);
735        }
736        for c in coefficients.iter() {
737            to_input(c);
738        }
739        to_input(generic);
740        to_input(psm);
741        to_input(complete_add);
742        to_input(mul);
743        to_input(emul);
744        to_input(endomul_scalar);
745    }
746}
747
748// Implementation for references
749impl<F: FieldWitness, T: Check<F>> Check<F> for &T {
750    fn check(&self, w: &mut Witness<F>) {
751        (*self).check(w)
752    }
753}
754
755impl<F: FieldWitness, T: Check<F> + Clone> Check<F> for std::borrow::Cow<'_, T> {
756    fn check(&self, w: &mut Witness<F>) {
757        let this: &T = self.as_ref();
758        this.check(w)
759    }
760}
761
762impl<F: FieldWitness> Check<F> for PlonkVerificationKeyEvals<F> {
763    fn check(&self, w: &mut Witness<F>) {
764        let Self {
765            sigma,
766            coefficients,
767            generic,
768            psm,
769            complete_add,
770            mul,
771            emul,
772            endomul_scalar,
773        } = self;
774
775        sigma.iter().for_each(|s| s.check(w));
776        coefficients.iter().for_each(|c| c.check(w));
777        generic.check(w);
778        psm.check(w);
779        complete_add.check(w);
780        mul.check(w);
781        emul.check(w);
782        endomul_scalar.check(w);
783    }
784}
785
786impl<F: FieldWitness> Check<F> for SgnStableV1 {
787    fn check(&self, _w: &mut Witness<F>) {
788        // Does not modify the witness
789    }
790}
791
792impl<F: FieldWitness> Check<F> for bool {
793    fn check(&self, _w: &mut Witness<F>) {
794        // Does not modify the witness
795    }
796}
797
798impl<F: FieldWitness> Check<F> for mina_signer::Signature {
799    fn check(&self, _w: &mut Witness<F>) {
800        // Does not modify the witness
801    }
802}
803
804impl<F: FieldWitness, T: Check<F>> Check<F> for MyCow<'_, T> {
805    fn check(&self, w: &mut Witness<F>) {
806        let this: &T = self;
807        this.check(w);
808    }
809}
810
811impl<F: FieldWitness> Check<F> for Fp {
812    fn check(&self, _w: &mut Witness<F>) {
813        // Does not modify the witness
814    }
815}
816
817impl<F: FieldWitness> Check<F> for Fq {
818    fn check(&self, _w: &mut Witness<F>) {
819        // Does not modify the witness
820    }
821}
822
823impl<F: FieldWitness, const N: usize> Check<F> for crate::address::raw::Address<N> {
824    fn check(&self, _w: &mut Witness<F>) {
825        // Does not modify the witness
826    }
827}
828
829impl<F: FieldWitness, T: Check<F>, const N: usize> Check<F> for [T; N] {
830    fn check(&self, w: &mut Witness<F>) {
831        self.iter().for_each(|v| v.check(w));
832    }
833}
834
835impl<F: FieldWitness> Check<F> for CurrencyAmountStableV1 {
836    fn check(&self, w: &mut Witness<F>) {
837        const NBITS: usize = u64::BITS as usize;
838
839        let amount: u64 = self.as_u64();
840        assert_eq!(NBITS, std::mem::size_of_val(&amount) * 8);
841
842        let amount: F = amount.into();
843        scalar_challenge::to_field_checked_prime::<F, NBITS>(amount, w);
844    }
845}
846
847impl<F: FieldWitness> Check<F> for SignedAmount {
848    fn check(&self, w: &mut Witness<F>) {
849        let Self { magnitude, sgn } = self;
850        magnitude.check(w);
851        sgn.check(w);
852    }
853}
854
855impl<F: FieldWitness, T: currency::Magnitude + Check<F>> Check<F> for currency::Signed<T> {
856    fn check(&self, w: &mut Witness<F>) {
857        let Self { magnitude, sgn } = self;
858        magnitude.check(w);
859        sgn.check(w);
860    }
861}
862
863impl<F: FieldWitness> Check<F> for MinaStateBlockchainStateValueStableV2SignedAmount {
864    fn check(&self, w: &mut Witness<F>) {
865        let Self { magnitude, sgn } = self;
866        magnitude.check(w);
867        sgn.check(w);
868    }
869}
870
871impl<F: FieldWitness> Check<F> for UnsignedExtendedUInt32StableV1 {
872    fn check(&self, w: &mut Witness<F>) {
873        let number: u32 = self.as_u32();
874        number.check(w);
875    }
876}
877
878impl<F: FieldWitness> Check<F> for u32 {
879    fn check(&self, w: &mut Witness<F>) {
880        const NBITS: usize = u32::BITS as usize;
881
882        let number: u32 = *self;
883        assert_eq!(NBITS, std::mem::size_of_val(&number) * 8);
884
885        let number: F = number.into();
886        scalar_challenge::to_field_checked_prime::<F, NBITS>(number, w);
887    }
888}
889
890impl<F: FieldWitness> Check<F> for MinaStateBlockchainStateValueStableV2LedgerProofStatementSource {
891    fn check(&self, w: &mut Witness<F>) {
892        let Self {
893            first_pass_ledger: _,
894            second_pass_ledger: _,
895            pending_coinbase_stack: _,
896            local_state:
897                MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1 {
898                    stack_frame: _,
899                    call_stack: _,
900                    transaction_commitment: _,
901                    full_transaction_commitment: _,
902                    excess,
903                    supply_increase,
904                    ledger: _,
905                    success,
906                    account_update_index,
907                    failure_status_tbl: _,
908                    will_succeed,
909                },
910        } = self;
911
912        excess.check(w);
913        supply_increase.check(w);
914        success.check(w);
915        account_update_index.check(w);
916        will_succeed.check(w);
917    }
918}
919
920impl<F: FieldWitness> Check<F> for Registers {
921    fn check(&self, w: &mut Witness<F>) {
922        let Self {
923            first_pass_ledger: _,
924            second_pass_ledger: _,
925            pending_coinbase_stack: _,
926            local_state:
927                LocalState {
928                    stack_frame: _,
929                    call_stack: _,
930                    transaction_commitment: _,
931                    full_transaction_commitment: _,
932                    excess,
933                    supply_increase,
934                    ledger: _,
935                    success,
936                    account_update_index,
937                    failure_status_tbl: _,
938                    will_succeed,
939                },
940        } = self;
941
942        excess.check(w);
943        supply_increase.check(w);
944        success.check(w);
945        account_update_index.check(w);
946        will_succeed.check(w);
947    }
948}
949
950impl<F: FieldWitness> Check<F> for MinaStateBlockchainStateValueStableV2LedgerProofStatement {
951    fn check(&self, w: &mut Witness<F>) {
952        let Self {
953            source,
954            target,
955            connecting_ledger_left: _,
956            connecting_ledger_right: _,
957            supply_increase,
958            fee_excess,
959            sok_digest: _,
960        } = self;
961
962        source.check(w);
963        target.check(w);
964        supply_increase.check(w);
965        fee_excess.check(w);
966    }
967}
968
969impl<F: FieldWitness, T> Check<F> for Statement<T> {
970    fn check(&self, w: &mut Witness<F>) {
971        let Self {
972            source,
973            target,
974            connecting_ledger_left: _,
975            connecting_ledger_right: _,
976            supply_increase,
977            fee_excess,
978            sok_digest: _,
979        } = self;
980
981        source.check(w);
982        target.check(w);
983        supply_increase.check(w);
984        fee_excess.check(w);
985    }
986}
987
988impl<F: FieldWitness> Check<F> for MinaBaseFeeExcessStableV1 {
989    fn check(&self, w: &mut Witness<F>) {
990        let Self(
991            TokenFeeExcess {
992                token: _fee_token_l,
993                amount: fee_excess_l,
994            },
995            TokenFeeExcess {
996                token: _fee_token_r,
997                amount: fee_excess_r,
998            },
999        ) = self;
1000
1001        fee_excess_l.check(w);
1002        fee_excess_r.check(w);
1003    }
1004}
1005
1006impl<F: FieldWitness> Check<F> for FeeExcess {
1007    fn check(&self, w: &mut Witness<F>) {
1008        let Self {
1009            fee_token_l: _,
1010            fee_excess_l,
1011            fee_token_r: _,
1012            fee_excess_r,
1013        } = self;
1014
1015        fee_excess_l.check(w);
1016        fee_excess_r.check(w);
1017    }
1018}
1019
1020impl<F: FieldWitness> Check<F> for UnsignedExtendedUInt64Int64ForVersionTagsStableV1 {
1021    fn check(&self, w: &mut Witness<F>) {
1022        const NBITS: usize = u64::BITS as usize;
1023
1024        let number: u64 = self.as_u64();
1025        assert_eq!(NBITS, std::mem::size_of_val(&number) * 8);
1026
1027        let number: F = number.into();
1028        scalar_challenge::to_field_checked_prime::<F, NBITS>(number, w);
1029    }
1030}
1031
1032impl<F: FieldWitness> Check<F> for MinaNumbersGlobalSlotSinceGenesisMStableV1 {
1033    fn check(&self, w: &mut Witness<F>) {
1034        let Self::SinceGenesis(global_slot) = self;
1035        global_slot.check(w);
1036    }
1037}
1038
1039impl<F: FieldWitness> Check<F> for MinaNumbersGlobalSlotSinceHardForkMStableV1 {
1040    fn check(&self, w: &mut Witness<F>) {
1041        let Self::SinceHardFork(global_slot) = self;
1042        global_slot.check(w);
1043    }
1044}
1045
1046impl<F: FieldWitness> Check<F>
1047    for ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1
1048{
1049    fn check(&self, w: &mut Witness<F>) {
1050        let Self {
1051            ledger:
1052                MinaBaseEpochLedgerValueStableV1 {
1053                    hash: _,
1054                    total_currency,
1055                },
1056            seed: _,
1057            start_checkpoint: _,
1058            lock_checkpoint: _,
1059            epoch_length,
1060        } = self;
1061
1062        total_currency.check(w);
1063        epoch_length.check(w);
1064    }
1065}
1066
1067impl<F: FieldWitness> Check<F>
1068    for ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1
1069{
1070    fn check(&self, w: &mut Witness<F>) {
1071        let Self {
1072            ledger:
1073                MinaBaseEpochLedgerValueStableV1 {
1074                    hash: _,
1075                    total_currency,
1076                },
1077            seed: _,
1078            start_checkpoint: _,
1079            lock_checkpoint: _,
1080            epoch_length,
1081        } = self;
1082
1083        total_currency.check(w);
1084        epoch_length.check(w);
1085    }
1086}
1087
1088impl<F: FieldWitness> Check<F>
1089    for crate::scan_state::transaction_logic::protocol_state::EpochData<Fp>
1090{
1091    fn check(&self, w: &mut Witness<F>) {
1092        let Self {
1093            ledger:
1094                crate::scan_state::transaction_logic::protocol_state::EpochLedger {
1095                    hash: _,
1096                    total_currency,
1097                },
1098            seed: _,
1099            start_checkpoint: _,
1100            lock_checkpoint: _,
1101            epoch_length,
1102        } = self;
1103
1104        total_currency.check(w);
1105        epoch_length.check(w);
1106    }
1107}
1108
1109impl Check<Fp> for crate::proofs::block::consensus::GlobalSlot {
1110    fn check(&self, w: &mut Witness<Fp>) {
1111        let Self {
1112            slot_number,
1113            slots_per_epoch,
1114        } = self;
1115
1116        slot_number.check(w);
1117        slots_per_epoch.check(w);
1118    }
1119}
1120
1121impl<F: FieldWitness> Check<F> for super::block::BlockchainState {
1122    fn check(&self, w: &mut Witness<F>) {
1123        let Self {
1124            staged_ledger_hash: _,
1125            genesis_ledger_hash: _,
1126            ledger_proof_statement:
1127                Statement {
1128                    source,
1129                    target,
1130                    connecting_ledger_left: _,
1131                    connecting_ledger_right: _,
1132                    supply_increase,
1133                    fee_excess,
1134                    sok_digest: _,
1135                },
1136            timestamp,
1137            body_reference: _,
1138        } = self;
1139
1140        source.check(w);
1141        target.check(w);
1142        supply_increase.check(w);
1143        fee_excess.check(w);
1144        timestamp.check(w);
1145    }
1146}
1147
1148impl Check<Fp> for super::block::ProtocolStateBody {
1149    fn check(&self, w: &mut Witness<Fp>) {
1150        let Self {
1151            genesis_state_hash: _,
1152            blockchain_state,
1153            consensus_state:
1154                super::block::consensus::ConsensusState {
1155                    blockchain_length,
1156                    epoch_count,
1157                    min_window_density,
1158                    sub_window_densities,
1159                    last_vrf_output: _,
1160                    total_currency,
1161                    curr_global_slot_since_hard_fork,
1162                    global_slot_since_genesis,
1163                    staking_epoch_data,
1164                    next_epoch_data,
1165                    has_ancestor_in_same_checkpoint_window,
1166                    block_stake_winner: _,
1167                    block_creator: _,
1168                    coinbase_receiver: _,
1169                    supercharge_coinbase,
1170                },
1171            constants:
1172                MinaBaseProtocolConstantsCheckedValueStableV1 {
1173                    k,
1174                    slots_per_epoch,
1175                    slots_per_sub_window,
1176                    grace_period_slots,
1177                    delta,
1178                    genesis_state_timestamp,
1179                },
1180        } = self;
1181
1182        blockchain_state.check(w);
1183
1184        blockchain_length.check(w);
1185        epoch_count.check(w);
1186        min_window_density.check(w);
1187        // TODO: Check/assert that length equal `constraint_constants.sub_windows_per_window`
1188        for sub_window_density in sub_window_densities {
1189            sub_window_density.check(w);
1190        }
1191        total_currency.check(w);
1192        curr_global_slot_since_hard_fork.check(w);
1193        global_slot_since_genesis.check(w);
1194        staking_epoch_data.check(w);
1195        next_epoch_data.check(w);
1196        has_ancestor_in_same_checkpoint_window.check(w);
1197        supercharge_coinbase.check(w);
1198        k.check(w);
1199        slots_per_epoch.check(w);
1200        slots_per_sub_window.check(w);
1201        grace_period_slots.check(w);
1202        delta.check(w);
1203        genesis_state_timestamp.check(w);
1204    }
1205}
1206
1207/// Rust calls:
1208/// <https://github.com/openmina/mina/blob/8f83199a92faa8ff592b7ae5ad5b3236160e8c20/src/lib/crypto/kimchi_bindings/stubs/src/projective.rs>
1209/// Conversion to/from OCaml:
1210/// <https://github.com/openmina/mina/blob/8f83199a92faa8ff592b7ae5ad5b3236160e8c20/src/lib/crypto/kimchi_bindings/stubs/src/arkworks/group_projective.rs>
1211/// Typ:
1212/// <https://github.com/o1-labs/snarky/blob/7edf13628872081fd7cad154de257dad8b9ba621/snarky_curve/snarky_curve.ml#L219-L229>
1213///
1214#[derive(
1215    Clone,
1216    derive_more::Add,
1217    derive_more::Sub,
1218    derive_more::Neg,
1219    derive_more::Mul,
1220    derive_more::Div,
1221    PartialEq,
1222    Eq,
1223)]
1224pub struct InnerCurve<F: FieldWitness> {
1225    // ProjectivePallas
1226    // ProjectiveVesta
1227    inner: F::Projective,
1228}
1229
1230impl<F: FieldWitness> std::fmt::Debug for InnerCurve<F> {
1231    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1232        // OCaml uses `to_affine_exn` when those are printed using `sexp`
1233        // <https://github.com/openmina/mina/blob/8f83199a92faa8ff592b7ae5ad5b3236160e8c20/src/lib/snark_params/snark_params.ml#L149>
1234        let GroupAffine::<F> { x, y, .. } = self.to_affine();
1235        f.debug_struct("InnerCurve")
1236            .field("x", &x)
1237            .field("y", &y)
1238            .finish()
1239    }
1240}
1241
1242impl crate::ToInputs for InnerCurve<Fp> {
1243    fn to_inputs(&self, inputs: &mut ::poseidon::hash::Inputs) {
1244        let GroupAffine::<Fp> { x, y, .. } = self.to_affine();
1245        inputs.append_field(x);
1246        inputs.append_field(y);
1247    }
1248}
1249
1250impl<F: FieldWitness> From<(F, F)> for InnerCurve<F> {
1251    fn from((x, y): (F, F)) -> Self {
1252        Self::of_affine(make_group(x, y))
1253    }
1254}
1255
1256impl<F: FieldWitness> InnerCurve<F> {
1257    pub fn one() -> Self {
1258        let inner = F::Projective::generator();
1259        Self { inner }
1260    }
1261
1262    fn double(&self) -> Self {
1263        Self {
1264            inner: self.inner.double(),
1265        }
1266    }
1267
1268    fn scale<S>(&self, scale: S) -> Self
1269    where
1270        S: Into<BigInteger256>,
1271    {
1272        let scale: BigInteger256 = scale.into();
1273        let inner = self.inner.mul_bigint(scale);
1274        Self { inner }
1275    }
1276
1277    fn add_fast(&self, other: Self, w: &mut Witness<F>) -> Self {
1278        let result = w.add_fast(self.to_affine(), other.to_affine());
1279        Self::of_affine(result)
1280    }
1281
1282    pub fn to_affine(&self) -> GroupAffine<F> {
1283        // Both `affine` below are the same type, but we use `into()` to make it non-generic
1284        let affine: F::Affine = self.inner.into_affine();
1285        let affine: GroupAffine<F> = affine.into();
1286        // OCaml panics on infinity
1287        // <https://github.com/MinaProtocol/mina/blob/3e58e92ea9aeddb41ad3b6e494279891c5f9aa09/src/lib/crypto/kimchi_backend/common/curve.ml#L180>
1288        assert!(!affine.infinity);
1289        affine
1290    }
1291
1292    pub fn of_affine(affine: GroupAffine<F>) -> Self {
1293        // Both `inner` below are the same type, but we use `into()` to make it generic
1294        let inner: Projective<F::Parameters> = affine.into_group();
1295        let inner: F::Projective = inner.into();
1296        Self { inner }
1297    }
1298
1299    pub fn random() -> Self {
1300        // NOTE: Not random in `cfg(test)`
1301        let mut rng = get_rng();
1302
1303        // Both `proj` below are the same type, but we use `into()` to make it generic
1304        let proj: Projective<F::Parameters> = ark_ff::UniformRand::rand(&mut rng);
1305        let proj: F::Projective = proj.into();
1306
1307        let proj2 = proj;
1308        LATEST_RANDOM.set(Box::new(move || {
1309            let this = Self { inner: proj2 };
1310            format!("{:#?}", this.to_affine())
1311        }));
1312
1313        Self { inner: proj }
1314    }
1315}
1316
1317use poly_commitment::SRS;
1318use std::cell::RefCell;
1319
1320thread_local! {
1321    static LATEST_RANDOM: RefCell<Box<dyn Fn() -> String>> = RefCell::new(Box::new(String::new));
1322}
1323
1324impl InnerCurve<Fp> {
1325    // TODO: Remove this
1326    pub fn rand() -> Self {
1327        let kp = gen_keypair();
1328        let point = kp.public.into_point();
1329        assert!(point.is_on_curve());
1330        Self::of_affine(point)
1331    }
1332}
1333
1334/// <https://github.com/openmina/mina/blob/45c195d72aa8308fcd9fc1c7bc5da36a0c3c3741/src/lib/snarky_curves/snarky_curves.ml#L267>
1335pub fn create_shifted_inner_curve<F>(w: &mut Witness<F>) -> InnerCurve<F>
1336where
1337    F: FieldWitness,
1338{
1339    w.exists(InnerCurve::<F>::random())
1340}
1341
1342impl<F: FieldWitness> Check<F> for InnerCurve<F> {
1343    // <https://github.com/openmina/mina/blob/8f83199a92faa8ff592b7ae5ad5b3236160e8c20/src/lib/snarky_curves/snarky_curves.ml#L167>
1344    fn check(&self, w: &mut Witness<F>) {
1345        self.to_affine().check(w);
1346    }
1347}
1348
1349impl<F: FieldWitness> Check<F> for GroupAffine<F> {
1350    // <https://github.com/openmina/mina/blob/8f83199a92faa8ff592b7ae5ad5b3236160e8c20/src/lib/snarky_curves/snarky_curves.ml#L167>
1351    fn check(&self, w: &mut Witness<F>) {
1352        let GroupAffine::<F> { x, y: _, .. } = self;
1353        let x2 = field::square(*x, w);
1354        let _x3 = field::mul(x2, *x, w);
1355        // Rest of the function doesn't modify witness
1356    }
1357}
1358
1359impl<F: FieldWitness> Check<F> for transaction_union_payload::Tag {
1360    fn check(&self, _w: &mut Witness<F>) {
1361        // Does not modify the witness
1362        // Note: For constraints we need to convert to unpacked union
1363        // <https://github.com/openmina/mina/blob/45c195d72aa8308fcd9fc1c7bc5da36a0c3c3741/src/lib/mina_base/transaction_union_tag.ml#L177>
1364    }
1365}
1366
1367impl<F: FieldWitness> Check<F> for transaction_union_payload::TransactionUnion {
1368    fn check(&self, w: &mut Witness<F>) {
1369        use transaction_union_payload::{Body, Common, TransactionUnionPayload};
1370
1371        let Self {
1372            payload:
1373                TransactionUnionPayload {
1374                    common:
1375                        Common {
1376                            fee,
1377                            fee_token: _,
1378                            fee_payer_pk: _,
1379                            nonce,
1380                            valid_until,
1381                            memo: _,
1382                        },
1383                    body:
1384                        Body {
1385                            tag,
1386                            source_pk: _,
1387                            receiver_pk: _,
1388                            token_id: _,
1389                            amount,
1390                        },
1391                },
1392            signer: _,
1393            signature: _,
1394        } = self;
1395
1396        fee.check(w);
1397        nonce.check(w);
1398        valid_until.check(w);
1399        tag.check(w);
1400        amount.check(w);
1401    }
1402}
1403
1404impl<F: FieldWitness> Check<F> for pending_coinbase::StateStack {
1405    fn check(&self, _w: &mut Witness<F>) {
1406        // Does not modify the witness
1407    }
1408}
1409
1410impl<F: FieldWitness> Check<F> for pending_coinbase::Stack {
1411    fn check(&self, _w: &mut Witness<F>) {
1412        // Does not modify the witness
1413    }
1414}
1415
1416impl<F: FieldWitness> Check<F> for TokenSymbol {
1417    fn check(&self, w: &mut Witness<F>) {
1418        let field: F = self.to_field();
1419        scalar_challenge::to_field_checked_prime::<F, 48>(field, w);
1420    }
1421}
1422
1423impl<F: FieldWitness> Check<F> for Box<Account> {
1424    fn check(&self, w: &mut Witness<F>) {
1425        let Account {
1426            public_key: _,
1427            token_id: _,
1428            token_symbol,
1429            balance,
1430            nonce,
1431            receipt_chain_hash: _,
1432            delegate: _,
1433            voting_for: _,
1434            timing,
1435            permissions,
1436            zkapp: _,
1437        } = &**self;
1438
1439        token_symbol.check(w);
1440        balance.check(w);
1441        nonce.check(w);
1442        timing.check(w);
1443        permissions.check(w);
1444    }
1445}
1446
1447impl<F: FieldWitness> Check<F> for crate::Timing {
1448    fn check(&self, w: &mut Witness<F>) {
1449        let TimingAsRecord {
1450            is_timed: _,
1451            initial_minimum_balance,
1452            cliff_time,
1453            cliff_amount,
1454            vesting_period,
1455            vesting_increment,
1456        } = self.to_record();
1457
1458        initial_minimum_balance.check(w);
1459        cliff_time.check(w);
1460        cliff_amount.check(w);
1461        vesting_period.check(w);
1462        vesting_increment.check(w);
1463    }
1464}
1465
1466impl<F: FieldWitness> Check<F> for crate::MerklePath {
1467    fn check(&self, _w: &mut Witness<F>) {
1468        // Does not modify the witness
1469    }
1470}
1471
1472impl<F: FieldWitness, T: Check<F>> Check<F> for Vec<T> {
1473    fn check(&self, w: &mut Witness<F>) {
1474        self.iter().for_each(|v| v.check(w))
1475    }
1476}
1477
1478impl<F: FieldWitness, T: Check<F>> Check<F> for Box<[T]> {
1479    fn check(&self, w: &mut Witness<F>) {
1480        self.iter().for_each(|v| v.check(w))
1481    }
1482}
1483
1484impl<F: FieldWitness, A: Check<F>, B: Check<F>> Check<F> for (A, B) {
1485    fn check(&self, w: &mut Witness<F>) {
1486        let (a, b) = self;
1487        a.check(w);
1488        b.check(w);
1489    }
1490}
1491
1492impl<F: FieldWitness> Check<F> for ReceiptChainHash {
1493    fn check(&self, _w: &mut Witness<F>) {
1494        // Does not modify the witness
1495    }
1496}
1497
1498impl<F: FieldWitness> Check<F> for Sgn {
1499    fn check(&self, _w: &mut Witness<F>) {
1500        // Does not modify the witness
1501    }
1502}
1503
1504impl<F: FieldWitness> Check<F> for CompressedPubKey {
1505    fn check(&self, _w: &mut Witness<F>) {
1506        // Does not modify the witness
1507    }
1508}
1509
1510impl<F: FieldWitness> Check<F> for TokenId {
1511    fn check(&self, _w: &mut Witness<F>) {
1512        // Does not modify the witness
1513    }
1514}
1515
1516impl<F: FieldWitness> Check<F> for v2::MinaBasePendingCoinbaseStackVersionedStableV1 {
1517    fn check(&self, _w: &mut Witness<F>) {
1518        // Does not modify the witness
1519    }
1520}
1521
1522impl<F: FieldWitness> Check<F> for &[AllEvals<F>] {
1523    fn check(&self, _w: &mut Witness<F>) {
1524        // Does not modify the witness
1525    }
1526}
1527
1528pub fn dummy_constraints<F>(w: &mut Witness<F>)
1529where
1530    F: FieldWitness,
1531{
1532    use crate::proofs::public_input::plonk_checks::ShiftingValue;
1533
1534    let x: F = w.exists(F::from(3u64));
1535    let g: InnerCurve<F> = w.exists(InnerCurve::<F>::one());
1536
1537    let _ = w.to_field_checked_prime::<16>(x);
1538
1539    // TODO: Fix `F, F` below
1540    let _ = plonk_curve_ops::scale_fast::<F, F, 5>(g.to_affine(), F::Shifting::of_raw(x), w);
1541    let _ = plonk_curve_ops::scale_fast::<F, F, 5>(g.to_affine(), F::Shifting::of_raw(x), w);
1542    let _ = scalar_challenge::endo::<F, F, 4>(g.to_affine(), x, w);
1543}
1544
1545pub mod legacy_input {
1546    use crate::scan_state::transaction_logic::transaction_union_payload::{
1547        Body, Common, TransactionUnionPayload,
1548    };
1549    use ::poseidon::hash::legacy;
1550
1551    use super::*;
1552
1553    pub struct BitsIterator<const N: usize> {
1554        pub index: usize,
1555        pub number: [u8; N],
1556    }
1557
1558    impl<const N: usize> Iterator for BitsIterator<N> {
1559        type Item = bool;
1560
1561        fn next(&mut self) -> Option<Self::Item> {
1562            let index = self.index;
1563            self.index += 1;
1564
1565            let limb_index = index / 8;
1566            let bit_index = index % 8;
1567
1568            let limb = self.number.get(limb_index)?;
1569            Some(limb & (1 << bit_index) != 0)
1570        }
1571    }
1572
1573    pub fn bits_iter<N: Into<u64>, const NBITS: usize>(number: N) -> impl Iterator<Item = bool> {
1574        let number: u64 = number.into();
1575        BitsIterator {
1576            index: 0,
1577            number: number.to_ne_bytes(),
1578        }
1579        .take(NBITS)
1580    }
1581
1582    pub fn to_bits<N: Into<u64>, const NBITS: usize>(number: N) -> [bool; NBITS] {
1583        let mut iter = bits_iter::<N, NBITS>(number);
1584        std::array::from_fn(|_| iter.next().unwrap())
1585    }
1586
1587    pub trait CheckedLegacyInput<F: FieldWitness> {
1588        fn to_checked_legacy_input(&self, inputs: &mut legacy::Inputs<F>, w: &mut Witness<F>);
1589
1590        fn to_checked_legacy_input_owned(&self, w: &mut Witness<F>) -> legacy::Inputs<F> {
1591            let mut inputs = legacy::Inputs::new();
1592            self.to_checked_legacy_input(&mut inputs, w);
1593            inputs
1594        }
1595    }
1596
1597    const LEGACY_DEFAULT_TOKEN: [bool; 64] = {
1598        let mut default = [false; 64];
1599        default[0] = true;
1600        default
1601    };
1602
1603    impl CheckedLegacyInput<Fp> for TransactionUnionPayload {
1604        fn to_checked_legacy_input(&self, inputs: &mut legacy::Inputs<Fp>, w: &mut Witness<Fp>) {
1605            let Self {
1606                common:
1607                    Common {
1608                        fee,
1609                        fee_payer_pk,
1610                        nonce,
1611                        valid_until,
1612                        memo,
1613                        fee_token: _,
1614                    },
1615                body:
1616                    Body {
1617                        tag,
1618                        source_pk,
1619                        receiver_pk,
1620                        token_id: _,
1621                        amount,
1622                    },
1623            } = self;
1624
1625            let fee_token = &LEGACY_DEFAULT_TOKEN;
1626
1627            // Common
1628            let nonce = w.exists(nonce.to_bits());
1629            let valid_until = w.exists(valid_until.to_bits());
1630            let fee = w.exists(fee.to_bits());
1631            inputs.append_bits(&fee);
1632            inputs.append_bits(fee_token);
1633            inputs.append_field(fee_payer_pk.x);
1634            inputs.append_bit(fee_payer_pk.is_odd);
1635            inputs.append_bits(&nonce);
1636            inputs.append_bits(&valid_until);
1637            inputs.append_bits(&memo.to_bits());
1638
1639            // Body
1640            let amount = w.exists(amount.to_bits());
1641            inputs.append_bits(&tag.to_bits());
1642            inputs.append_field(source_pk.x);
1643            inputs.append_bit(source_pk.is_odd);
1644            inputs.append_field(receiver_pk.x);
1645            inputs.append_bit(receiver_pk.is_odd);
1646            inputs.append_bits(fee_token);
1647            inputs.append_bits(&amount);
1648            inputs.append_bit(false);
1649        }
1650    }
1651}
1652
1653pub mod poseidon {
1654    use std::marker::PhantomData;
1655
1656    use ::poseidon::{PlonkSpongeConstantsKimchi, SpongeConstants, SpongeParams, SpongeState};
1657
1658    use super::*;
1659
1660    #[derive(Clone)]
1661    pub struct Sponge<F: FieldWitness, C: SpongeConstants = PlonkSpongeConstantsKimchi> {
1662        pub state: [F; 3],
1663        pub sponge_state: SpongeState,
1664        params: &'static SpongeParams<F>,
1665        nabsorb: usize,
1666        _constants: PhantomData<C>,
1667    }
1668
1669    impl<F, C> Default for Sponge<F, C>
1670    where
1671        F: FieldWitness,
1672        C: SpongeConstants,
1673    {
1674        fn default() -> Self {
1675            Self::new()
1676        }
1677    }
1678
1679    impl<F, C> Sponge<F, C>
1680    where
1681        F: FieldWitness,
1682        C: SpongeConstants,
1683    {
1684        pub fn new_with_state_params(state: [F; 3], params: &'static SpongeParams<F>) -> Self {
1685            Self {
1686                state,
1687                sponge_state: SpongeState::Absorbed(0),
1688                params,
1689                nabsorb: 0,
1690                _constants: PhantomData,
1691            }
1692        }
1693
1694        pub fn new_with_state(state: [F; 3]) -> Self {
1695            Self::new_with_state_params(state, F::get_params())
1696        }
1697
1698        pub fn new() -> Self {
1699            Self::new_with_state([F::zero(); 3])
1700        }
1701
1702        fn absorb_empty(&mut self, w: &mut Witness<F>) {
1703            match self.sponge_state {
1704                SpongeState::Absorbed(n) => {
1705                    if n == C::SPONGE_RATE {
1706                        self.poseidon_block_cipher(true, w);
1707                        self.sponge_state = SpongeState::Absorbed(1);
1708                    } else {
1709                        self.sponge_state = SpongeState::Absorbed(n + 1);
1710                    }
1711                }
1712                SpongeState::Squeezed(_n) => {
1713                    self.sponge_state = SpongeState::Absorbed(1);
1714                }
1715            }
1716        }
1717
1718        pub fn absorb(&mut self, x: &[F], w: &mut Witness<F>) {
1719            if x.is_empty() {
1720                self.absorb_empty(w);
1721                return;
1722            }
1723
1724            // Hack to know when to ignore witness
1725            // That should be removed once we use `cvar`
1726            let mut first = true;
1727
1728            for x in x.iter() {
1729                match self.sponge_state {
1730                    SpongeState::Absorbed(n) => {
1731                        if n == C::SPONGE_RATE {
1732                            // eprintln!("Sponge::Absorbed_A({})", n);
1733                            self.poseidon_block_cipher(first, w);
1734                            self.sponge_state = SpongeState::Absorbed(1);
1735                            self.state[0].add_assign(x);
1736                            w.exists(self.state[0]); // Good
1737                            first = false;
1738                        } else {
1739                            // eprintln!("Sponge::Absorbed_B({})", n);
1740                            self.sponge_state = SpongeState::Absorbed(n + 1);
1741                            self.state[n].add_assign(x);
1742                            w.exists(self.state[n]); // Good
1743                        }
1744                    }
1745                    SpongeState::Squeezed(_n) => {
1746                        self.state[0].add_assign(x);
1747                        w.exists(self.state[0]); // Unknown
1748                        self.sponge_state = SpongeState::Absorbed(1);
1749                    }
1750                }
1751            }
1752        }
1753
1754        pub fn absorb2(&mut self, x: &[F], w: &mut Witness<F>) {
1755            if x.is_empty() {
1756                self.absorb_empty(w);
1757                return;
1758            }
1759
1760            // Hack to know when to ignore witness
1761            // That should be removed once we use `cvar`
1762            let mut first = true;
1763
1764            for x in x.iter() {
1765                match self.sponge_state {
1766                    SpongeState::Absorbed(n) => {
1767                        if n == C::SPONGE_RATE {
1768                            // eprintln!("Sponge::Absorbed2_A({})", n);
1769                            self.poseidon_block_cipher(first, w);
1770                            self.sponge_state = SpongeState::Absorbed(1);
1771                            self.state[0].add_assign(x);
1772                            w.exists(self.state[0]); // Good
1773                            first = false;
1774                        } else {
1775                            // eprintln!("Sponge::Absorbed2_B({})", n);
1776                            self.sponge_state = SpongeState::Absorbed(n + 1);
1777                            self.state[n].add_assign(x);
1778                            if self.nabsorb > 2 {
1779                                w.exists(self.state[n]); // Good
1780                            }
1781                        }
1782                    }
1783                    SpongeState::Squeezed(_n) => {
1784                        // eprintln!("Sponge::Squeezed({})", _n);
1785                        self.state[0].add_assign(x);
1786                        w.exists(self.state[0]); // Unknown
1787                        self.sponge_state = SpongeState::Absorbed(1);
1788                    }
1789                }
1790                self.nabsorb += 1;
1791            }
1792        }
1793
1794        pub fn absorb3(&mut self, x: &[F], w: &mut Witness<F>) {
1795            if x.is_empty() {
1796                self.absorb_empty(w);
1797                return;
1798            }
1799
1800            // Hack to know when to ignore witness
1801            // That should be removed once we use `cvar`
1802            let mut first = true;
1803
1804            for x in x.iter() {
1805                match self.sponge_state {
1806                    SpongeState::Absorbed(n) => {
1807                        if n == C::SPONGE_RATE {
1808                            // eprintln!("Sponge::Absorbed2_A({})", n);
1809                            self.poseidon_block_cipher(first, w);
1810                            self.sponge_state = SpongeState::Absorbed(1);
1811                            self.state[0].add_assign(x);
1812                            if self.nabsorb > 2 {
1813                                w.exists(self.state[0]); // Good
1814                            }
1815                            first = false;
1816                        } else {
1817                            // eprintln!("Sponge::Absorbed2_B({})", n);
1818                            self.sponge_state = SpongeState::Absorbed(n + 1);
1819                            self.state[n].add_assign(x);
1820                            if self.nabsorb > 2 {
1821                                w.exists(self.state[n]); // Good
1822                            }
1823                        }
1824                    }
1825                    SpongeState::Squeezed(_n) => {
1826                        // eprintln!("Sponge::Squeezed({})", _n);
1827                        self.state[0].add_assign(x);
1828                        w.exists(self.state[0]); // Unknown
1829                        self.sponge_state = SpongeState::Absorbed(1);
1830                    }
1831                }
1832                self.nabsorb += 1;
1833            }
1834        }
1835
1836        pub fn squeeze(&mut self, w: &mut Witness<F>) -> F {
1837            match self.sponge_state {
1838                SpongeState::Squeezed(n) => {
1839                    if n == C::SPONGE_RATE {
1840                        self.poseidon_block_cipher(false, w);
1841                        self.sponge_state = SpongeState::Squeezed(1);
1842                        self.state[0]
1843                    } else {
1844                        self.sponge_state = SpongeState::Squeezed(n + 1);
1845                        self.state[n]
1846                    }
1847                }
1848                SpongeState::Absorbed(_n) => {
1849                    self.poseidon_block_cipher(false, w);
1850                    self.sponge_state = SpongeState::Squeezed(1);
1851                    self.state[0]
1852                }
1853            }
1854        }
1855
1856        pub fn poseidon_block_cipher(&mut self, first: bool, w: &mut Witness<F>) {
1857            if C::PERM_HALF_ROUNDS_FULL == 0 {
1858                if C::PERM_INITIAL_ARK {
1859                    // legacy
1860
1861                    for (i, x) in self.params.round_constants[0].iter().enumerate() {
1862                        self.state[i].add_assign(x);
1863                    }
1864                    w.exists(self.state[0]); // Good
1865                    w.exists(self.state[1]); // Good
1866                    if !first {
1867                        w.exists(self.state[2]); // Good
1868                    }
1869                    // dbg!(&state, &params.round_constants[0]);
1870                    for r in 0..C::PERM_ROUNDS_FULL {
1871                        self.full_round(r + 1, first && r == 0, w);
1872                    }
1873                } else {
1874                    // non-legacy
1875
1876                    w.exists(self.state);
1877                    for r in 0..C::PERM_ROUNDS_FULL {
1878                        self.full_round(r, first, w);
1879                    }
1880                }
1881            } else {
1882                unimplemented!()
1883            }
1884        }
1885
1886        pub fn full_round(&mut self, r: usize, first: bool, w: &mut Witness<F>) {
1887            for (index, state_i) in self.state.iter_mut().enumerate() {
1888                let push_witness = !(first && index == 2);
1889                *state_i = sbox::<F, C>(*state_i, push_witness, w);
1890            }
1891            self.state = apply_mds_matrix::<F, C>(self.params, &self.state);
1892            for (i, x) in self.params.round_constants[r].iter().enumerate() {
1893                self.state[i].add_assign(x);
1894                if C::PERM_SBOX == 5 {
1895                    // legacy
1896                    w.exists(self.state[i]); // Good
1897                }
1898            }
1899            if C::PERM_SBOX == 7 {
1900                // non-legacy
1901                w.exists(self.state);
1902            }
1903        }
1904    }
1905
1906    pub fn sbox<F: FieldWitness, C: SpongeConstants>(
1907        x: F,
1908        push_witness: bool,
1909        w: &mut Witness<F>,
1910    ) -> F {
1911        if C::PERM_SBOX == 5 {
1912            // legacy
1913
1914            let res = x;
1915            let res = res * res;
1916            if push_witness {
1917                w.exists(res); // Good
1918            }
1919            let res = res * res;
1920            if push_witness {
1921                w.exists(res); // Good
1922            }
1923            let res = res * x;
1924            if push_witness {
1925                w.exists(res); // Good
1926            }
1927            res
1928        } else if C::PERM_SBOX == 7 {
1929            // non-legacy
1930
1931            let mut res = x.square();
1932            res *= x;
1933            let res = res.square();
1934            res * x
1935        } else {
1936            unimplemented!()
1937        }
1938    }
1939
1940    fn apply_mds_matrix<F: Field, C: SpongeConstants>(
1941        params: &SpongeParams<F>,
1942        state: &[F; 3],
1943    ) -> [F; 3] {
1944        if C::PERM_FULL_MDS {
1945            std::array::from_fn(|i| {
1946                state
1947                    .iter()
1948                    .zip(params.mds[i].iter())
1949                    .fold(F::zero(), |x, (s, &m)| m * s + x)
1950            })
1951        } else {
1952            [
1953                state[0] + state[2],
1954                state[0] + state[1],
1955                state[1] + state[2],
1956            ]
1957        }
1958    }
1959}
1960
1961fn double_group<F: FieldWitness>(group: GroupAffine<F>, w: &mut Witness<F>) -> GroupAffine<F> {
1962    let GroupAffine::<F> { x: ax, y: ay, .. } = group;
1963    let ax: F = ax;
1964    let ay: F = ay;
1965
1966    let x_squared = w.exists(ax.square());
1967    let lambda = w.exists({
1968        (x_squared + x_squared + x_squared + F::PARAMS.a) * (ay + ay).inverse().unwrap()
1969    });
1970    let bx = w.exists(lambda.square() - (ax + ax));
1971    let by = w.exists((lambda * (ax - bx)) - ay);
1972
1973    make_group(bx, by)
1974}
1975
1976// Used as the _if method
1977fn group_to_witness<F: FieldWitness>(group: GroupAffine<F>, w: &mut Witness<F>) -> GroupAffine<F> {
1978    // We don't want to call `GroupAffine::check` here
1979    let GroupAffine::<F> { x, y, .. } = &group;
1980    w.exists(*x);
1981    w.exists(*y);
1982    group
1983}
1984
1985pub fn scale_non_constant<F: FieldWitness, const N: usize>(
1986    mut g: GroupAffine<F>,
1987    bits: &[bool; N],
1988    init: &InnerCurve<F>,
1989    w: &mut Witness<F>,
1990) -> GroupAffine<F> {
1991    let mut acc = init.to_affine();
1992
1993    for b in bits {
1994        acc = {
1995            let add_pt = w.add_fast(acc, g);
1996            let dont_add_pt = acc;
1997            if *b {
1998                group_to_witness(add_pt, w)
1999            } else {
2000                group_to_witness(dont_add_pt, w)
2001            }
2002        };
2003        g = double_group(g, w);
2004    }
2005
2006    acc
2007}
2008
2009fn lookup_point<F: FieldWitness>(
2010    (b0, b1): (bool, bool),
2011    (t1, t2, t3, t4): (InnerCurve<F>, InnerCurve<F>, InnerCurve<F>, InnerCurve<F>),
2012    w: &mut Witness<F>,
2013) -> (F, F) {
2014    // This doesn't push to the witness, except for the `b0_and_b1`
2015
2016    let b0_and_b1 = w.exists(F::from(b0 && b1));
2017    let b0 = F::from(b0);
2018    let b1 = F::from(b1);
2019    let lookup_one = |a1: F, a2: F, a3: F, a4: F| -> F {
2020        a1 + ((a2 - a1) * b0) + ((a3 - a1) * b1) + ((a4 + a1 - a2 - a3) * b0_and_b1)
2021    };
2022    let GroupAffine::<F> { x: x1, y: y1, .. } = t1.to_affine();
2023    let GroupAffine::<F> { x: x2, y: y2, .. } = t2.to_affine();
2024    let GroupAffine::<F> { x: x3, y: y3, .. } = t3.to_affine();
2025    let GroupAffine::<F> { x: x4, y: y4, .. } = t4.to_affine();
2026
2027    (lookup_one(x1, x2, x3, x4), lookup_one(y1, y2, y3, y4))
2028}
2029
2030fn lookup_single_bit<F: FieldWitness>(b: bool, (t1, t2): (InnerCurve<F>, InnerCurve<F>)) -> (F, F) {
2031    let lookup_one = |a1: F, a2: F| a1 + (F::from(b) * (a2 - a1));
2032
2033    let GroupAffine::<F> { x: x1, y: y1, .. } = t1.to_affine();
2034    let GroupAffine::<F> { x: x2, y: y2, .. } = t2.to_affine();
2035
2036    (lookup_one(x1, x2), lookup_one(y1, y2))
2037}
2038
2039pub fn scale_known<F: FieldWitness, const N: usize>(
2040    t: GroupAffine<F>,
2041    bits: &[bool; N],
2042    init: &InnerCurve<F>,
2043    w: &mut Witness<F>,
2044) -> GroupAffine<F> {
2045    let sigma = InnerCurve::of_affine(t);
2046    let n = bits.len();
2047    let sigma_count = n.div_ceil(2);
2048
2049    let to_term = |two_to_the_i: InnerCurve<F>,
2050                   two_to_the_i_plus_1: InnerCurve<F>,
2051                   bits: (bool, bool),
2052                   w: &mut Witness<F>| {
2053        let sigma0 = sigma.clone();
2054        let sigma1 = sigma.clone();
2055        let sigma2 = sigma.clone();
2056        let sigma3 = sigma.clone();
2057        lookup_point(
2058            bits,
2059            (
2060                sigma0,
2061                (sigma1 + two_to_the_i.clone()),
2062                (sigma2 + two_to_the_i_plus_1.clone()),
2063                (sigma3 + two_to_the_i + two_to_the_i_plus_1),
2064            ),
2065            w,
2066        )
2067    };
2068
2069    let mut acc = init.to_affine();
2070    let mut two_to_the_i = sigma.clone();
2071    for chunk in bits.chunks(2) {
2072        match chunk {
2073            [b_i] => {
2074                let (term_x, term_y) =
2075                    lookup_single_bit(*b_i, (sigma.clone(), sigma.clone() + two_to_the_i.clone()));
2076                let [term_y, term_x] = w.exists([term_y, term_x]);
2077                acc = w.add_fast(acc, make_group(term_x, term_y));
2078            }
2079            [b_i, b_i_plus_1] => {
2080                let two_to_the_i_plus_1 = two_to_the_i.double().to_affine();
2081                let (term_x, term_y) = to_term(
2082                    two_to_the_i.clone(),
2083                    InnerCurve::of_affine(two_to_the_i_plus_1),
2084                    (*b_i, *b_i_plus_1),
2085                    w,
2086                );
2087                let [term_y, term_x] = w.exists([term_y, term_x]);
2088                acc = w.add_fast(acc, make_group(term_x, term_y));
2089                two_to_the_i = InnerCurve::of_affine(two_to_the_i_plus_1).double();
2090            }
2091            _ => unreachable!(), // chunks of 2
2092        }
2093    }
2094
2095    let result_with_shift = acc;
2096    let unshift = std::ops::Neg::neg(sigma).scale(sigma_count as u64);
2097
2098    w.add_fast(result_with_shift, unshift.to_affine())
2099}
2100
2101#[derive(Debug)]
2102enum ExprBinary<T> {
2103    Lit(T),
2104    And(T, Box<ExprBinary<T>>),
2105    Or(T, Box<ExprBinary<T>>),
2106}
2107
2108#[derive(Debug)]
2109enum ExprNary<T> {
2110    Lit(T),
2111    And(Vec<ExprNary<T>>),
2112    Or(Vec<ExprNary<T>>),
2113}
2114
2115fn lt_binary<F: FieldWitness>(xs: &[bool], ys: &[bool]) -> ExprBinary<Boolean> {
2116    match (xs, ys) {
2117        ([], []) => ExprBinary::Lit(Boolean::False),
2118        ([_x], [false]) => ExprBinary::Lit(Boolean::False),
2119        ([x], [true]) => ExprBinary::Lit(x.to_boolean().neg()),
2120        ([x1, _x2], [true, false]) => ExprBinary::Lit(x1.to_boolean().neg()),
2121        ([_x1, _x2], [false, false]) => ExprBinary::Lit(Boolean::False),
2122        ([x, xs @ ..], [false, ys @ ..]) => {
2123            ExprBinary::And(x.to_boolean().neg(), Box::new(lt_binary::<F>(xs, ys)))
2124        }
2125        ([x, xs @ ..], [true, ys @ ..]) => {
2126            ExprBinary::Or(x.to_boolean().neg(), Box::new(lt_binary::<F>(xs, ys)))
2127        }
2128        _ => panic!("unequal length"),
2129    }
2130}
2131
2132fn of_binary<F: FieldWitness>(expr: &ExprBinary<Boolean>) -> ExprNary<Boolean> {
2133    match expr {
2134        ExprBinary::Lit(x) => ExprNary::Lit(*x),
2135        ExprBinary::And(x, t) => match &**t {
2136            ExprBinary::And(y, t) => ExprNary::And(vec![
2137                ExprNary::Lit(*x),
2138                ExprNary::Lit(*y),
2139                of_binary::<F>(t),
2140            ]),
2141            _ => ExprNary::And(vec![ExprNary::Lit(*x), of_binary::<F>(t)]),
2142        },
2143        ExprBinary::Or(x, t) => match &**t {
2144            ExprBinary::Or(y, t) => ExprNary::Or(vec![
2145                ExprNary::Lit(*x),
2146                ExprNary::Lit(*y),
2147                of_binary::<F>(t),
2148            ]),
2149            _ => ExprNary::Or(vec![ExprNary::Lit(*x), of_binary::<F>(t)]),
2150        },
2151    }
2152}
2153
2154impl ExprNary<Boolean> {
2155    fn eval<F: FieldWitness>(&self, w: &mut Witness<F>) -> Boolean {
2156        match self {
2157            ExprNary::Lit(x) => *x,
2158            ExprNary::And(xs) => {
2159                let xs: Vec<_> = xs.iter().map(|x| Self::eval::<F>(x, w)).collect();
2160                Boolean::all::<F>(&xs, w)
2161            }
2162            ExprNary::Or(xs) => {
2163                let xs: Vec<_> = xs.iter().map(|x| Self::eval::<F>(x, w)).collect();
2164                Boolean::any::<F>(&xs, w)
2165            }
2166        }
2167    }
2168}
2169
2170fn lt_bitstring_value<F: FieldWitness>(
2171    xs: &[bool; 255],
2172    ys: &[bool; 255],
2173    w: &mut Witness<F>,
2174) -> Boolean {
2175    let value = of_binary::<F>(&lt_binary::<F>(xs, ys));
2176    value.eval(w)
2177}
2178
2179fn unpack_full<F: FieldWitness>(x: F, w: &mut Witness<F>) -> [bool; 255] {
2180    let bits_lsb = w.exists(field_to_bits::<F, 255>(x));
2181
2182    let bits_msb = {
2183        let mut bits = bits_lsb;
2184        bits.reverse(); // msb
2185        bits
2186    };
2187
2188    let size_msb = {
2189        let mut size = bigint_to_bits::<255>(F::SIZE);
2190        size.reverse(); // msb
2191        size
2192    };
2193
2194    lt_bitstring_value::<F>(&bits_msb, &size_msb, w);
2195
2196    bits_lsb
2197}
2198
2199fn is_even<F: FieldWitness>(y: F, w: &mut Witness<F>) -> Boolean {
2200    let bits_msb = {
2201        let mut bits = w.exists(field_to_bits::<F, 255>(y));
2202        bits.reverse(); // msb
2203        bits
2204    };
2205
2206    let size_msb = {
2207        let mut size = bigint_to_bits::<255>(F::SIZE);
2208        size.reverse(); // msb
2209        size
2210    };
2211
2212    lt_bitstring_value::<F>(&bits_msb, &size_msb, w)
2213}
2214
2215pub struct CompressedPubKeyVar<F: FieldWitness> {
2216    pub x: F,
2217    pub is_odd: bool,
2218}
2219
2220pub fn compress_var<F: FieldWitness>(
2221    v: &GroupAffine<F>,
2222    w: &mut Witness<F>,
2223) -> CompressedPubKeyVar<F> {
2224    let GroupAffine::<F> { x, y, .. } = v;
2225
2226    let is_odd = {
2227        let bits = unpack_full(*y, w);
2228        bits[0]
2229    };
2230
2231    CompressedPubKeyVar { x: *x, is_odd }
2232}
2233
2234pub fn decompress_var(pk: &CompressedPubKey, w: &mut Witness<Fp>) -> PubKey {
2235    let CompressedPubKey { x, is_odd: _ } = pk;
2236    let GroupAffine::<Fp> { y, .. } = decompress_pk(pk).unwrap().into_point();
2237
2238    w.exists(y);
2239
2240    let point = make_group(*x, y);
2241    point.check(w);
2242
2243    let _is_odd2 = {
2244        let bits = unpack_full(y, w);
2245        bits[0]
2246    };
2247    PubKey::from_point_unsafe(point)
2248}
2249
2250pub mod transaction_snark {
2251    use std::ops::Neg;
2252
2253    use crate::{
2254        checked_equal_compressed_key, checked_equal_compressed_key_const_and,
2255        checked_verify_merkle_path,
2256        proofs::{
2257            block::ProtocolStateBody,
2258            numbers::{
2259                currency::{
2260                    CheckedAmount, CheckedBalance, CheckedCurrency, CheckedFee, CheckedSigned,
2261                },
2262                nat::{CheckedNat, CheckedSlot, CheckedSlotSpan},
2263            },
2264            transaction::legacy_input::CheckedLegacyInput,
2265        },
2266        scan_state::{
2267            fee_excess::CheckedFeeExcess,
2268            transaction_logic::{checked_cons_signed_command_payload, Coinbase},
2269        },
2270        sparse_ledger::SparseLedger,
2271        zkapps::interfaces::{SignedAmountBranchParam, SignedAmountInterface},
2272        AccountId, PermissionTo, PermsConst, Timing, TimingAsRecordChecked, ToInputs,
2273    };
2274    use ::poseidon::hash::{params::MINA_PROTO_STATE_BODY, Inputs, LazyParam};
2275    use ark_ff::Zero;
2276
2277    use crate::scan_state::{
2278        currency,
2279        transaction_logic::transaction_union_payload::{TransactionUnion, TransactionUnionPayload},
2280    };
2281    use ::poseidon::hash::legacy;
2282    use mina_core::constants::constraint_constants;
2283    use mina_signer::Signature;
2284
2285    use super::*;
2286
2287    mod user_command_failure {
2288        use crate::scan_state::{
2289            currency::Magnitude,
2290            transaction_logic::{
2291                timing_error_to_user_command_status, validate_timing, TransactionFailure,
2292            },
2293        };
2294
2295        use super::*;
2296
2297        const NUM_FIELDS: usize = 8;
2298
2299        pub struct Failure {
2300            pub predicate_failed: bool,                 // User commands
2301            pub source_not_present: bool,               // User commands
2302            pub receiver_not_present: bool,             // Delegate
2303            pub amount_insufficient_to_create: bool,    // Payment only
2304            pub token_cannot_create: bool,              // Payment only, token<>default
2305            pub source_insufficient_balance: bool,      // Payment only
2306            pub source_minimum_balance_violation: bool, // Payment only
2307            pub source_bad_timing: bool,                // Payment only
2308        }
2309
2310        impl<F: FieldWitness> ToFieldElements<F> for Failure {
2311            fn to_field_elements(&self, fields: &mut Vec<F>) {
2312                let list = self.to_list();
2313                list.to_field_elements(fields)
2314            }
2315        }
2316
2317        impl<F: FieldWitness> Check<F> for Failure {
2318            fn check(&self, _w: &mut Witness<F>) {
2319                // Nothing
2320            }
2321        }
2322
2323        impl Failure {
2324            fn empty() -> Self {
2325                Self {
2326                    predicate_failed: false,
2327                    source_not_present: false,
2328                    receiver_not_present: false,
2329                    amount_insufficient_to_create: false,
2330                    token_cannot_create: false,
2331                    source_insufficient_balance: false,
2332                    source_minimum_balance_violation: false,
2333                    source_bad_timing: false,
2334                }
2335            }
2336
2337            pub fn to_list(&self) -> [Boolean; NUM_FIELDS] {
2338                let Self {
2339                    predicate_failed,
2340                    source_not_present,
2341                    receiver_not_present,
2342                    amount_insufficient_to_create,
2343                    token_cannot_create,
2344                    source_insufficient_balance,
2345                    source_minimum_balance_violation,
2346                    source_bad_timing,
2347                } = self;
2348
2349                [
2350                    predicate_failed.to_boolean(),
2351                    source_not_present.to_boolean(),
2352                    receiver_not_present.to_boolean(),
2353                    amount_insufficient_to_create.to_boolean(),
2354                    token_cannot_create.to_boolean(),
2355                    source_insufficient_balance.to_boolean(),
2356                    source_minimum_balance_violation.to_boolean(),
2357                    source_bad_timing.to_boolean(),
2358                ]
2359            }
2360        }
2361
2362        pub fn compute_as_prover<F: FieldWitness>(
2363            txn_global_slot: CheckedSlot<F>,
2364            txn: &TransactionUnion,
2365            sparse_ledger: &SparseLedger,
2366            w: &mut Witness<F>,
2367        ) -> Failure {
2368            w.exists(compute_as_prover_impl(
2369                txn_global_slot.to_inner(),
2370                txn,
2371                sparse_ledger,
2372            ))
2373        }
2374
2375        /// NOTE: Unchecked computation
2376        // TODO: Returns errors instead of panics
2377        fn compute_as_prover_impl(
2378            txn_global_slot: currency::Slot,
2379            txn: &TransactionUnion,
2380            sparse_ledger: &SparseLedger,
2381        ) -> Failure {
2382            use transaction_union_payload::Tag::*;
2383
2384            let _fee_token = &txn.payload.common.fee_token;
2385            let token = &txn.payload.body.token_id;
2386            let fee_payer =
2387                AccountId::create(txn.payload.common.fee_payer_pk.clone(), token.clone());
2388            let source = AccountId::create(txn.payload.body.source_pk.clone(), token.clone());
2389            let receiver = AccountId::create(txn.payload.body.receiver_pk.clone(), token.clone());
2390
2391            let mut fee_payer_account = sparse_ledger.get_account(&fee_payer);
2392            let source_account = sparse_ledger.get_account(&source);
2393            let receiver_account = sparse_ledger.get_account(&receiver);
2394
2395            // compute_unchecked
2396            let TransactionUnion {
2397                payload,
2398                signer: _,
2399                signature: _,
2400            } = txn;
2401
2402            if let FeeTransfer | Coinbase = payload.body.tag {
2403                return Failure::empty();
2404            };
2405
2406            fee_payer_account.balance = fee_payer_account
2407                .balance
2408                .sub_amount(currency::Amount::of_fee(&payload.common.fee))
2409                .unwrap();
2410
2411            let predicate_failed = if payload.common.fee_payer_pk == payload.body.source_pk {
2412                false
2413            } else {
2414                match payload.body.tag {
2415                    Payment | StakeDelegation => true,
2416                    FeeTransfer | Coinbase => panic!(), // Checked above
2417                }
2418            };
2419
2420            match payload.body.tag {
2421                FeeTransfer | Coinbase => panic!(), // Checked above
2422                StakeDelegation => {
2423                    let receiver_account = if receiver == fee_payer {
2424                        &fee_payer_account
2425                    } else {
2426                        &receiver_account
2427                    };
2428
2429                    let receiver_not_present = {
2430                        let id = receiver_account.id();
2431                        if id.is_empty() {
2432                            true
2433                        } else if receiver == id {
2434                            false
2435                        } else {
2436                            panic!("bad receiver account ID")
2437                        }
2438                    };
2439
2440                    let source_account = if source == fee_payer {
2441                        &fee_payer_account
2442                    } else {
2443                        &source_account
2444                    };
2445
2446                    let source_not_present = {
2447                        let id = source_account.id();
2448                        if id.is_empty() {
2449                            true
2450                        } else if source == id {
2451                            false
2452                        } else {
2453                            panic!("bad source account ID")
2454                        }
2455                    };
2456
2457                    Failure {
2458                        predicate_failed,
2459                        source_not_present,
2460                        receiver_not_present,
2461                        amount_insufficient_to_create: false,
2462                        token_cannot_create: false,
2463                        source_insufficient_balance: false,
2464                        source_minimum_balance_violation: false,
2465                        source_bad_timing: false,
2466                    }
2467                }
2468                Payment => {
2469                    let receiver_account = if receiver == fee_payer {
2470                        &fee_payer_account
2471                    } else {
2472                        &receiver_account
2473                    };
2474
2475                    let receiver_needs_creating = {
2476                        let id = receiver_account.id();
2477                        if id.is_empty() {
2478                            true
2479                        } else if id == receiver {
2480                            false
2481                        } else {
2482                            panic!("bad receiver account ID");
2483                        }
2484                    };
2485
2486                    let token_is_default = true;
2487                    let token_cannot_create = receiver_needs_creating && !token_is_default;
2488
2489                    let amount_insufficient_to_create = {
2490                        let creation_amount =
2491                            currency::Amount::from_u64(constraint_constants().account_creation_fee);
2492                        receiver_needs_creating
2493                            && payload.body.amount.checked_sub(&creation_amount).is_none()
2494                    };
2495
2496                    let fee_payer_is_source = fee_payer == source;
2497                    let source_account = if fee_payer_is_source {
2498                        &fee_payer_account
2499                    } else {
2500                        &source_account
2501                    };
2502
2503                    let source_not_present = {
2504                        let id = source_account.id();
2505                        if id.is_empty() {
2506                            true
2507                        } else if source == id {
2508                            false
2509                        } else {
2510                            panic!("bad source account ID");
2511                        }
2512                    };
2513
2514                    let source_insufficient_balance = !fee_payer_is_source
2515                        && if source == receiver {
2516                            receiver_needs_creating
2517                        } else {
2518                            source_account.balance.to_amount() < payload.body.amount
2519                        };
2520
2521                    let timing_or_error =
2522                        validate_timing(source_account, payload.body.amount, &txn_global_slot);
2523
2524                    let source_minimum_balance_violation = matches!(
2525                        timing_error_to_user_command_status(timing_or_error.clone()),
2526                        Err(TransactionFailure::SourceMinimumBalanceViolation),
2527                    );
2528
2529                    let source_bad_timing = !fee_payer_is_source
2530                        && !source_insufficient_balance
2531                        && timing_or_error.is_err();
2532
2533                    Failure {
2534                        predicate_failed,
2535                        source_not_present,
2536                        receiver_not_present: false,
2537                        amount_insufficient_to_create,
2538                        token_cannot_create,
2539                        source_insufficient_balance,
2540                        source_minimum_balance_violation,
2541                        source_bad_timing,
2542                    }
2543                }
2544            }
2545        }
2546    }
2547
2548    pub fn checked_legacy_hash(
2549        param: &LazyParam,
2550        inputs: legacy::Inputs<Fp>,
2551        w: &mut Witness<Fp>,
2552    ) -> Fp {
2553        use ::poseidon::{fp_legacy::params, PlonkSpongeConstantsLegacy as Constants};
2554
2555        let initial_state: [Fp; 3] = param.state();
2556        let mut sponge =
2557            poseidon::Sponge::<Fp, Constants>::new_with_state_params(initial_state, params());
2558        sponge.absorb(&inputs.to_fields(), w);
2559        sponge.squeeze(w)
2560    }
2561
2562    pub fn checked_hash(param: &LazyParam, inputs: &[Fp], w: &mut Witness<Fp>) -> Fp {
2563        let initial_state: [Fp; 3] = param.state();
2564        let mut sponge = poseidon::Sponge::<Fp>::new_with_state(initial_state);
2565        sponge.absorb(inputs, w);
2566        sponge.squeeze(w)
2567    }
2568
2569    pub fn checked_hash3(param: &LazyParam, inputs: &[Fp], w: &mut Witness<Fp>) -> Fp {
2570        let initial_state: [Fp; 3] = param.state();
2571        let mut sponge = poseidon::Sponge::<Fp>::new_with_state(initial_state);
2572        sponge.absorb3(inputs, w);
2573        sponge.squeeze(w)
2574    }
2575
2576    fn checked_legacy_signature_hash(
2577        mut inputs: legacy::Inputs<Fp>,
2578        signer: &PubKey,
2579        signature: &Signature,
2580        w: &mut Witness<Fp>,
2581    ) -> [bool; 255] {
2582        let GroupAffine::<Fp> { x: px, y: py, .. } = signer.point();
2583        let Signature { rx, s: _ } = signature;
2584
2585        inputs.append_field(*px);
2586        inputs.append_field(*py);
2587        inputs.append_field(*rx);
2588        let signature_prefix = mina_core::NetworkConfig::global().legacy_signature_prefix;
2589        let hash = checked_legacy_hash(signature_prefix, inputs, w);
2590
2591        w.exists(field_to_bits::<_, 255>(hash))
2592    }
2593
2594    pub fn checked_legacy_signature_verify(
2595        shifted: &InnerCurve<Fp>,
2596        signer: &PubKey,
2597        signature: &Signature,
2598        inputs: legacy::Inputs<Fp>,
2599        w: &mut Witness<Fp>,
2600    ) -> Boolean {
2601        let hash = checked_legacy_signature_hash(inputs, signer, signature, w);
2602        checked_signature_verify_impl(shifted, signer, signature, &hash, w)
2603    }
2604
2605    pub fn checked_signature_verify_impl(
2606        shifted: &InnerCurve<Fp>,
2607        signer: &PubKey,
2608        signature: &Signature,
2609        hash: &[bool; 255],
2610        w: &mut Witness<Fp>,
2611    ) -> Boolean {
2612        // negate
2613        let public_key = {
2614            let GroupAffine::<Fp> { x, y, .. } = signer.point();
2615            let y = w.exists(y.neg()); // This is actually made in the `scale` call below in OCaml
2616            make_group::<Fp>(*x, y)
2617        };
2618
2619        let e_pk = scale_non_constant::<Fp, 255>(public_key, hash, shifted, w);
2620
2621        let Signature { rx: _, s } = signature;
2622        let bits: [bool; 255] = field_to_bits::<_, 255>(*s);
2623        let one: GroupAffine<Fp> = InnerCurve::<Fp>::one().to_affine();
2624        let s_g_e_pk = scale_known(one, &bits, &InnerCurve::of_affine(e_pk), w);
2625
2626        let GroupAffine::<Fp> { x: rx, y: ry, .. } = {
2627            let neg_shifted = shifted.to_affine().neg();
2628            w.exists(neg_shifted.y);
2629            w.add_fast(neg_shifted, s_g_e_pk)
2630        };
2631
2632        let y_even = is_even(ry, w);
2633        let r_correct = field::equal(signature.rx, rx, w);
2634
2635        y_even.and(&r_correct, w)
2636    }
2637
2638    fn checked_chunked_signature_hash(
2639        mut inputs: Inputs,
2640        signer: &PubKey,
2641        signature: &Signature,
2642        w: &mut Witness<Fp>,
2643    ) -> [bool; 255] {
2644        let GroupAffine::<Fp> { x: px, y: py, .. } = signer.point();
2645        let Signature { rx, s: _ } = signature;
2646
2647        inputs.append_field(*px);
2648        inputs.append_field(*py);
2649        inputs.append_field(*rx);
2650        let signature_prefix = mina_core::NetworkConfig::global().signature_prefix;
2651        let hash = checked_hash(signature_prefix, &inputs.to_fields(), w);
2652
2653        w.exists(field_to_bits::<_, 255>(hash))
2654    }
2655
2656    pub fn checked_chunked_signature_verify(
2657        shifted: &InnerCurve<Fp>,
2658        signer: &PubKey,
2659        signature: &Signature,
2660        inputs: Inputs,
2661        w: &mut Witness<Fp>,
2662    ) -> Boolean {
2663        let hash = checked_chunked_signature_hash(inputs, signer, signature, w);
2664        checked_signature_verify_impl(shifted, signer, signature, &hash, w)
2665    }
2666
2667    fn check_signature(
2668        shifted: &InnerCurve<Fp>,
2669        payload: &TransactionUnionPayload,
2670        is_user_command: Boolean,
2671        signer: &PubKey,
2672        signature: &Signature,
2673        w: &mut Witness<Fp>,
2674    ) {
2675        let inputs = payload.to_checked_legacy_input_owned(w);
2676        let verifies = checked_legacy_signature_verify(shifted, signer, signature, inputs, w);
2677        Boolean::assert_any(&[is_user_command.neg(), verifies][..], w);
2678    }
2679
2680    fn add_burned_tokens<F: FieldWitness>(
2681        acc_burned_tokens: CheckedAmount<F>,
2682        amount: CheckedAmount<F>,
2683        is_coinbase_or_fee_transfer: Boolean,
2684        update_account: Boolean,
2685        is_const_add_flagged: bool,
2686        w: &mut Witness<F>,
2687    ) -> CheckedAmount<F> {
2688        let accumulate_burned_tokens =
2689            Boolean::all(&[is_coinbase_or_fee_transfer, update_account.neg()], w);
2690
2691        let (amt, overflow) = if is_const_add_flagged {
2692            acc_burned_tokens.const_add_flagged(&amount, w)
2693        } else {
2694            acc_burned_tokens.add_flagged(&amount, w)
2695        };
2696
2697        Boolean::assert_any(&[accumulate_burned_tokens.neg(), overflow.neg()], w);
2698
2699        w.exists_no_check(match accumulate_burned_tokens {
2700            Boolean::True => amt,
2701            Boolean::False => acc_burned_tokens,
2702        })
2703    }
2704
2705    pub fn checked_min_balance_at_slot<F: FieldWitness>(
2706        global_slot: &CheckedSlot<F>,
2707        cliff_time: &CheckedSlot<F>,
2708        cliff_amount: &CheckedAmount<F>,
2709        vesting_period: &CheckedSlotSpan<F>,
2710        vesting_increment: &CheckedAmount<F>,
2711        initial_minimum_balance: &CheckedBalance<F>,
2712        w: &mut Witness<F>,
2713    ) -> CheckedBalance<F> {
2714        let before_cliff = global_slot.less_than(cliff_time, w);
2715
2716        let else_value = {
2717            let (_, slot_diff) = global_slot.diff_or_zero(cliff_time, w);
2718
2719            let cliff_decrement = cliff_amount;
2720            let min_balance_less_cliff_decrement =
2721                initial_minimum_balance.sub_amount_or_zero(cliff_decrement, w);
2722
2723            let (num_periods, _) = slot_diff.div_mod(vesting_period, w);
2724
2725            let vesting_decrement = CheckedAmount::from_field(field::mul(
2726                num_periods.to_field(),
2727                vesting_increment.to_field(),
2728                w,
2729            ));
2730
2731            min_balance_less_cliff_decrement.sub_amount_or_zero(&vesting_decrement, w)
2732        };
2733
2734        w.exists_no_check(match before_cliff {
2735            Boolean::True => *initial_minimum_balance,
2736            Boolean::False => else_value,
2737        })
2738    }
2739
2740    pub fn check_timing<F: FieldWitness, Fun>(
2741        account: &Account,
2742        txn_amount: Option<&CheckedAmount<F>>,
2743        txn_global_slot: CheckedSlot<F>,
2744        mut timed_balance_check: Fun,
2745        w: &mut Witness<F>,
2746    ) -> (CheckedBalance<F>, Timing)
2747    where
2748        Fun: FnMut(Boolean, &mut Witness<F>),
2749    {
2750        let TimingAsRecordChecked {
2751            is_timed,
2752            initial_minimum_balance,
2753            cliff_time,
2754            cliff_amount,
2755            vesting_period,
2756            vesting_increment,
2757        } = account.timing.to_record_checked::<F>();
2758
2759        let curr_min_balance = checked_min_balance_at_slot(
2760            &txn_global_slot,
2761            &cliff_time,
2762            &cliff_amount,
2763            &vesting_period,
2764            &vesting_increment,
2765            &initial_minimum_balance,
2766            w,
2767        );
2768
2769        let account_balance = account.balance.to_checked();
2770        let proposed_balance = match txn_amount {
2771            Some(txn_amount) => {
2772                let (proposed_balance, _underflow) =
2773                    account_balance.sub_amount_flagged(txn_amount, w);
2774                proposed_balance
2775            }
2776            None => account_balance,
2777        };
2778
2779        let sufficient_timed_balance = proposed_balance.gte(&curr_min_balance, w);
2780
2781        {
2782            let ok = Boolean::any(&[is_timed.neg(), sufficient_timed_balance], w);
2783            timed_balance_check(ok, w);
2784        }
2785
2786        let is_timed_balance_zero = field::equal(curr_min_balance.to_field(), F::zero(), w);
2787
2788        let is_untimed = is_timed.neg().or(&is_timed_balance_zero, w);
2789
2790        let timing = w.exists_no_check(match is_untimed {
2791            Boolean::True => Timing::Untimed,
2792            Boolean::False => account.timing.clone(),
2793        });
2794
2795        (curr_min_balance, timing)
2796    }
2797
2798    #[allow(unused_assignments)]
2799    fn apply_tagged_transaction(
2800        shifted: &InnerCurve<Fp>,
2801        _fee_payment_root: Fp,
2802        global_slot: currency::Slot,
2803        pending_coinbase_init: &pending_coinbase::Stack,
2804        pending_coinbase_stack_before: &pending_coinbase::Stack,
2805        pending_coinbase_after: &pending_coinbase::Stack,
2806        state_body: &ProtocolStateBody,
2807        tx: &TransactionUnion,
2808        sparse_ledger: &SparseLedger,
2809        w: &mut Witness<Fp>,
2810    ) -> anyhow::Result<(
2811        Fp,
2812        CheckedSigned<Fp, CheckedAmount<Fp>>,
2813        CheckedSigned<Fp, CheckedAmount<Fp>>,
2814    )> {
2815        let TransactionUnion {
2816            payload,
2817            signer,
2818            signature,
2819        } = tx;
2820
2821        let global_slot = global_slot.to_checked();
2822
2823        let mut ledger = sparse_ledger.copy_content();
2824
2825        let tag = payload.body.tag.clone();
2826        let is_user_command = tag.is_user_command();
2827
2828        check_signature(shifted, payload, is_user_command, signer, signature, w);
2829
2830        let _signer_pk = compress_var(signer.point(), w);
2831
2832        let is_payment = tag.is_payment();
2833        let is_stake_delegation = tag.is_stake_delegation();
2834        let is_fee_transfer = tag.is_fee_transfer();
2835        let is_coinbase = tag.is_coinbase();
2836
2837        let fee_token = &payload.common.fee_token;
2838        let fee_token_default = field::equal(fee_token.0, TokenId::default().0, w);
2839
2840        let token = &payload.body.token_id;
2841        let token_default = field::equal(token.0, TokenId::default().0, w);
2842
2843        Boolean::assert_any(
2844            &[
2845                fee_token_default,
2846                is_payment,
2847                is_stake_delegation,
2848                is_fee_transfer,
2849            ],
2850            w,
2851        );
2852
2853        Boolean::assert_any(
2854            &[
2855                is_payment,
2856                is_stake_delegation,
2857                is_fee_transfer,
2858                is_coinbase,
2859            ],
2860            w,
2861        );
2862
2863        let current_global_slot = global_slot;
2864        let user_command_failure =
2865            user_command_failure::compute_as_prover(current_global_slot, tx, &ledger, w);
2866
2867        let user_command_fails = Boolean::any(&user_command_failure.to_list(), w);
2868        let fee = payload.common.fee.to_checked();
2869        let receiver = AccountId::create(payload.body.receiver_pk.clone(), token.clone());
2870        let source = AccountId::create(payload.body.source_pk.clone(), token.clone());
2871        let nonce = payload.common.nonce.to_checked();
2872        let fee_payer = AccountId::create(payload.common.fee_payer_pk.clone(), fee_token.clone());
2873
2874        fee_payer.checked_equal(&source, w);
2875        current_global_slot.lte(&payload.common.valid_until.to_checked(), w);
2876
2877        let state_body_hash = state_body.checked_hash_with_param(&MINA_PROTO_STATE_BODY, w);
2878
2879        let pending_coinbase_stack_with_state =
2880            pending_coinbase_init.checked_push_state(state_body_hash, current_global_slot, w);
2881
2882        let computed_pending_coinbase_stack_after = {
2883            let coinbase = Coinbase {
2884                receiver: receiver.public_key.clone(),
2885                amount: payload.body.amount,
2886                fee_transfer: None,
2887            };
2888
2889            let stack_prime = pending_coinbase_stack_with_state.checked_push_coinbase(coinbase, w);
2890
2891            w.exists(match is_coinbase {
2892                Boolean::True => stack_prime,
2893                Boolean::False => pending_coinbase_stack_with_state.clone(),
2894            })
2895        };
2896
2897        let _correct_coinbase_target_stack =
2898            computed_pending_coinbase_stack_after.equal_var(pending_coinbase_after, w);
2899
2900        let _valid_init_state = {
2901            let equal_source = pending_coinbase_init.equal_var(pending_coinbase_stack_before, w);
2902
2903            let equal_source_with_state =
2904                pending_coinbase_stack_with_state.equal_var(pending_coinbase_stack_before, w);
2905
2906            equal_source.or(&equal_source_with_state, w)
2907        };
2908
2909        Boolean::assert_any(&[is_user_command, user_command_fails.neg()], w);
2910
2911        let _predicate_result = {
2912            let is_own_account = checked_equal_compressed_key(
2913                &payload.common.fee_payer_pk,
2914                &payload.body.source_pk,
2915                w,
2916            );
2917            let predicate_result = Boolean::False;
2918
2919            is_own_account.const_or(&predicate_result)
2920        };
2921
2922        let account_creation_amount =
2923            currency::Amount::from_u64(constraint_constants().account_creation_fee).to_checked();
2924        let is_zero_fee = fee.equal(&CheckedFee::zero(), w);
2925
2926        let is_coinbase_or_fee_transfer = is_user_command.neg();
2927
2928        let can_create_fee_payer_account = {
2929            let fee_may_be_charged = token_default.or(&is_zero_fee, w);
2930            is_coinbase_or_fee_transfer.and(&fee_may_be_charged, w)
2931        };
2932
2933        let mut burned_tokens = CheckedAmount::<Fp>::zero();
2934        let zero_fee = CheckedSigned::zero();
2935        let mut new_account_fees = zero_fee.clone();
2936
2937        let root_after_fee_payer_update = {
2938            let index = ledger.find_index_exn(fee_payer.clone());
2939            w.exists(index.to_bits());
2940
2941            let account = ledger.get_exn(&index);
2942            let path = ledger.path_exn(index.clone());
2943
2944            let (account, path) = w.exists((account, path));
2945            checked_verify_merkle_path(&account, &path, w);
2946
2947            // filter
2948            let is_empty_and_writeable = {
2949                let is_writable = can_create_fee_payer_account;
2950                let account_already_there = account.id().checked_equal(&fee_payer, w);
2951                let account_not_there = checked_equal_compressed_key_const_and(
2952                    &account.public_key,
2953                    &CompressedPubKey::empty(),
2954                    w,
2955                );
2956                let not_there_but_writeable = account_not_there.and(&is_writable, w);
2957                Boolean::assert_any(&[account_already_there, not_there_but_writeable], w);
2958                not_there_but_writeable
2959            };
2960
2961            // f
2962            let next = {
2963                // Why OCaml doesn't push value here ?
2964                let next_nonce = match is_user_command {
2965                    Boolean::True => account.nonce.incr().to_checked::<Fp>(),
2966                    Boolean::False => account.nonce.to_checked(),
2967                };
2968
2969                let account_nonce = account.nonce.to_checked();
2970                let nonce_matches = nonce.equal(&account_nonce, w);
2971                Boolean::assert_any(&[is_user_command.neg(), nonce_matches], w);
2972
2973                let current = &account.receipt_chain_hash;
2974                let r = checked_cons_signed_command_payload(payload, current.clone(), w);
2975                let receipt_chain_hash = w.exists(match is_user_command {
2976                    Boolean::True => r,
2977                    Boolean::False => current.clone(),
2978                });
2979
2980                let _permitted_to_access = account.checked_has_permission_to(
2981                    PermsConst {
2982                        and_const: false,
2983                        or_const: false,
2984                    },
2985                    Some(is_user_command),
2986                    PermissionTo::Access,
2987                    w,
2988                );
2989                let permitted_to_increment_nonce = account.checked_has_permission_to(
2990                    PermsConst {
2991                        and_const: true,
2992                        or_const: false,
2993                    },
2994                    None,
2995                    PermissionTo::IncrementNonce,
2996                    w,
2997                );
2998                let permitted_to_send = account.checked_has_permission_to(
2999                    PermsConst {
3000                        and_const: true,
3001                        or_const: false,
3002                    },
3003                    None,
3004                    PermissionTo::Send,
3005                    w,
3006                );
3007                let permitted_to_receive = account.checked_has_permission_to(
3008                    PermsConst {
3009                        and_const: true,
3010                        or_const: true,
3011                    },
3012                    None,
3013                    PermissionTo::Receive,
3014                    w,
3015                );
3016
3017                Boolean::assert_any(&[is_user_command.neg(), permitted_to_increment_nonce], w);
3018                Boolean::assert_any(&[is_user_command.neg(), permitted_to_send], w);
3019
3020                let update_account = {
3021                    let receiving_allowed =
3022                        Boolean::all(&[is_coinbase_or_fee_transfer, permitted_to_receive], w);
3023                    Boolean::any(&[is_user_command, receiving_allowed], w)
3024                };
3025
3026                let is_empty_and_writeable =
3027                    Boolean::all(&[is_empty_and_writeable, is_zero_fee.neg()], w);
3028
3029                let should_pay_to_create = is_empty_and_writeable;
3030
3031                let amount = {
3032                    let fee_payer_amount = {
3033                        let sgn = match is_user_command {
3034                            Boolean::True => Sgn::Neg,
3035                            Boolean::False => Sgn::Pos,
3036                        };
3037                        CheckedSigned::create(
3038                            CheckedAmount::of_fee(&fee),
3039                            CircuitVar::Constant(sgn),
3040                            None,
3041                        )
3042                    };
3043
3044                    let account_creation_fee = {
3045                        // We don't use `exists_no_check` here because both are constants
3046                        let magnitude = if should_pay_to_create.as_bool() {
3047                            account_creation_amount
3048                        } else {
3049                            CheckedAmount::zero()
3050                        };
3051                        CheckedSigned::create(magnitude, CircuitVar::Constant(Sgn::Neg), None)
3052                    };
3053
3054                    new_account_fees = account_creation_fee.clone();
3055
3056                    account_creation_fee.set_value(); // We set it because it's a Constant
3057                    fee_payer_amount.add(&account_creation_fee, w)
3058                };
3059
3060                {
3061                    let amt = add_burned_tokens::<Fp>(
3062                        burned_tokens,
3063                        CheckedAmount::of_fee(&fee),
3064                        is_coinbase_or_fee_transfer,
3065                        update_account,
3066                        true,
3067                        w,
3068                    );
3069                    burned_tokens = amt;
3070                }
3071
3072                let txn_global_slot = current_global_slot;
3073                let timing = {
3074                    let txn_amount = w.exists_no_check(match amount.sgn.value() {
3075                        Sgn::Neg => amount.magnitude,
3076                        Sgn::Pos => CheckedAmount::zero(),
3077                    });
3078
3079                    let timed_balance_check = |_ok: Boolean, _w: &mut Witness<Fp>| {};
3080
3081                    let (_, timing) = check_timing(
3082                        &account,
3083                        Some(&txn_amount),
3084                        txn_global_slot,
3085                        timed_balance_check,
3086                        w,
3087                    );
3088
3089                    w.exists_no_check(match update_account {
3090                        Boolean::True => timing,
3091                        Boolean::False => account.timing.clone(),
3092                    })
3093                };
3094
3095                let balance = {
3096                    let account_balance = account.balance.to_checked();
3097                    let updated_balance = account_balance.add_signed_amount(amount, w);
3098                    w.exists_no_check(match update_account {
3099                        Boolean::True => updated_balance,
3100                        Boolean::False => account_balance,
3101                    })
3102                };
3103                let public_key = w.exists(match is_empty_and_writeable {
3104                    Boolean::True => fee_payer.public_key.clone(),
3105                    Boolean::False => account.public_key.clone(),
3106                });
3107                let token_id = w.exists(match is_empty_and_writeable {
3108                    Boolean::True => fee_payer.token_id.clone(),
3109                    Boolean::False => account.token_id.clone(),
3110                });
3111                let delegate = w.exists(match is_empty_and_writeable {
3112                    Boolean::True => fee_payer.public_key.clone(),
3113                    Boolean::False => account
3114                        .delegate
3115                        .clone()
3116                        .unwrap_or_else(CompressedPubKey::empty),
3117                });
3118
3119                Box::new(Account {
3120                    public_key,
3121                    token_id,
3122                    token_symbol: account.token_symbol,
3123                    balance: balance.to_inner(),
3124                    nonce: next_nonce.to_inner(),
3125                    receipt_chain_hash,
3126                    delegate: if delegate == CompressedPubKey::empty() {
3127                        None
3128                    } else {
3129                        Some(delegate)
3130                    },
3131                    voting_for: account.voting_for,
3132                    timing,
3133                    permissions: account.permissions,
3134                    zkapp: account.zkapp,
3135                })
3136            };
3137
3138            ledger.set_exn(index, next.clone());
3139            checked_verify_merkle_path(&next, &path, w)
3140        };
3141
3142        let receiver_increase = {
3143            let base_amount = {
3144                let zero_transfer = is_stake_delegation;
3145                w.exists_no_check(match zero_transfer {
3146                    Boolean::True => CheckedAmount::zero(),
3147                    Boolean::False => payload.body.amount.to_checked(),
3148                })
3149            };
3150
3151            let coinbase_receiver_fee = w.exists_no_check(match is_coinbase {
3152                Boolean::True => CheckedAmount::of_fee(&fee),
3153                Boolean::False => CheckedAmount::zero(),
3154            });
3155
3156            base_amount.sub(&coinbase_receiver_fee, w)
3157        };
3158
3159        let mut receiver_overflow = Boolean::False;
3160        let mut receiver_balance_update_permitted = Boolean::True;
3161
3162        let _root_after_receiver_update = {
3163            let index = ledger.find_index_exn(receiver.clone());
3164            w.exists(index.to_bits());
3165
3166            let account = ledger.get_exn(&index);
3167            let path = ledger.path_exn(index.clone());
3168
3169            let (account, path) = w.exists((account, path));
3170            checked_verify_merkle_path(&account, &path, w);
3171
3172            // filter
3173            let is_empty_and_writeable = {
3174                let aid = &receiver;
3175                let account_already_there = account.id().checked_equal(aid, w);
3176                let account_not_there = checked_equal_compressed_key_const_and(
3177                    &account.public_key,
3178                    &CompressedPubKey::empty(),
3179                    w,
3180                );
3181
3182                Boolean::assert_any(&[account_already_there, account_not_there], w);
3183
3184                account_not_there
3185            };
3186
3187            // f
3188            let next = {
3189                let permitted_to_access = account.checked_has_permission_to(
3190                    PermsConst {
3191                        and_const: true,
3192                        or_const: true,
3193                    },
3194                    Some(Boolean::False),
3195                    PermissionTo::Access,
3196                    w,
3197                );
3198                let permitted_to_receive = account
3199                    .checked_has_permission_to(
3200                        PermsConst {
3201                            and_const: true,
3202                            or_const: true,
3203                        },
3204                        None,
3205                        PermissionTo::Receive,
3206                        w,
3207                    )
3208                    .and(&permitted_to_access, w);
3209
3210                let payment_or_internal_command =
3211                    Boolean::any(&[is_payment, is_coinbase_or_fee_transfer], w);
3212
3213                let update_account = Boolean::any(
3214                    &[payment_or_internal_command.neg(), permitted_to_receive],
3215                    w,
3216                )
3217                .and(&permitted_to_access, w);
3218
3219                receiver_balance_update_permitted = permitted_to_receive;
3220
3221                let is_empty_failure = {
3222                    let must_not_be_empty = is_stake_delegation;
3223                    is_empty_and_writeable.and(&must_not_be_empty, w)
3224                };
3225
3226                // is_empty_failure.equal(&Boolean::from_bool(user_command_failure.receiver_not_present), w);
3227
3228                let is_empty_and_writeable =
3229                    Boolean::all(&[is_empty_and_writeable, is_empty_failure.neg()], w);
3230
3231                let should_pay_to_create = is_empty_and_writeable;
3232
3233                {
3234                    let token_should_not_create = should_pay_to_create.and(&token_default.neg(), w);
3235
3236                    let _token_cannot_create = token_should_not_create.and(&is_user_command, w);
3237                }
3238
3239                let balance = {
3240                    let receiver_amount = {
3241                        let account_creation_fee = match should_pay_to_create {
3242                            Boolean::True => account_creation_amount,
3243                            Boolean::False => CheckedAmount::zero(),
3244                        };
3245
3246                        let account_creation_fee_neg =
3247                            CheckedSigned::of_unsigned(account_creation_fee).negate();
3248
3249                        account_creation_fee_neg.set_value(); // We set it because it's a Constant
3250                        new_account_fees.set_value(); // We set it because it's a Constant
3251                        let new_account_fees_total =
3252                            account_creation_fee_neg.add(&new_account_fees, w);
3253                        new_account_fees = new_account_fees_total;
3254
3255                        let (amount_for_new_account, _underflow) =
3256                            receiver_increase.sub_flagged(&account_creation_fee, w);
3257
3258                        w.exists_no_check(match user_command_fails {
3259                            Boolean::True => CheckedAmount::zero(),
3260                            Boolean::False => amount_for_new_account,
3261                        })
3262                    };
3263
3264                    let account_balance = account.balance.to_checked();
3265                    let (balance, overflow) =
3266                        account_balance.add_amount_flagged(&receiver_amount, w);
3267
3268                    Boolean::assert_any(&[is_user_command, overflow.neg()], w);
3269
3270                    receiver_overflow = overflow;
3271
3272                    w.exists_no_check(match overflow {
3273                        Boolean::True => account_balance,
3274                        Boolean::False => balance,
3275                    })
3276                };
3277
3278                {
3279                    let amt = add_burned_tokens::<Fp>(
3280                        burned_tokens,
3281                        receiver_increase,
3282                        is_coinbase_or_fee_transfer,
3283                        permitted_to_receive,
3284                        false,
3285                        w,
3286                    );
3287                    burned_tokens = amt;
3288                }
3289
3290                let user_command_fails = receiver_overflow.or(&user_command_fails, w);
3291
3292                let is_empty_and_writeable = Boolean::all(
3293                    &[
3294                        is_empty_and_writeable,
3295                        user_command_fails.neg(),
3296                        update_account,
3297                    ],
3298                    w,
3299                );
3300
3301                let balance = w.exists_no_check(match update_account {
3302                    Boolean::True => balance,
3303                    Boolean::False => account.balance.to_checked(),
3304                });
3305
3306                let may_delegate = is_empty_and_writeable.and(&token_default, w);
3307
3308                let delegate = w.exists(match may_delegate {
3309                    Boolean::True => receiver.public_key.clone(),
3310                    Boolean::False => account
3311                        .delegate
3312                        .clone()
3313                        .unwrap_or_else(CompressedPubKey::empty),
3314                });
3315
3316                let public_key = w.exists(match is_empty_and_writeable {
3317                    Boolean::True => receiver.public_key.clone(),
3318                    Boolean::False => account.public_key.clone(),
3319                });
3320
3321                let token_id = w.exists(match is_empty_and_writeable {
3322                    Boolean::True => token.clone(),
3323                    Boolean::False => account.token_id.clone(),
3324                });
3325
3326                Box::new(Account {
3327                    public_key,
3328                    token_id,
3329                    token_symbol: account.token_symbol,
3330                    balance: balance.to_inner(),
3331                    nonce: account.nonce,
3332                    receipt_chain_hash: account.receipt_chain_hash,
3333                    delegate: if delegate == CompressedPubKey::empty() {
3334                        None
3335                    } else {
3336                        Some(delegate)
3337                    },
3338                    voting_for: account.voting_for,
3339                    timing: account.timing,
3340                    permissions: account.permissions,
3341                    zkapp: account.zkapp,
3342                })
3343            };
3344
3345            ledger.set_exn(index, next.clone());
3346            checked_verify_merkle_path(&next, &path, w)
3347        };
3348
3349        let user_command_fails = receiver_overflow.or(&user_command_fails, w);
3350        let fee_payer_is_source = fee_payer.checked_equal(&source, w);
3351
3352        let root_after_source_update = {
3353            let index = ledger.find_index_exn(source.clone());
3354            w.exists(index.to_bits());
3355
3356            let account = ledger.get_exn(&index);
3357            let path = ledger.path_exn(index.clone());
3358
3359            let (account, path) = w.exists((account, path));
3360            checked_verify_merkle_path(&account, &path, w);
3361
3362            // filter
3363            let _is_empty_and_writeable = {
3364                let is_writable = user_command_failure.source_not_present.to_boolean();
3365                let account_already_there = account.id().checked_equal(&source, w);
3366                let account_not_there = checked_equal_compressed_key_const_and(
3367                    &account.public_key,
3368                    &CompressedPubKey::empty(),
3369                    w,
3370                );
3371                let not_there_but_writeable = account_not_there.and(&is_writable, w);
3372                Boolean::assert_any(&[account_already_there, not_there_but_writeable], w);
3373                not_there_but_writeable
3374            };
3375
3376            // f
3377            let next = {
3378                let bool_to_field = |b: bool| b.to_boolean().to_field::<Fp>();
3379                let _num_failures = field::const_add(
3380                    bool_to_field(user_command_failure.source_insufficient_balance),
3381                    bool_to_field(user_command_failure.source_bad_timing),
3382                );
3383                let _not_fee_payer_is_source = fee_payer_is_source.neg();
3384
3385                let permitted_to_access = account.checked_has_permission_to(
3386                    PermsConst {
3387                        and_const: false,
3388                        or_const: false,
3389                    },
3390                    Some(is_user_command),
3391                    PermissionTo::Access,
3392                    w,
3393                );
3394                let permitted_to_update_delegate = account.checked_has_permission_to(
3395                    PermsConst {
3396                        and_const: true,
3397                        or_const: false,
3398                    },
3399                    None,
3400                    PermissionTo::SetDelegate,
3401                    w,
3402                );
3403                let permitted_to_send = account.checked_has_permission_to(
3404                    PermsConst {
3405                        and_const: true,
3406                        or_const: false,
3407                    },
3408                    None,
3409                    PermissionTo::Send,
3410                    w,
3411                );
3412                let permitted_to_receive = account.checked_has_permission_to(
3413                    PermsConst {
3414                        and_const: true,
3415                        or_const: true,
3416                    },
3417                    None,
3418                    PermissionTo::Receive,
3419                    w,
3420                );
3421
3422                let payment_permitted = Boolean::all(
3423                    &[
3424                        is_payment,
3425                        permitted_to_access,
3426                        permitted_to_send,
3427                        receiver_balance_update_permitted,
3428                    ],
3429                    w,
3430                );
3431
3432                let update_account = {
3433                    let delegation_permitted =
3434                        Boolean::all(&[is_stake_delegation, permitted_to_update_delegate], w);
3435
3436                    let fee_receiver_update_permitted =
3437                        Boolean::all(&[is_coinbase_or_fee_transfer, permitted_to_receive], w);
3438
3439                    Boolean::any(
3440                        &[
3441                            payment_permitted,
3442                            delegation_permitted,
3443                            fee_receiver_update_permitted,
3444                        ],
3445                        w,
3446                    )
3447                    .and(&permitted_to_access, w)
3448                };
3449
3450                let amount = w.exists_no_check(match payment_permitted {
3451                    Boolean::True => payload.body.amount.to_checked(),
3452                    Boolean::False => CheckedAmount::zero(),
3453                });
3454
3455                let txn_global_slot = current_global_slot;
3456
3457                let timing = {
3458                    let timed_balance_check = |ok: Boolean, w: &mut Witness<Fp>| {
3459                        let _not_ok = ok.neg().and(
3460                            &user_command_failure
3461                                .source_insufficient_balance
3462                                .to_boolean(),
3463                            w,
3464                        );
3465                    };
3466
3467                    let (_, timing) = check_timing(
3468                        &account,
3469                        Some(&amount),
3470                        txn_global_slot,
3471                        timed_balance_check,
3472                        w,
3473                    );
3474
3475                    w.exists_no_check(match update_account {
3476                        Boolean::True => timing,
3477                        Boolean::False => account.timing.clone(),
3478                    })
3479                };
3480
3481                let (balance, _underflow) =
3482                    account.balance.to_checked().sub_amount_flagged(&amount, w);
3483
3484                let delegate = {
3485                    let may_delegate = Boolean::all(&[is_stake_delegation, update_account], w);
3486
3487                    w.exists(match may_delegate {
3488                        Boolean::True => receiver.public_key,
3489                        Boolean::False => account
3490                            .delegate
3491                            .clone()
3492                            .unwrap_or_else(CompressedPubKey::empty),
3493                    })
3494                };
3495
3496                Box::new(Account {
3497                    public_key: account.public_key,
3498                    token_id: account.token_id,
3499                    token_symbol: account.token_symbol,
3500                    balance: balance.to_inner(),
3501                    nonce: account.nonce,
3502                    receipt_chain_hash: account.receipt_chain_hash,
3503                    delegate: if delegate == CompressedPubKey::empty() {
3504                        None
3505                    } else {
3506                        Some(delegate)
3507                    },
3508                    voting_for: account.voting_for,
3509                    timing,
3510                    permissions: account.permissions,
3511                    zkapp: account.zkapp,
3512                })
3513            };
3514
3515            ledger.set_exn(index, next.clone());
3516            checked_verify_merkle_path(&next, &path, w)
3517        };
3518
3519        let fee_excess = {
3520            let then_value = CheckedSigned::of_unsigned(CheckedAmount::zero());
3521
3522            let else_value = {
3523                let amount_fee = CheckedAmount::of_fee(&payload.common.fee.to_checked());
3524
3525                let user_command_excess = CheckedSigned::of_unsigned(amount_fee);
3526
3527                let (fee_transfer_excess, fee_transfer_excess_overflowed) = {
3528                    let (magnitude, overflow) =
3529                        payload.body.amount.to_checked().add_flagged(&amount_fee, w);
3530                    (
3531                        CheckedSigned::create(magnitude, CircuitVar::Constant(Sgn::Neg), None),
3532                        overflow,
3533                    )
3534                };
3535
3536                Boolean::assert_any(
3537                    &[is_fee_transfer.neg(), fee_transfer_excess_overflowed.neg()],
3538                    w,
3539                );
3540
3541                CheckedSigned::on_if(
3542                    is_fee_transfer.var(),
3543                    SignedAmountBranchParam {
3544                        on_true: &fee_transfer_excess,
3545                        on_false: &user_command_excess,
3546                    },
3547                    w,
3548                )
3549            };
3550
3551            CheckedSigned::on_if(
3552                is_coinbase.var(),
3553                SignedAmountBranchParam {
3554                    on_true: &then_value,
3555                    on_false: &else_value,
3556                },
3557                w,
3558            )
3559        };
3560
3561        let supply_increase = {
3562            let expected_supply_increase = CheckedSigned::on_if(
3563                is_coinbase.var(),
3564                SignedAmountBranchParam {
3565                    on_true: &CheckedSigned::of_unsigned(payload.body.amount.to_checked()),
3566                    on_false: &CheckedSigned::of_unsigned(CheckedAmount::zero()),
3567                },
3568                w,
3569            );
3570
3571            let (amt0, _overflow0) = expected_supply_increase
3572                .add_flagged(&CheckedSigned::of_unsigned(burned_tokens).negate(), w);
3573
3574            let new_account_fees_total = CheckedSigned::on_if(
3575                user_command_fails.var(),
3576                SignedAmountBranchParam {
3577                    on_true: &zero_fee,
3578                    on_false: &new_account_fees,
3579                },
3580                w,
3581            );
3582
3583            let (amt, _overflow) = amt0.add_flagged(&new_account_fees_total, w);
3584
3585            amt
3586        };
3587
3588        let final_root = w.exists_no_check(match user_command_fails {
3589            Boolean::True => root_after_fee_payer_update,
3590            Boolean::False => root_after_source_update,
3591        });
3592
3593        Ok((final_root, fee_excess, supply_increase))
3594    }
3595
3596    pub fn assert_equal_local_state<F: FieldWitness>(
3597        t1: &LocalState,
3598        t2: &LocalState,
3599        w: &mut Witness<F>,
3600    ) {
3601        t1.excess.to_checked::<F>().value(w);
3602        t2.excess.to_checked::<F>().value(w);
3603
3604        t1.supply_increase.to_checked::<F>().value(w);
3605        t2.supply_increase.to_checked::<F>().value(w);
3606    }
3607
3608    pub fn main(
3609        statement_with_sok: &Statement<SokDigest>,
3610        tx_witness: &v2::TransactionWitnessStableV2,
3611        w: &mut Witness<Fp>,
3612    ) -> anyhow::Result<()> {
3613        let tx: crate::scan_state::transaction_logic::Transaction =
3614            (&tx_witness.transaction).try_into()?;
3615        let tx = transaction_union_payload::TransactionUnion::of_transaction(&tx);
3616
3617        dummy_constraints(w);
3618        let shifted = create_shifted_inner_curve(w);
3619
3620        let tx = w.exists(&tx);
3621        let pending_coinbase_init: pending_coinbase::Stack =
3622            w.exists((&tx_witness.init_stack).try_into()?);
3623        let state_body: ProtocolStateBody = w.exists((&tx_witness.protocol_state_body).try_into()?);
3624        let global_slot: currency::Slot = w.exists((&tx_witness.block_global_slot).into());
3625
3626        let sparse_ledger: SparseLedger = (&tx_witness.first_pass_ledger).try_into()?;
3627
3628        let (_fee_payment_root_after, fee_excess, _supply_increase) = apply_tagged_transaction(
3629            &shifted,
3630            statement_with_sok.source.first_pass_ledger,
3631            currency::Slot::from_u32(global_slot.as_u32()),
3632            &pending_coinbase_init,
3633            &statement_with_sok.source.pending_coinbase_stack,
3634            &statement_with_sok.target.pending_coinbase_stack,
3635            &state_body,
3636            tx,
3637            &sparse_ledger,
3638            w,
3639        )?;
3640
3641        let _fee_excess = {
3642            let fee_excess_zero = {
3643                let fee_excess = w.exists(fee_excess.force_value());
3644                field::equal(fee_excess, Fp::zero(), w)
3645            };
3646
3647            let fee_token_l = w.exists_no_check(match fee_excess_zero {
3648                Boolean::True => TokenId::default(),
3649                Boolean::False => tx.payload.common.fee_token.clone(),
3650            });
3651
3652            CheckedFeeExcess {
3653                fee_token_l,
3654                fee_excess_l: fee_excess.to_fee(),
3655                fee_token_r: TokenId::default(),
3656                fee_excess_r: CheckedSigned::zero(),
3657            }
3658        };
3659
3660        assert_equal_local_state(
3661            &statement_with_sok.source.local_state,
3662            &statement_with_sok.target.local_state,
3663            w,
3664        );
3665
3666        // Checked.all_unit
3667        {
3668            let supply_increase = statement_with_sok.supply_increase;
3669            w.exists_no_check(supply_increase.to_checked::<Fp>().force_value());
3670
3671            let FeeExcess {
3672                fee_token_l: _,
3673                fee_excess_l,
3674                fee_token_r: _,
3675                fee_excess_r,
3676            } = statement_with_sok.fee_excess;
3677
3678            w.exists_no_check(fee_excess_l.to_checked::<Fp>().force_value());
3679            w.exists_no_check(fee_excess_r.to_checked::<Fp>().force_value());
3680        }
3681
3682        Ok(())
3683    }
3684}
3685
3686pub fn get_messages_for_next_wrap_proof_padded() -> Vec<Fp> {
3687    let hash = messages_for_next_wrap_proof_padding();
3688    vec![hash, hash]
3689}
3690
3691pub fn messages_for_next_wrap_proof_padding() -> Fp {
3692    cache_one!(Fp, {
3693        let msg = MessagesForNextWrapProof {
3694            challenge_polynomial_commitment: InnerCurve::from(dummy_ipa_step_sg()),
3695            old_bulletproof_challenges: vec![], // Filled with padding, in `hash()` below
3696        };
3697        let hash: [u64; 4] = msg.hash();
3698        Fp::from(BigInteger256::new(hash)) // Never fail
3699    })
3700}
3701
3702pub fn checked_hash2<F: FieldWitness>(inputs: &[F], w: &mut Witness<F>) -> F {
3703    let mut sponge = poseidon::Sponge::<F>::new();
3704    sponge.absorb2(inputs, w);
3705    sponge.squeeze(w)
3706}
3707
3708pub fn checked_hash3<F: FieldWitness>(inputs: &[F], w: &mut Witness<F>) -> F {
3709    let mut sponge = poseidon::Sponge::<F>::new();
3710    sponge.absorb(inputs, w);
3711    sponge.squeeze(w)
3712}
3713
3714pub struct StepMainProofState {
3715    pub unfinalized_proofs: Vec<Unfinalized>,
3716    pub messages_for_next_step_proof: Fp,
3717}
3718
3719pub struct StepMainStatement {
3720    pub proof_state: StepMainProofState,
3721    pub messages_for_next_wrap_proof: Vec<Fp>,
3722}
3723
3724#[derive(Clone, Debug)]
3725pub struct StepProofState {
3726    pub unfinalized_proofs: Vec<Unfinalized>,
3727    pub messages_for_next_step_proof: ReducedMessagesForNextStepProof,
3728}
3729
3730#[derive(Debug)]
3731pub struct StepStatement {
3732    pub proof_state: StepProofState,
3733    pub messages_for_next_wrap_proof: Vec<MessagesForNextWrapProof>,
3734}
3735
3736#[derive(Debug)]
3737pub struct StepStatementWithHash {
3738    pub proof_state: StepProofState,
3739    pub messages_for_next_wrap_proof: Vec<[u64; 4]>,
3740}
3741
3742#[derive(Clone, Debug)]
3743pub struct ReducedMessagesForNextStepProof {
3744    pub app_state: Rc<dyn ToFieldElementsDebug>,
3745    pub challenge_polynomial_commitments: Vec<InnerCurve<Fp>>,
3746    pub old_bulletproof_challenges: Vec<[Fp; 16]>,
3747}
3748
3749#[derive(Clone, Debug)]
3750pub struct MessagesForNextStepProof<'a> {
3751    pub app_state: Rc<dyn ToFieldElementsDebug>,
3752    pub dlog_plonk_index: &'a PlonkVerificationKeyEvals<Fp>,
3753    pub challenge_polynomial_commitments: Vec<InnerCurve<Fp>>,
3754    pub old_bulletproof_challenges: Vec<[Fp; 16]>,
3755}
3756
3757impl MessagesForNextStepProof<'_> {
3758    /// Implementation of `hash_messages_for_next_step_proof`
3759    /// <https://github.com/MinaProtocol/mina/blob/32a91613c388a71f875581ad72276e762242f802/src/lib/pickles/common.ml#L33>
3760    pub fn hash(&self) -> [u64; 4] {
3761        let fields: Vec<Fp> = self.to_fields();
3762        let field: Fp = ::poseidon::hash::hash_fields(&fields);
3763
3764        let bigint: BigInteger256 = field.into_bigint();
3765        bigint.0
3766    }
3767
3768    /// Implementation of `to_field_elements`
3769    /// <https://github.com/MinaProtocol/mina/blob/32a91613c388a71f875581ad72276e762242f802/src/lib/pickles/composition_types/composition_types.ml#L493>
3770    pub fn to_fields(&self) -> Vec<Fp> {
3771        const NFIELDS: usize = 93; // TODO: This is bigger with transactions
3772
3773        let mut fields = Vec::with_capacity(NFIELDS);
3774
3775        let push_curve = |fields: &mut Vec<Fp>, curve: &InnerCurve<Fp>| {
3776            let GroupAffine::<Fp> { x, y, .. } = curve.to_affine();
3777            fields.push(x);
3778            fields.push(y);
3779        };
3780
3781        // Self::dlog_plonk_index
3782        // Refactor with `src/account/account.rs`, this is the same code
3783        {
3784            let index = &self.dlog_plonk_index;
3785
3786            for curve in &index.sigma {
3787                push_curve(&mut fields, curve);
3788            }
3789            for curve in &index.coefficients {
3790                push_curve(&mut fields, curve);
3791            }
3792            push_curve(&mut fields, &index.generic);
3793            push_curve(&mut fields, &index.psm);
3794            push_curve(&mut fields, &index.complete_add);
3795            push_curve(&mut fields, &index.mul);
3796            push_curve(&mut fields, &index.emul);
3797            push_curve(&mut fields, &index.endomul_scalar);
3798        }
3799
3800        self.app_state.to_field_elements(&mut fields);
3801
3802        let commitments = &self.challenge_polynomial_commitments;
3803        let old_challenges = &self.old_bulletproof_challenges;
3804        for (commitments, old) in commitments.iter().zip(old_challenges) {
3805            push_curve(&mut fields, commitments);
3806            fields.extend_from_slice(old);
3807        }
3808
3809        fields
3810    }
3811}
3812
3813// TODO: Find better name
3814#[derive(Clone, Debug, PartialEq)]
3815pub enum V {
3816    External(usize),
3817    Internal(usize),
3818}
3819
3820pub type InternalVars<F> = HashMap<usize, (Vec<(F, V)>, Option<F>)>;
3821
3822pub fn compute_witness<C: ProofConstants, F: FieldWitness>(
3823    prover: &Prover<F>,
3824    w: &Witness<F>,
3825) -> [Vec<F>; COLUMNS] {
3826    #[cfg(test)]
3827    {
3828        // Make sure our constants are correct
3829        eprintln!("compute_witness {:?}", std::any::type_name::<C>());
3830        assert_eq!(C::ROWS, prover.rows_rev.len() + C::PRIMARY_LEN);
3831        assert_eq!(C::AUX_LEN, w.aux().len());
3832    }
3833
3834    if !w.ocaml_aux.is_empty() {
3835        assert_eq!(w.aux().len(), w.ocaml_aux.len());
3836    };
3837
3838    let external_values = |i: usize| {
3839        if i < C::PRIMARY_LEN {
3840            w.primary[i]
3841        } else {
3842            w.aux()[i - C::PRIMARY_LEN]
3843        }
3844    };
3845
3846    let mut internal_values = HashMap::<usize, F>::with_capacity(13_000);
3847    let public_input_size = C::PRIMARY_LEN;
3848    let num_rows = C::ROWS;
3849
3850    let mut res: [_; COLUMNS] = std::array::from_fn(|_| vec![F::zero(); num_rows]);
3851
3852    // public input
3853    #[allow(
3854        clippy::needless_range_loop,
3855        reason = "Clippy incorrectly assumes we're indexing res with `i`, but we're actually not!"
3856    )]
3857    for i in 0..public_input_size {
3858        res[0][i] = external_values(i);
3859    }
3860
3861    let compute = |(lc, c): &(Vec<(F, V)>, Option<F>), internal_values: &HashMap<_, _>| {
3862        lc.iter().fold(c.unwrap_or_else(F::zero), |acc, (s, x)| {
3863            let x = match x {
3864                V::External(x) => external_values(*x),
3865                V::Internal(x) => internal_values.get(x).copied().unwrap(),
3866            };
3867            acc + (*s * x)
3868        })
3869    };
3870
3871    for (i_after_input, cols) in prover.rows_rev.iter().rev().enumerate() {
3872        let row_idx = i_after_input + public_input_size;
3873        for (col_idx, var) in cols.iter().enumerate() {
3874            // println!("w[{}][{}]", col_idx, row_idx);
3875            match var {
3876                None => (),
3877                Some(V::External(var)) => {
3878                    res[col_idx][row_idx] = external_values(*var);
3879                }
3880                Some(V::Internal(var)) => {
3881                    let lc = prover.internal_vars.get(var).unwrap();
3882                    let value = compute(lc, &internal_values);
3883                    res[col_idx][row_idx] = value;
3884                    internal_values.insert(*var, value);
3885                }
3886            }
3887        }
3888    }
3889
3890    res
3891}
3892
3893pub fn make_prover_index<C: ProofConstants, F: FieldWitness>(
3894    gates: Vec<CircuitGate<F>>,
3895    verifier_index: Option<Arc<super::VerifierIndex<F>>>,
3896) -> ProverIndex<F> {
3897    use kimchi::circuits::constraints::ConstraintSystem;
3898
3899    let public = C::PRIMARY_LEN;
3900    let prev_challenges = C::PREVIOUS_CHALLENGES;
3901
3902    let cs = ConstraintSystem::<F>::create(gates)
3903        .public(public)
3904        .prev_challenges(prev_challenges)
3905        .build()
3906        .unwrap();
3907
3908    let (endo_q, _endo_r) = endos::<F>();
3909
3910    // TODO: `proof-systems` needs to change how the SRS is used
3911    let srs: poly_commitment::ipa::SRS<F::OtherCurve> = {
3912        let srs = get_srs_mut::<F>();
3913        let srs = srs.lock().unwrap().clone();
3914        srs.get_lagrange_basis(cs.domain.d1);
3915        srs
3916    };
3917
3918    let mut index = ProverIndex::<F>::create(cs, endo_q, Arc::new(srs), false);
3919    index.verifier_index = verifier_index.map(|i| i.as_ref().clone());
3920
3921    // Compute and cache the verifier index digest
3922    index.compute_verifier_index_digest::<F::FqSponge>();
3923    index
3924}
3925
3926/// During tests, we don't want randomness, to get reproducible witness/proofs
3927/// TODO: Are there other cases where we don't want randomness ?
3928#[cfg(test)]
3929fn get_rng() -> rand::rngs::StdRng {
3930    <rand::rngs::StdRng as rand::SeedableRng>::seed_from_u64(0)
3931}
3932#[cfg(not(test))]
3933fn get_rng() -> rand::rngs::OsRng {
3934    rand::rngs::OsRng
3935}
3936
3937#[derive(Debug, thiserror::Error)]
3938pub enum ProofError {
3939    #[error("kimchi error: {0:?}")]
3940    ProvingError(#[from] kimchi::error::ProverError),
3941    #[error("kimchi error with context: {0:?}")]
3942    ProvingErrorWithContext(#[from] debug::KimchiProofError),
3943    #[error("constraint not satisfield: {0}")]
3944    ConstraintsNotSatisfied(String),
3945    #[error("invalid bigint")]
3946    InvalidBigint(#[from] InvalidBigInt),
3947    /// We still return an error when `only_verify_constraints` is true and
3948    /// constraints are verified, to short-circuit easily
3949    #[error("Constraints ok")]
3950    ConstraintsOk,
3951}
3952
3953pub(super) struct CreateProofParams<'a, F: FieldWitness> {
3954    pub(super) prover: &'a Prover<F>,
3955    pub(super) prev_challenges: Vec<RecursionChallenge<F::OtherCurve>>,
3956    pub(super) only_verify_constraints: bool,
3957}
3958
3959/// <https://github.com/o1-labs/proof-systems/blob/553795286d4561aa5d7e928ed1e3555e3a4a81be/kimchi/src/prover.rs#L1718>
3960///
3961/// Note: OCaml keeps the `public_evals`, but we already have it in our `proof`
3962pub struct ProofWithPublic<F: FieldWitness> {
3963    pub proof: super::ProverProof<F>,
3964    pub public_input: Vec<F>,
3965}
3966impl<F: FieldWitness> ProofWithPublic<F> {
3967    pub fn public_evals(&self) -> Option<&kimchi::proof::PointEvaluations<Vec<F>>> {
3968        self.proof.evals.public.as_ref()
3969    }
3970}
3971
3972pub(super) fn create_proof<C: ProofConstants, F: FieldWitness>(
3973    params: CreateProofParams<F>,
3974    w: &Witness<F>,
3975) -> anyhow::Result<ProofWithPublic<F>> {
3976    type EFrSponge<F> = mina_poseidon::sponge::DefaultFrSponge<F, PlonkSpongeConstantsKimchi>;
3977
3978    let CreateProofParams {
3979        prover,
3980        prev_challenges,
3981        only_verify_constraints,
3982    } = params;
3983
3984    let computed_witness: [Vec<F>; COLUMNS] = compute_witness::<C, _>(prover, w);
3985    let prover_index: &ProverIndex<F> = &prover.index;
3986
3987    // public input
3988    let public_input = computed_witness[0][..prover_index.cs.public].to_vec();
3989
3990    if only_verify_constraints {
3991        prover_index
3992            .verify(&computed_witness, &public_input)
3993            .map_err(|e| {
3994                ProofError::ConstraintsNotSatisfied(format!("incorrect witness: {:?}", e))
3995            })?;
3996
3997        // We still return an error when `only_verify_constraints` is true and
3998        // constraints are verified, to short-circuit easily
3999        return Err(ProofError::ConstraintsOk.into());
4000    }
4001
4002    // NOTE: Not random in `cfg(test)`
4003    // let mut rng = get_rng();
4004    let mut rng = get_rng();
4005
4006    let now = redux::Instant::now();
4007    let group_map = <F::OtherCurve as CommitmentCurve>::Map::setup();
4008    let proof = kimchi::proof::ProverProof::create_recursive::<F::FqSponge, EFrSponge<F>, _>(
4009        &group_map,
4010        computed_witness,
4011        &[],
4012        prover_index,
4013        prev_challenges.clone(),
4014        None,
4015        &mut rng,
4016    )
4017    .map_err(|e| {
4018        let prev_challenges_hash = debug::hash_prev_challenge::<F>(&prev_challenges);
4019        let witness_primary_hash = debug::hash_slice(&w.primary);
4020        let witness_aux_hash = debug::hash_slice(w.aux());
4021        let group_map_hash = {
4022            // Recreating the same value to access the field.
4023            // We should find a way to bypass the type-checker to reuse
4024            // the value group_map defined above.
4025            // As it is only in the case of errors, the additional cost of
4026            // creating a new value can be ignored.
4027            let group_map_for_debug =
4028                BWParameters::<<<F as FieldWitness>::Scalar as FieldWitness>::Parameters>::setup();
4029            let d = vec![
4030                group_map_for_debug.u,
4031                group_map_for_debug.fu,
4032                group_map_for_debug.sqrt_neg_three_u_squared_minus_u_over_2,
4033                group_map_for_debug.sqrt_neg_three_u_squared,
4034                group_map_for_debug.inv_three_u_squared,
4035            ];
4036            debug::hash_slice(&d)
4037        };
4038
4039        dbg!(
4040            &prev_challenges_hash,
4041            &witness_primary_hash,
4042            &witness_aux_hash,
4043            &group_map_hash
4044        );
4045
4046        let context = debug::KimchiProofError {
4047            inner_error: e.to_string(),
4048            witness_primary: w.primary.iter().map(|f| (*f).into()).collect(),
4049            witness_aux: w.aux().iter().map(|f| (*f).into()).collect(),
4050            // prev_challenges,
4051            witness_primary_hash,
4052            witness_aux_hash,
4053            prev_challenges_hash,
4054            group_map_hash,
4055            latest_random: LATEST_RANDOM.with_borrow(|fun| (fun)()),
4056        };
4057
4058        ProofError::ProvingErrorWithContext(context)
4059    })
4060    .context("create_recursive")?;
4061
4062    eprintln!("proof_elapsed={:?}", now.elapsed());
4063
4064    Ok(ProofWithPublic {
4065        proof,
4066        public_input,
4067    })
4068}
4069
4070pub mod debug {
4071    use super::*;
4072
4073    use mina_p2p_messages::{bigint::BigInt, binprot};
4074    use sha2::Digest;
4075
4076    fn hash_field<F: FieldWitness>(state: &mut sha2::Sha256, f: &F) {
4077        let ark_ff::BigInt(int): BigInteger256 = (*f).into();
4078        for limb in int {
4079            state.update(limb.to_le_bytes());
4080        }
4081    }
4082
4083    fn hash_field_slice<F: FieldWitness>(state: &mut sha2::Sha256, slice: &[F]) {
4084        state.update(slice.len().to_le_bytes());
4085        for f in slice.iter().flat_map(|f| {
4086            let ark_ff::BigInt(int): BigInteger256 = (*f).into();
4087            int
4088        }) {
4089            state.update(f.to_le_bytes());
4090        }
4091    }
4092
4093    pub(super) fn hash_slice<F: FieldWitness>(slice: &[F]) -> String {
4094        let mut hasher = sha2::Sha256::new();
4095        hash_field_slice(&mut hasher, slice);
4096        hex::encode(hasher.finalize())
4097    }
4098
4099    pub(super) fn hash_prev_challenge<F: FieldWitness>(
4100        prevs: &[RecursionChallenge<F::OtherCurve>],
4101    ) -> String {
4102        use poly_commitment::commitment::CommitmentCurve;
4103        use sha2::Digest;
4104        let mut hasher = sha2::Sha256::new();
4105        for RecursionChallenge { chals, comm } in prevs {
4106            hash_field_slice(&mut hasher, chals);
4107            let poly_commitment::PolyComm { chunks } = comm;
4108            for elem in chunks {
4109                match elem.to_coordinates() {
4110                    None => {
4111                        hasher.update([0]);
4112                    }
4113                    Some((c1, c2)) => {
4114                        hasher.update([1]);
4115                        hash_field(&mut hasher, &c1);
4116                        hash_field(&mut hasher, &c2);
4117                    }
4118                }
4119            }
4120        }
4121        hex::encode(hasher.finalize())
4122    }
4123
4124    #[derive(Clone)]
4125    pub struct KimchiProofError {
4126        pub inner_error: String,
4127        pub witness_primary: Vec<BigInt>,
4128        pub witness_aux: Vec<BigInt>,
4129        // pub prev_challenges: Vec<RecursionChallenge<F::OtherCurve>>,
4130        // Store hashes in case there is a de/serialization bug
4131        pub witness_primary_hash: String,
4132        pub witness_aux_hash: String,
4133        pub prev_challenges_hash: String,
4134        pub group_map_hash: String,
4135        pub latest_random: String,
4136    }
4137
4138    // Manual implementation because String does not implement binprot traits (because unbounded)
4139    impl binprot::BinProtWrite for KimchiProofError {
4140        fn binprot_write<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
4141            let Self {
4142                inner_error,
4143                witness_primary,
4144                witness_aux,
4145                witness_primary_hash,
4146                witness_aux_hash,
4147                prev_challenges_hash,
4148                group_map_hash,
4149                latest_random,
4150            } = self;
4151            let inner_error: &[u8] = inner_error.as_bytes();
4152            let witness_primary_hash: &[u8] = witness_primary_hash.as_bytes();
4153            let witness_aux_hash: &[u8] = witness_aux_hash.as_bytes();
4154            let prev_challenges_hash: &[u8] = prev_challenges_hash.as_bytes();
4155            let group_map_hash: &[u8] = group_map_hash.as_bytes();
4156            let latest_random: &[u8] = latest_random.as_bytes();
4157            binprot::BinProtWrite::binprot_write(&inner_error, w)?;
4158            binprot::BinProtWrite::binprot_write(witness_primary, w)?;
4159            binprot::BinProtWrite::binprot_write(witness_aux, w)?;
4160            binprot::BinProtWrite::binprot_write(&witness_primary_hash, w)?;
4161            binprot::BinProtWrite::binprot_write(&witness_aux_hash, w)?;
4162            binprot::BinProtWrite::binprot_write(&prev_challenges_hash, w)?;
4163            binprot::BinProtWrite::binprot_write(&group_map_hash, w)?;
4164            binprot::BinProtWrite::binprot_write(&latest_random, w)?;
4165            Ok(())
4166        }
4167    }
4168    // Manual implementation because String does not implement binprot traits (because unbounded)
4169    impl binprot::BinProtRead for KimchiProofError {
4170        fn binprot_read<R: std::io::Read + ?Sized>(r: &mut R) -> Result<Self, binprot::Error>
4171        where
4172            Self: Sized,
4173        {
4174            let to_string = |bytes: Vec<u8>| -> String { String::from_utf8(bytes).unwrap() };
4175            let inner_error: Vec<u8> = binprot::BinProtRead::binprot_read(r)?;
4176            let witness_primary: Vec<BigInt> = binprot::BinProtRead::binprot_read(r)?;
4177            let witness_aux: Vec<BigInt> = binprot::BinProtRead::binprot_read(r)?;
4178            let witness_primary_hash: Vec<u8> = binprot::BinProtRead::binprot_read(r)?;
4179            let witness_aux_hash: Vec<u8> = binprot::BinProtRead::binprot_read(r)?;
4180            let prev_challenges_hash: Vec<u8> = binprot::BinProtRead::binprot_read(r)?;
4181            let group_map_hash: Vec<u8> = binprot::BinProtRead::binprot_read(r)?;
4182            let latest_random: Vec<u8> = binprot::BinProtRead::binprot_read(r)?;
4183            Ok(Self {
4184                inner_error: to_string(inner_error),
4185                witness_primary,
4186                witness_aux,
4187                witness_primary_hash: to_string(witness_primary_hash),
4188                witness_aux_hash: to_string(witness_aux_hash),
4189                prev_challenges_hash: to_string(prev_challenges_hash),
4190                group_map_hash: to_string(group_map_hash),
4191                latest_random: to_string(latest_random),
4192            })
4193        }
4194    }
4195
4196    impl core::fmt::Display for KimchiProofError {
4197        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4198            f.write_fmt(format_args!("{:?}", self))
4199        }
4200    }
4201
4202    impl std::error::Error for KimchiProofError {}
4203
4204    impl core::fmt::Debug for KimchiProofError {
4205        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4206            let Self {
4207                inner_error,
4208                witness_primary,
4209                witness_aux,
4210                witness_primary_hash,
4211                witness_aux_hash,
4212                prev_challenges_hash,
4213                group_map_hash,
4214                latest_random,
4215            } = self;
4216
4217            // Print witness lengths, not the whole vectors
4218            f.debug_struct("KimchiProofError")
4219                .field("inner_error", inner_error)
4220                .field("witness_primary", &witness_primary.len())
4221                .field("witness_aux", &witness_aux.len())
4222                .field("witness_primary_hash", &witness_primary_hash)
4223                .field("witness_aux_hash", &witness_aux_hash)
4224                .field("prev_challenges_hash", &prev_challenges_hash)
4225                .field("group_map_hash", &group_map_hash)
4226                .field("latest_random", &latest_random)
4227                .finish()
4228        }
4229    }
4230}
4231
4232#[derive(Clone)]
4233pub struct Prover<F: FieldWitness> {
4234    /// Constants to each kind of proof
4235    pub internal_vars: InternalVars<F>,
4236    /// Constants to each kind of proof
4237    pub rows_rev: Vec<Vec<Option<V>>>,
4238    pub index: ProverIndex<F>,
4239}
4240
4241pub struct TransactionParams<'a> {
4242    pub statement: &'a MinaStateBlockchainStateValueStableV2LedgerProofStatement,
4243    pub tx_witness: &'a v2::TransactionWitnessStableV2,
4244    pub message: &'a SokMessage,
4245    pub tx_step_prover: &'a Prover<Fp>,
4246    pub tx_wrap_prover: &'a Prover<Fq>,
4247    /// When set to `true`, `generate_block_proof` will not create a proof, but only
4248    /// verify constraints in the step witnesses
4249    pub only_verify_constraints: bool,
4250    /// For debugging only
4251    pub expected_step_proof: Option<&'static str>,
4252    /// For debugging only
4253    pub ocaml_wrap_witness: Option<Vec<Fq>>,
4254}
4255
4256pub(super) fn generate_tx_proof(
4257    params: TransactionParams,
4258    w: &mut Witness<Fp>,
4259) -> anyhow::Result<WrapProof> {
4260    let TransactionParams {
4261        statement,
4262        tx_witness,
4263        message,
4264        tx_step_prover,
4265        tx_wrap_prover,
4266        only_verify_constraints,
4267        expected_step_proof,
4268        ocaml_wrap_witness,
4269    } = params;
4270
4271    let statement: Statement<()> = statement.try_into()?;
4272    let sok_digest = message.digest();
4273    let statement_with_sok = statement.with_digest(sok_digest);
4274
4275    let dlog_plonk_index =
4276        PlonkVerificationKeyEvals::from(tx_wrap_prover.index.verifier_index.as_ref().unwrap());
4277
4278    let statement_with_sok = Rc::new(w.exists(statement_with_sok));
4279    transaction_snark::main(&statement_with_sok, tx_witness, w)?;
4280
4281    let StepProof {
4282        statement: step_statement,
4283        prev_evals,
4284        proof_with_public,
4285    } = step::step::<StepTransactionProof, 0>(
4286        step::StepParams {
4287            app_state: Rc::clone(&statement_with_sok) as _,
4288            rule: InductiveRule::empty(),
4289            for_step_datas: [],
4290            indexes: [],
4291            prev_challenge_polynomial_commitments: vec![],
4292            hack_feature_flags: OptFlag::No,
4293            step_prover: tx_step_prover,
4294            wrap_prover: tx_wrap_prover,
4295            only_verify_constraints,
4296        },
4297        w,
4298    )?;
4299
4300    if let Some(expected) = expected_step_proof {
4301        let proof_json = serde_json::to_vec(&proof_with_public.proof).unwrap();
4302        assert_eq!(sha256_sum(&proof_json), expected);
4303    };
4304
4305    let mut w = Witness::new::<WrapTransactionProof>();
4306
4307    if let Some(ocaml_aux) = ocaml_wrap_witness {
4308        w.ocaml_aux = ocaml_aux;
4309    };
4310
4311    wrap::wrap::<WrapTransactionProof>(
4312        WrapParams {
4313            app_state: statement_with_sok,
4314            proof_with_public: &proof_with_public,
4315            step_statement,
4316            prev_evals: &prev_evals,
4317            dlog_plonk_index: &dlog_plonk_index,
4318            step_prover_index: &tx_step_prover.index,
4319            wrap_prover: tx_wrap_prover,
4320        },
4321        &mut w,
4322    )
4323}
4324
4325#[cfg(test)]
4326mod tests_with_wasm {
4327    #[cfg(target_family = "wasm")]
4328    use wasm_bindgen_test::wasm_bindgen_test as test;
4329
4330    use super::*;
4331    #[test]
4332    fn test_to_field_checked() {
4333        let mut witness = Witness::empty();
4334        let f = Fp::from_str("1866").unwrap();
4335
4336        let res = scalar_challenge::to_field_checked_prime::<_, 32>(f, &mut witness);
4337
4338        assert_eq!(res, (131085.into(), 65636.into(), 1866.into()));
4339        assert_eq!(
4340            witness.aux(),
4341            &[
4342                0.into(),
4343                0.into(),
4344                0.into(),
4345                0.into(),
4346                0.into(),
4347                0.into(),
4348                0.into(),
4349                0.into(),
4350                0.into(),
4351                512.into(),
4352                257.into(),
4353                0.into(),
4354                0.into(),
4355                1.into(),
4356                3.into(),
4357                1.into(),
4358                0.into(),
4359                2.into(),
4360                2.into(),
4361                1866.into(),
4362                131085.into(),
4363                65636.into(),
4364            ]
4365        );
4366    }
4367}
4368
4369#[cfg(test)]
4370pub(super) mod tests {
4371    use core::panic;
4372    use std::path::Path;
4373
4374    use ::poseidon::hash::params::MINA_ZKAPP_EVENT;
4375    use mina_p2p_messages::binprot::{
4376        self,
4377        macros::{BinProtRead, BinProtWrite},
4378        BinProtRead,
4379    };
4380
4381    use crate::{
4382        proofs::{
4383            block::{generate_block_proof, BlockParams},
4384            constants::{StepBlockProof, StepMergeProof},
4385            merge::{generate_merge_proof, MergeParams},
4386            provers::{devnet_circuit_directory, BlockProver, TransactionProver, ZkappProver},
4387            util::sha256_sum,
4388            zkapp::{generate_zkapp_proof, LedgerProof, ZkappParams},
4389        },
4390        scan_state::scan_state::transaction_snark::SokMessage,
4391    };
4392
4393    use super::*;
4394
4395    type SnarkWorkSpec =
4396        mina_p2p_messages::v2::SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponseA0Instances;
4397
4398    /// External worker input.
4399    #[derive(Debug, BinProtRead, BinProtWrite)]
4400    pub enum ExternalSnarkWorkerRequest {
4401        /// Queries worker for readiness, expected reply is `true`.
4402        AwaitReadiness,
4403        /// Commands worker to start specified snark job, expected reply is `ExternalSnarkWorkerResult`[ExternalSnarkWorkerResult].
4404        PerformJob(mina_p2p_messages::v2::SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponse),
4405    }
4406
4407    fn read_binprot<T, R>(mut r: R) -> T
4408    where
4409        T: binprot::BinProtRead,
4410        R: std::io::Read,
4411    {
4412        use std::io::Read;
4413
4414        let mut len_buf = [0; std::mem::size_of::<u64>()];
4415        r.read_exact(&mut len_buf).unwrap();
4416        let len = u64::from_le_bytes(len_buf);
4417
4418        let mut buf = Vec::with_capacity(len as usize);
4419        let mut r = r.take(len);
4420        r.read_to_end(&mut buf).unwrap();
4421
4422        let mut read = buf.as_slice();
4423        T::binprot_read(&mut read).unwrap()
4424    }
4425
4426    fn read_witnesses<F: FieldWitness>(filename: &str) -> Result<Vec<F>, ()> {
4427        let f = std::fs::read_to_string(
4428            std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
4429                .join(devnet_circuit_directory())
4430                .join("witnesses")
4431                .join(filename),
4432        )
4433        .unwrap();
4434        f.lines()
4435            .filter(|s| !s.is_empty())
4436            .map(|s| F::from_str(s).map_err(|_| ()))
4437            .collect()
4438    }
4439
4440    #[allow(unused)]
4441    #[test]
4442    fn test_convert_requests() {
4443        use binprot::BinProtWrite;
4444        use mina_p2p_messages::v2::*;
4445
4446        fn write_binprot<T: BinProtWrite, W: std::io::Write>(spec: T, mut w: W) {
4447            let mut buf = Vec::new();
4448            spec.binprot_write(&mut buf).unwrap();
4449            let len = (buf.len() as u64).to_le_bytes();
4450            w.write_all(&len).unwrap();
4451            w.write_all(&buf).unwrap();
4452        }
4453
4454        let path = Path::new(env!("CARGO_MANIFEST_DIR"))
4455            .join(devnet_circuit_directory())
4456            .join("tests");
4457
4458        let entries = std::fs::read_dir(path)
4459            .unwrap()
4460            .map(|res| res.map(|e| e.path()))
4461            .collect::<Result<Vec<_>, std::io::Error>>()
4462            .unwrap();
4463
4464        let prover = CompressedPubKey::from_address(
4465            "B62qpK6TcG4sWdtT3BzdbWHiK3RJMj3Zbo9mqwBos7cVsydPMCj5wZx",
4466        )
4467        .unwrap();
4468        let fee = crate::scan_state::currency::Fee::from_u64(1_000_000);
4469
4470        for filename in entries {
4471            if !filename.ends_with(".bin") {
4472                continue;
4473            }
4474            let bytes = std::fs::read(&filename).unwrap();
4475
4476            // let job: v2::ProverExtendBlockchainInputStableV2 =
4477            //     match binprot::BinProtRead::binprot_read(&mut bytes.as_slice()) {
4478            //         Ok(job) => job,
4479            //         _ => continue,
4480            //     };
4481
4482            let single: SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponseA0Single =
4483                binprot::BinProtRead::binprot_read(&mut bytes.as_slice()).unwrap();
4484            let instances =
4485                SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponseA0Instances::One(single);
4486            let job = SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponseA0 {
4487                instances,
4488                fee: (&fee).into(),
4489            };
4490            let job =
4491                SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponse(Some((job, (&prover).into())));
4492            let job = ExternalSnarkWorkerRequest::PerformJob(job);
4493
4494            let path = Path::new("/tmp")
4495                .join("requests")
4496                .join(filename.file_name().unwrap());
4497            let mut file = std::fs::File::create(path).unwrap();
4498            write_binprot(job, &mut file);
4499            file.sync_all().unwrap();
4500        }
4501    }
4502
4503    fn extract_request(
4504        mut bytes: &[u8],
4505    ) -> (
4506        v2::MinaStateSnarkedLedgerStateStableV2,
4507        v2::TransactionWitnessStableV2,
4508        SokMessage,
4509    ) {
4510        use mina_p2p_messages::v2::*;
4511
4512        let v: ExternalSnarkWorkerRequest = read_binprot(&mut bytes);
4513
4514        let ExternalSnarkWorkerRequest::PerformJob(job) = v else {
4515            panic!()
4516        };
4517        let SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponse(Some((a, prover))) = job else {
4518            panic!()
4519        };
4520        let SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponseA0Instances::One(single) = a.instances
4521        else {
4522            panic!()
4523        };
4524        let SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponseA0Single::Transition(
4525            statement,
4526            tx_witness,
4527        ) = single
4528        else {
4529            panic!()
4530        };
4531
4532        let prover: CompressedPubKey = (&prover).try_into().unwrap();
4533        let fee = crate::scan_state::currency::Fee::from_u64(a.fee.as_u64());
4534
4535        let message = SokMessage { fee, prover };
4536
4537        (statement, tx_witness, message)
4538    }
4539
4540    fn extract_merge(
4541        mut bytes: &[u8],
4542    ) -> (
4543        v2::MinaStateSnarkedLedgerStateStableV2,
4544        [v2::LedgerProofProdStableV2; 2],
4545        SokMessage,
4546    ) {
4547        use mina_p2p_messages::v2::*;
4548
4549        let v: ExternalSnarkWorkerRequest = read_binprot(&mut bytes);
4550
4551        let ExternalSnarkWorkerRequest::PerformJob(job) = v else {
4552            panic!()
4553        };
4554        let SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponse(Some((a, prover))) = job else {
4555            panic!()
4556        };
4557        let SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponseA0Instances::One(single) = a.instances
4558        else {
4559            panic!()
4560        };
4561        let SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponseA0Single::Merge(merge) = single else {
4562            panic!()
4563        };
4564
4565        let (statement, p1, p2) = *merge;
4566
4567        let prover: CompressedPubKey = (&prover).try_into().unwrap();
4568        let fee = crate::scan_state::currency::Fee::from_u64(a.fee.as_u64());
4569
4570        let message = SokMessage { fee, prover };
4571
4572        (statement, [p1, p2], message)
4573    }
4574
4575    #[allow(unused)]
4576    #[test]
4577    fn test_make_verifier_index() {
4578        BlockProver::make(None, None);
4579        TransactionProver::make(None);
4580
4581        // use crate::proofs::caching::verifier_index_to_bytes;
4582        // use crate::proofs::verifier_index::get_verifier_index;
4583
4584        // let v = &tx_wrap_prover.index.verifier_index.as_ref().unwrap();
4585        // let v = verifier_index_to_bytes(&v);
4586        // let new_v = get_verifier_index(crate::proofs::verifier_index::VerifierKind::Transaction);
4587        // let new_v = verifier_index_to_bytes(&new_v);
4588        // assert_eq!(v, new_v);
4589        // let tx_old = new_v;
4590
4591        // let v = &block_wrap_prover.index.verifier_index.as_ref().unwrap();
4592        // let v = verifier_index_to_bytes(&v);
4593        // let new_v = get_verifier_index(crate::proofs::verifier_index::VerifierKind::Blockchain);
4594        // let new_v = verifier_index_to_bytes(&new_v);
4595        // assert_eq!(v, new_v);
4596        // let block_old = new_v;
4597
4598        eprintln!("OK");
4599
4600        // let Provers {
4601        //     tx_step_prover,
4602        //     tx_wrap_prover,
4603        //     merge_step_prover,
4604        //     block_step_prover,
4605        //     block_wrap_prover,
4606        //     zkapp_step_opt_signed_opt_signed_prover,
4607        //     zkapp_step_opt_signed_prover,
4608        //     zkapp_step_proof_prover,
4609        // } = crate::proofs::gates::make_provers2();
4610
4611        // // let writer = std::fs::File::create("/tmp/transaction_verifier_index.json").unwrap();
4612        // // let value = tx_wrap_prover.index.verifier_index.as_ref().unwrap();
4613        // // serde_json::to_writer(writer, value).unwrap();
4614
4615        // // let writer = std::fs::File::create("/tmp/blockchain_verifier_index.json").unwrap();
4616        // // let value = block_wrap_prover.index.verifier_index.as_ref().unwrap();
4617        // // serde_json::to_writer(writer, value).unwrap();
4618
4619        // let v = &tx_wrap_prover.index.verifier_index.as_ref().unwrap();
4620        // let v = verifier_index_to_bytes(&v);
4621        // let tx_is_same = v == tx_old;
4622
4623        // let v = &block_wrap_prover.index.verifier_index.as_ref().unwrap();
4624        // let v = verifier_index_to_bytes(&v);
4625        // let block_is_same = v == block_old;
4626
4627        // dbg!(tx_is_same, block_is_same);
4628        // eprintln!("OK2");
4629
4630        // let linear2 = &new_v.linearization;
4631
4632        // assert_eq!(linear.constant_term, linear2.constant_term);
4633        // assert_eq!(linear.index_terms, linear2.index_terms);
4634        // assert_eq!(v.shift, new_v.shift);
4635        // assert_eq!(v.zkpm, new_v.zkpm);
4636        // assert_eq!(v.w, new_v.w);
4637        // assert_eq!(v.endo, new_v.endo);
4638        // assert_eq!(format!("{:?}", v.lookup_index), format!("{:?}", new_v.lookup_index));
4639    }
4640
4641    #[test]
4642    #[ignore = "Failing due to circuits"]
4643    fn test_regular_tx() {
4644        let data = std::fs::read(
4645            Path::new(env!("CARGO_MANIFEST_DIR"))
4646                .join(devnet_circuit_directory())
4647                .join("tests")
4648                .join("command-0-1.bin"),
4649        )
4650        .unwrap();
4651
4652        let (statement, tx_witness, message) = extract_request(&data);
4653        let TransactionProver {
4654            tx_step_prover,
4655            tx_wrap_prover,
4656            merge_step_prover: _,
4657        } = TransactionProver::make(None);
4658
4659        let mut witnesses: Witness<Fp> = Witness::new::<StepTransactionProof>();
4660        // witnesses.ocaml_aux = read_witnesses("tx_fps.txt").unwrap();
4661
4662        let WrapProof { proof, .. } = generate_tx_proof(
4663            TransactionParams {
4664                statement: &statement,
4665                tx_witness: &tx_witness,
4666                message: &message,
4667                tx_step_prover: &tx_step_prover,
4668                tx_wrap_prover: &tx_wrap_prover,
4669                only_verify_constraints: false,
4670                expected_step_proof: None,
4671                ocaml_wrap_witness: None,
4672            },
4673            &mut witnesses,
4674        )
4675        .unwrap();
4676
4677        let proof_json = serde_json::to_vec(&proof.proof).unwrap();
4678        let sum = sha256_sum(&proof_json);
4679        dbg!(sum);
4680    }
4681
4682    #[test]
4683    fn test_hash_empty_event_checked() {
4684        // Same value than OCaml
4685        const EXPECTED: &str =
4686            "6963060754718463299978089777716994949151371320681588566338620419071140958308";
4687
4688        let mut w = Witness::empty();
4689        let hash = transaction_snark::checked_hash(&MINA_ZKAPP_EVENT, &[], &mut w);
4690        assert_eq!(hash, Fp::from_str(EXPECTED).unwrap());
4691
4692        let mut w = Witness::empty();
4693        let hash = transaction_snark::checked_hash3(&MINA_ZKAPP_EVENT, &[], &mut w);
4694        assert_eq!(hash, Fp::from_str(EXPECTED).unwrap());
4695    }
4696
4697    /// Print requests types
4698    #[allow(unused)]
4699    #[test]
4700    fn test_read_requests() {
4701        let path = Path::new(env!("CARGO_MANIFEST_DIR"))
4702            .join(devnet_circuit_directory())
4703            .join("tests");
4704
4705        let mut files = Vec::with_capacity(1000);
4706
4707        for index in 0..285 {
4708            for j in 0..2 {
4709                let filename = format!("command-{index}-{j}.bin");
4710                let file_path = path.join(filename);
4711                if file_path.exists() {
4712                    files.push(file_path);
4713                }
4714            }
4715        }
4716
4717        for (index, file) in files.iter().enumerate() {
4718            use mina_p2p_messages::v2::*;
4719
4720            let bytes = std::fs::read(file).unwrap();
4721
4722            let v: ExternalSnarkWorkerRequest = read_binprot(&mut bytes.as_slice());
4723            let ExternalSnarkWorkerRequest::PerformJob(job) = v else {
4724                panic!()
4725            };
4726            let SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponse(Some((a, _prover))) = job else {
4727                panic!()
4728            };
4729            let SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponseA0Instances::One(single) =
4730                a.instances
4731            else {
4732                panic!()
4733            };
4734
4735            let (_stmt, witness) = match single {
4736                mina_p2p_messages::v2::SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponseA0Single::Transition(stmt, witness) => (stmt, witness),
4737                mina_p2p_messages::v2::SnarkWorkerWorkerRpcsVersionedGetWorkV2TResponseA0Single::Merge(_) => todo!(),
4738            };
4739
4740            match &witness.transaction {
4741                MinaTransactionTransactionStableV2::Command(cmd) => match &**cmd {
4742                    mina_p2p_messages::v2::MinaBaseUserCommandStableV2::SignedCommand(_) => {
4743                        eprintln!("[{}] signed: {:?}", index, file)
4744                    }
4745                    mina_p2p_messages::v2::MinaBaseUserCommandStableV2::ZkappCommand(z) => {
4746                        eprintln!("[{}] zkapp: {:?}", index, file);
4747                        // Print the zkapp to find if it's signature or proof authorization
4748                        // eprintln!("zkapp {:#?}", z);
4749                    }
4750                },
4751                MinaTransactionTransactionStableV2::FeeTransfer(_) => {
4752                    eprintln!("[{}] fee_transfer", index)
4753                }
4754                MinaTransactionTransactionStableV2::Coinbase(_) => {
4755                    eprintln!("[{}] coinbase", index)
4756                }
4757            }
4758        }
4759    }
4760
4761    #[test]
4762    #[ignore = "Failing due to circuits"]
4763    fn test_merge_proof() {
4764        let data = std::fs::read(
4765            Path::new(env!("CARGO_MANIFEST_DIR"))
4766                .join(devnet_circuit_directory())
4767                .join("tests")
4768                .join("merge-100-0.bin"),
4769        )
4770        .unwrap();
4771
4772        let (statement, proofs, message) = extract_merge(&data);
4773        let TransactionProver {
4774            tx_step_prover: _,
4775            tx_wrap_prover,
4776            merge_step_prover,
4777        } = TransactionProver::make(None);
4778
4779        let mut witnesses: Witness<Fp> = Witness::new::<StepMergeProof>();
4780        // witnesses.ocaml_aux = read_witnesses("fps_merge.txt").unwrap();
4781
4782        let WrapProof { proof, .. } = generate_merge_proof(
4783            MergeParams {
4784                statement: (&*statement).try_into().unwrap(),
4785                proofs: &proofs,
4786                message: &message,
4787                step_prover: &merge_step_prover,
4788                wrap_prover: &tx_wrap_prover,
4789                only_verify_constraints: false,
4790                expected_step_proof: None,
4791                // expected_step_proof: Some(
4792                //     "fb89b6d51ce5ed6fe7815b86ca37a7dcdc34d9891b4967692d3751dad32842f8",
4793                // ),
4794                ocaml_wrap_witness: None,
4795                // ocaml_wrap_witness: Some(read_witnesses("fqs_merge.txt").unwrap()),
4796            },
4797            &mut witnesses,
4798        )
4799        .unwrap();
4800        let proof_json = serde_json::to_vec(&proof.proof).unwrap();
4801
4802        let _sum = dbg!(sha256_sum(&proof_json));
4803    }
4804
4805    #[test]
4806    #[ignore = "Failing due to circuits"]
4807    fn test_proof_zkapp_sig() {
4808        let data = std::fs::read(
4809            Path::new(env!("CARGO_MANIFEST_DIR"))
4810                .join(devnet_circuit_directory())
4811                .join("tests")
4812                .join("command-1-0.bin"),
4813        )
4814        .unwrap();
4815
4816        let (statement, tx_witness, message) = extract_request(&data);
4817
4818        let ZkappProver {
4819            tx_wrap_prover,
4820            merge_step_prover,
4821            step_opt_signed_opt_signed_prover,
4822            step_opt_signed_prover,
4823            step_proof_prover,
4824        } = ZkappProver::make(None);
4825
4826        dbg!(step_opt_signed_opt_signed_prover.rows_rev.len());
4827        // dbg!(step_opt_signed_opt_signed_prover.rows_rev.iter().map(|v| v.len()).collect::<Vec<_>>());
4828
4829        let LedgerProof { proof, .. } = generate_zkapp_proof(ZkappParams {
4830            statement: &statement,
4831            tx_witness: &tx_witness,
4832            message: &message,
4833            step_opt_signed_opt_signed_prover: &step_opt_signed_opt_signed_prover,
4834            step_opt_signed_prover: &step_opt_signed_prover,
4835            step_proof_prover: &step_proof_prover,
4836            merge_step_prover: &merge_step_prover,
4837            tx_wrap_prover: &tx_wrap_prover,
4838            opt_signed_path: None,
4839            // opt_signed_path: Some("zkapp_opt_signed"),
4840            proved_path: None,
4841        })
4842        .unwrap();
4843
4844        let proof_json = serde_json::to_vec(&proof.proof.proof).unwrap();
4845        let _sum = dbg!(sha256_sum(&proof_json));
4846    }
4847
4848    #[test]
4849    #[ignore = "Failing due to circuits"]
4850    fn test_proof_zkapp_proof() {
4851        let data = std::fs::read(
4852            Path::new(env!("CARGO_MANIFEST_DIR"))
4853                .join(devnet_circuit_directory())
4854                .join("tests")
4855                .join("zkapp-command-with-proof-128-1.bin"),
4856        )
4857        .unwrap();
4858
4859        let (statement, tx_witness, message) = extract_request(&data);
4860
4861        let ZkappProver {
4862            tx_wrap_prover,
4863            merge_step_prover,
4864            step_opt_signed_opt_signed_prover,
4865            step_opt_signed_prover,
4866            step_proof_prover,
4867        } = ZkappProver::make(None);
4868
4869        let LedgerProof { proof, .. } = generate_zkapp_proof(ZkappParams {
4870            statement: &statement,
4871            tx_witness: &tx_witness,
4872            message: &message,
4873            step_opt_signed_opt_signed_prover: &step_opt_signed_opt_signed_prover,
4874            step_opt_signed_prover: &step_opt_signed_prover,
4875            step_proof_prover: &step_proof_prover,
4876            merge_step_prover: &merge_step_prover,
4877            tx_wrap_prover: &tx_wrap_prover,
4878            opt_signed_path: None,
4879            proved_path: None,
4880            // opt_signed_path: Some("zkapp_proof"),
4881            // proved_path: Some("zkapp_proof2"),
4882        })
4883        .unwrap();
4884
4885        let proof_json = serde_json::to_vec(&proof.proof.proof).unwrap();
4886        let _sum = dbg!(sha256_sum(&proof_json));
4887    }
4888
4889    #[test]
4890    #[ignore = "Failing due to circuits"]
4891    fn test_block_proof() {
4892        let data = std::fs::read(
4893            Path::new(env!("CARGO_MANIFEST_DIR"))
4894                .join(devnet_circuit_directory())
4895                .join("tests")
4896                .join("block_input-2483246-0.bin"),
4897        )
4898        .unwrap();
4899
4900        let blockchain_input: v2::ProverExtendBlockchainInputStableV2 =
4901            read_binprot(&mut data.as_slice());
4902
4903        let BlockProver {
4904            block_step_prover,
4905            block_wrap_prover,
4906            tx_wrap_prover,
4907        } = BlockProver::make(None, None);
4908        let mut witnesses: Witness<Fp> = Witness::new::<StepBlockProof>();
4909        // witnesses.ocaml_aux = read_witnesses("block_fps.txt").unwrap();
4910
4911        let WrapProof { proof, .. } = generate_block_proof(
4912            BlockParams {
4913                input: &blockchain_input,
4914                block_step_prover: &block_step_prover,
4915                block_wrap_prover: &block_wrap_prover,
4916                tx_wrap_prover: &tx_wrap_prover,
4917                only_verify_constraints: false,
4918                expected_step_proof: None,
4919                ocaml_wrap_witness: None,
4920                // expected_step_proof: Some(
4921                //     "a82a10e5c276dd6dc251241dcbad005201034ffff5752516a179f317dfe385f5",
4922                // ),
4923                // ocaml_wrap_witness: Some(read_witnesses("block_fqs.txt").unwrap()),
4924            },
4925            &mut witnesses,
4926        )
4927        .unwrap();
4928
4929        let proof_json = serde_json::to_vec(&proof.proof).unwrap();
4930        let _sum = dbg!(sha256_sum(&proof_json));
4931    }
4932
4933    #[test]
4934    fn make_rsa_key() {
4935        use rsa::{
4936            pkcs1::{EncodeRsaPrivateKey, EncodeRsaPublicKey},
4937            pkcs8::LineEnding::LF,
4938            RsaPrivateKey, RsaPublicKey,
4939        };
4940
4941        let mut rng = rand::thread_rng();
4942        let bits = 2048;
4943        let priv_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
4944        let pub_key = RsaPublicKey::from(&priv_key);
4945
4946        let priv_key = priv_key.to_pkcs1_pem(LF).unwrap();
4947        let priv_key: &str = priv_key.as_ref();
4948        println!("private:\n{}", priv_key);
4949
4950        let pub_key = pub_key.to_pkcs1_pem(LF).unwrap();
4951        println!("public:\n{}", pub_key);
4952    }
4953
4954    #[test]
4955    fn add_private_key_to_block_proof_input() {
4956        use mina_p2p_messages::binprot::BinProtWrite;
4957        use rsa::{pkcs1::DecodeRsaPrivateKey, Pkcs1v15Encrypt};
4958
4959        #[derive(binprot::macros::BinProtRead)]
4960        struct DumpBlockProof {
4961            input: Box<v2::ProverExtendBlockchainInputStableV2>,
4962            key: Vec<u8>,
4963            error: Vec<u8>,
4964        }
4965
4966        let rsa_private_key = {
4967            let Ok(home) = std::env::var("HOME") else {
4968                eprintln!("$HOME not set");
4969                return;
4970            };
4971            let Ok(string) = std::fs::read_to_string(format!("{home}/.openmina/debug/rsa.priv"))
4972            else {
4973                eprintln!("Missing private key");
4974                return;
4975            };
4976            rsa::RsaPrivateKey::from_pkcs1_pem(&string).unwrap()
4977        };
4978
4979        let DumpBlockProof {
4980            mut input,
4981            key,
4982            error,
4983        } = {
4984            let Ok(data) = std::fs::read("/tmp/block_proof.binprot") else {
4985                eprintln!("Missing block proof");
4986                return;
4987            };
4988            DumpBlockProof::binprot_read(&mut data.as_slice()).unwrap()
4989        };
4990        eprintln!("error was: {}", String::from_utf8_lossy(&error));
4991
4992        let producer_private_key = {
4993            let producer_private_key = rsa_private_key.decrypt(Pkcs1v15Encrypt, &key).unwrap();
4994            v2::SignatureLibPrivateKeyStableV1::binprot_read(&mut producer_private_key.as_slice())
4995                .unwrap()
4996        };
4997
4998        input.prover_state.producer_private_key = producer_private_key;
4999
5000        let mut file = std::fs::File::create("/tmp/block_proof_with_key.binprot").unwrap();
5001        input.binprot_write(&mut file).unwrap();
5002        file.sync_all().unwrap();
5003        eprintln!("saved to /tmp/block_proof_with_key.binprot");
5004    }
5005
5006    #[test]
5007    #[ignore = "Failing due to circuits"]
5008    fn test_proofs() {
5009        let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
5010            .join(devnet_circuit_directory())
5011            .join("tests");
5012
5013        if !base_dir.exists() {
5014            eprintln!("{:?} not found", base_dir);
5015            panic!();
5016        }
5017
5018        let BlockProver {
5019            block_step_prover,
5020            block_wrap_prover,
5021            tx_wrap_prover: _,
5022        } = BlockProver::make(None, None);
5023        let TransactionProver { tx_step_prover, .. } = TransactionProver::make(None);
5024        let ZkappProver {
5025            tx_wrap_prover,
5026            merge_step_prover,
5027            step_opt_signed_opt_signed_prover: zkapp_step_opt_signed_opt_signed_prover,
5028            step_opt_signed_prover: zkapp_step_opt_signed_prover,
5029            step_proof_prover: zkapp_step_proof_prover,
5030        } = ZkappProver::make(None);
5031
5032        // TODO: Compare checksum with OCaml
5033        #[rustfmt::skip]
5034        let zkapp_cases = [
5035            // zkapp proof with signature authorization
5036            ("command-1-0.bin", None, None, "b5295e34d8f4b0f349fc48c4f46e9bd400c1f3e551deab75e3fc541282f6d714"),
5037            // zkapp proof with proof authorization
5038            ("zkapp-command-with-proof-128-1.bin", None, None, "daa090e212c9fcd4e0c7fa70de4708878a6ac0186238d6ca094129fad1cfa5c2"),
5039            // zkapp with multiple account updates
5040            // ("zkapp_2_0_rampup4.bin", None, None, "03153d1c5b934e00c7102d3683f27572b6e8bfe0335817cb822d701c83415930"),
5041        ];
5042
5043        for (file, opt_signed_path, proved_path, expected_sum) in zkapp_cases {
5044            let data = std::fs::read(base_dir.join(file)).unwrap();
5045            let (statement, tx_witness, message) = extract_request(&data);
5046
5047            let LedgerProof { proof, .. } = generate_zkapp_proof(ZkappParams {
5048                statement: &statement,
5049                tx_witness: &tx_witness,
5050                message: &message,
5051                step_opt_signed_opt_signed_prover: &zkapp_step_opt_signed_opt_signed_prover,
5052                step_opt_signed_prover: &zkapp_step_opt_signed_prover,
5053                step_proof_prover: &zkapp_step_proof_prover,
5054                merge_step_prover: &merge_step_prover,
5055                tx_wrap_prover: &tx_wrap_prover,
5056                opt_signed_path,
5057                proved_path,
5058            })
5059            .unwrap();
5060
5061            let proof_json = serde_json::to_vec(&proof.proof.proof).unwrap();
5062            let sum = dbg!(sha256_sum(&proof_json));
5063
5064            assert_eq!(sum, expected_sum);
5065        }
5066
5067        // Block proof
5068        #[allow(clippy::single_element_loop)]
5069        for (filename, fps_filename) in [
5070            ("block_input-2483246-0.bin", None),
5071            // ("block_prove_inputs_7.bin", None),
5072        ] {
5073            let data = std::fs::read(base_dir.join(filename)).unwrap();
5074
5075            let blockchain_input: v2::ProverExtendBlockchainInputStableV2 =
5076                read_binprot(&mut data.as_slice());
5077
5078            let mut witnesses: Witness<Fp> = Witness::new::<StepBlockProof>();
5079            if let Some(filename) = fps_filename {
5080                witnesses.ocaml_aux = read_witnesses(filename).unwrap();
5081            };
5082
5083            let WrapProof { proof, .. } = generate_block_proof(
5084                BlockParams {
5085                    input: &blockchain_input,
5086                    block_step_prover: &block_step_prover,
5087                    block_wrap_prover: &block_wrap_prover,
5088                    tx_wrap_prover: &tx_wrap_prover,
5089                    only_verify_constraints: false,
5090                    expected_step_proof: None,
5091                    ocaml_wrap_witness: None,
5092                    // expected_step_proof: Some(
5093                    //     "a82a10e5c276dd6dc251241dcbad005201034ffff5752516a179f317dfe385f5",
5094                    // ),
5095                    // ocaml_wrap_witness: Some(read_witnesses("block_fqs.txt").unwrap()),
5096                },
5097                &mut witnesses,
5098            )
5099            .unwrap();
5100            let proof_json = serde_json::to_vec(&proof.proof).unwrap();
5101
5102            let sum = sha256_sum(&proof_json);
5103            assert_eq!(
5104                sum,
5105                "2488ef14831ce4bf196ec381fe955312b3c0946354af1c2bcedffb38c1072147"
5106            );
5107        }
5108
5109        // Merge proof
5110        {
5111            let data = std::fs::read(base_dir.join("merge-100-0.bin")).unwrap();
5112
5113            let (statement, proofs, message) = extract_merge(&data);
5114
5115            let mut witnesses: Witness<Fp> = Witness::new::<StepMergeProof>();
5116            // witnesses.ocaml_aux = read_witnesses("fps_merge.txt").unwrap();
5117
5118            let WrapProof { proof, .. } = generate_merge_proof(
5119                MergeParams {
5120                    statement: (&*statement).try_into().unwrap(),
5121                    proofs: &proofs,
5122                    message: &message,
5123                    step_prover: &merge_step_prover,
5124                    wrap_prover: &tx_wrap_prover,
5125                    only_verify_constraints: false,
5126                    expected_step_proof: None,
5127                    ocaml_wrap_witness: None,
5128                    // expected_step_proof: Some(
5129                    //     "fb89b6d51ce5ed6fe7815b86ca37a7dcdc34d9891b4967692d3751dad32842f8",
5130                    // ),
5131                    // ocaml_wrap_witness: Some(read_witnesses("fqs_merge.txt").unwrap()),
5132                },
5133                &mut witnesses,
5134            )
5135            .unwrap();
5136
5137            let proof_json = serde_json::to_vec(&proof.proof).unwrap();
5138
5139            let sum = dbg!(sha256_sum(&proof_json));
5140            assert_eq!(
5141                sum,
5142                "da069a6752ca677fdb2e26e643c26d4e28f6e52210547d6ed99f9dd7fd324803"
5143            );
5144        }
5145
5146        // TODO: Compare checksum with OCaml
5147        // Same values than OCaml
5148        #[rustfmt::skip]
5149        let requests = [
5150            ("command-0-1.bin", "cbcb54861c5c65b7d454e7add9a780fa574f5145aa225387a287abe612925abb"),
5151            // ("request_payment_1_rampup4.bin", "a5391b8ac8663a06a0a57ee6b6479e3cf4d95dfbb6d0688e439cb8c36cf187f6"),
5152            // ("coinbase_0_rampup4.bin", "a2ce1982938687ca3ba3b1994e5100090a80649aefb1f0d10f736a845dab2812"),
5153            // ("coinbase_1_rampup4.bin", "1120c9fe25078866e0df90fd09a41a2f5870351a01c8a7227d51a19290883efe"),
5154            // ("coinbase_2_rampup4.bin", "7875781e8ea4a7eb9035a5510cd54cfc33229867f46f97e68fbb9a7a6534ec74"),
5155            // ("coinbase_3_rampup4.bin", "12875cb8a182d550eb527e3561ad71458e1ca651ea399ee1878244c9b8f04966"),
5156            // ("coinbase_4_rampup4.bin", "718cdc4b4803afd0f4d6ca38937211b196609f71c393f1195a55ff101d58f843"),
5157            // ("coinbase_5_rampup4.bin", "a0d03705274ee56908a3fad1c260c56a0e07566d58c19bbba5c95cc8a9d11ee0"),
5158            // ("coinbase_6_rampup4.bin", "4b213eeea865b9e6253f3c074017553243420b3183860a7f7720648677c02c54"),
5159            // ("coinbase_7_rampup4.bin", "78fcec79bf2013d4f3d97628b316da7410af3c92a73dc26abc3ea63fbe92372a"),
5160            // ("coinbase_8_rampup4.bin", "169f1ad4739d0a3fe194a66497bcabbca8dd5584cd83d13a5addede4b5a49e9d"),
5161            // ("coinbase_9_rampup4.bin", "dfe50b656e0c0520a9678a1d34dd68af4620ea9909461b39c24bdda69504ed4b"),
5162            // ("fee_transfer_0_rampup4.bin", "58d711bcc6377037e1c6a1334a49d53789b6e9c93aa343bda2f736cfc40d90b3"),
5163            // ("fee_transfer_1_rampup4.bin", "791644dc9b5f17be24cbacab83e8b1f4b2ba7218e09ec718b37f1cd280b6c467"),
5164            // ("fee_transfer_2_rampup4.bin", "ea02567ed5f116191ece0e7f6ac78a3b014079509457d03dd8d654e601404722"),
5165            // ("fee_transfer_3_rampup4.bin", "6048053909b20e57cb104d1838c3aca565462605c69ced184f1a0e31b18c9c05"),
5166            // ("fee_transfer_4_rampup4.bin", "1d6ab348dde0d008691dbb30ddb1412fabd5fe1adca788779c3674e2af412211"),
5167            // ("fee_transfer_5_rampup4.bin", "a326eeeea08778795f35da77b43fc01c0c4b6cbf89cb1bb460c80bfab97d339e"),
5168            // ("fee_transfer_6_rampup4.bin", "6b95aa737e1c8351bbb7a141108a73c808cb92aae9e266ecce13f679d6f6b2df"),
5169            // ("fee_transfer_7_rampup4.bin", "5d97141c3adf576503381e485f5ab20ed856448880658a0a56fb23567225875c"),
5170            // ("fee_transfer_8_rampup4.bin", "e1fa6b5a88b184428a0918cd4bd56952b54f05a5dc175b17e154204533167a78"),
5171            // ("fee_transfer_9_rampup4.bin", "087a07eddedf5de18b2f2bd7ded3cd474d00a0030e9c13d7a5fd2433c72fc7d5"),
5172        ];
5173
5174        for (file, expected_sum) in requests {
5175            let data = std::fs::read(base_dir.join(file)).unwrap();
5176            let (statement, tx_witness, message) = extract_request(&data);
5177
5178            let mut witnesses: Witness<Fp> = Witness::new::<StepTransactionProof>();
5179
5180            if file == "request_payment_0_rampup4.bin" {
5181                witnesses.ocaml_aux = read_witnesses("fps_rampup4.txt").unwrap();
5182            }
5183
5184            let WrapProof { proof, .. } = generate_tx_proof(
5185                TransactionParams {
5186                    statement: &statement,
5187                    tx_witness: &tx_witness,
5188                    message: &message,
5189                    tx_step_prover: &tx_step_prover,
5190                    tx_wrap_prover: &tx_wrap_prover,
5191                    only_verify_constraints: false,
5192                    expected_step_proof: None,
5193                    ocaml_wrap_witness: None,
5194                },
5195                &mut witnesses,
5196            )
5197            .unwrap();
5198
5199            let proof_json = serde_json::to_vec(&proof.proof).unwrap();
5200            let sum = dbg!(sha256_sum(&proof_json));
5201
5202            if dbg!(&sum) != expected_sum {
5203                eprintln!("Wrong proof: {:?}", file);
5204                eprintln!("got sum:  {:?}", sum);
5205                eprintln!("expected: {:?}", expected_sum);
5206                panic!()
5207            }
5208        }
5209    }
5210}