mina_tree/proofs/
transaction.rs

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