mina_tree/proofs/
transaction.rs

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