mina_tree/proofs/
zkapp.rs

1use std::{cell::RefCell, rc::Rc};
2
3use ark_ff::{BigInteger256, Zero};
4use kimchi::proof::PointEvaluations;
5use mina_curves::pasta::{Fp, Fq};
6use mina_p2p_messages::{bigint::InvalidBigInt, v2};
7use poly_commitment::ipa::OpeningProof;
8use poseidon::hash::{
9    hash_with_kimchi,
10    params::{MINA_ACCOUNT_UPDATE_CONS, MINA_PROTO_STATE_BODY},
11};
12
13use crate::{
14    proofs::{
15        constants::{
16            make_step_zkapp_data, StepMergeProof, StepZkappOptSignedOptSignedProof,
17            WrapZkappOptSignedProof, WrapZkappProof,
18        },
19        field::{Boolean, CircuitVar, FieldWitness, ToBoolean},
20        merge::{generate_merge_proof, MergeParams},
21        public_input::{messages::MessagesForNextWrapProof, prepared_statement::DeferredValues},
22        step::{
23            extract_recursion_challenges, step, InductiveRule, OptFlag, PreviousProofStatement,
24            StepParams, StepProof,
25        },
26        transaction::{ProofWithPublic, ReducedMessagesForNextStepProof},
27        unfinalized::{AllEvals, EvalsWithPublicInput},
28        verification::prev_evals_to_p2p,
29        verifiers::make_zkapp_verifier_index,
30        wrap::{self, WrapParams, WrapProofState, WrapStatement},
31        zkapp::group::{State, ZkappCommandIntermediateState},
32    },
33    scan_state::{
34        currency::{Amount, Index, Signed, Slot},
35        fee_excess::FeeExcess,
36        pending_coinbase::{self, Stack, StackState},
37        scan_state::transaction_snark::{Registers, SokDigest, SokMessage, Statement},
38        transaction_logic::{
39            local_state::{
40                LocalState, LocalStateEnv, LocalStateSkeleton, StackFrame, StackFrameChecked,
41            },
42            protocol_state::{protocol_state_body_view, GlobalStateSkeleton},
43            zkapp_command::{AccountUpdate, CallForest, Control, WithHash, ZkAppCommand},
44            zkapp_statement::{TransactionCommitment, ZkappStatement},
45            TransactionFailure,
46        },
47    },
48    sparse_ledger::SparseLedger,
49    zkapps::{
50        snark::ZkappSnark,
51        zkapp_logic::{self, ApplyZkappParams},
52    },
53    AccountId, ControlTag, ToInputs, TokenId,
54};
55
56use self::group::SegmentBasic;
57
58use super::{
59    block::ProtocolStateBody,
60    constants::{
61        ForWrapData, ProofConstants, StepZkappOptSignedProof, StepZkappProvedProof,
62        WrapZkappProvedProof,
63    },
64    field::GroupAffine,
65    numbers::{
66        currency::{CheckedAmount, CheckedSigned},
67        nat::{CheckedIndex, CheckedSlot},
68    },
69    provers::devnet_circuit_directory,
70    to_field_elements::ToFieldElements,
71    transaction::{dummy_constraints, Check, Prover},
72    witness::Witness,
73    wrap::WrapProof,
74};
75
76pub struct ZkappParams<'a> {
77    pub statement: &'a v2::MinaStateBlockchainStateValueStableV2LedgerProofStatement,
78    pub tx_witness: &'a v2::TransactionWitnessStableV2,
79    pub message: &'a SokMessage,
80    pub step_opt_signed_opt_signed_prover: &'a Prover<Fp>,
81    pub step_opt_signed_prover: &'a Prover<Fp>,
82    pub step_proof_prover: &'a Prover<Fp>,
83    pub merge_step_prover: &'a Prover<Fp>,
84    pub tx_wrap_prover: &'a Prover<Fq>,
85
86    /// For debugging only
87    pub opt_signed_path: Option<&'a str>,
88    /// For debugging only
89    pub proved_path: Option<&'a str>,
90}
91
92pub mod group {
93    use super::*;
94
95    #[derive(Debug)]
96    pub enum SegmentBasic {
97        OptSignedOptSigned,
98        OptSigned,
99        Proved,
100    }
101
102    impl SegmentBasic {
103        pub fn of_controls(controls: &[&Control]) -> Self {
104            use Control::{NoneGiven, Proof, Signature};
105
106            match controls {
107                [Proof(_)] => Self::Proved,
108                [Signature(_) | NoneGiven] => Self::OptSigned,
109                [Signature(_) | NoneGiven, Signature(_) | NoneGiven] => Self::OptSignedOptSigned,
110                _ => panic!("Unsupported combination"),
111            }
112        }
113    }
114
115    #[derive(Debug)]
116    pub enum Kind {
117        New,
118        Same,
119        TwoNew,
120    }
121
122    #[derive(Debug)]
123    pub struct State<GlobalState, LocalState> {
124        pub global: GlobalState,
125        pub local: LocalState,
126    }
127
128    #[derive(Debug)]
129    pub struct ZkappCommandIntermediateState<GlobalState, LocalState, ConnectingLedgerHash> {
130        pub kind: Kind,
131        pub spec: SegmentBasic,
132        pub state_before: State<GlobalState, LocalState>,
133        pub state_after: State<GlobalState, LocalState>,
134        pub connecting_ledger: ConnectingLedgerHash,
135    }
136
137    fn intermediate_state<GlobalState, LocalState, ConnectingLedgerHash>(
138        kind: Kind,
139        spec: SegmentBasic,
140        before: &(GlobalState, LocalState, ConnectingLedgerHash),
141        after: &(GlobalState, LocalState, ConnectingLedgerHash),
142    ) -> ZkappCommandIntermediateState<GlobalState, LocalState, ConnectingLedgerHash>
143    where
144        GlobalState: Clone,
145        LocalState: Clone,
146        ConnectingLedgerHash: Clone,
147    {
148        let (global_before, local_before, _) = before;
149        let (global_after, local_after, connecting_ledger) = after;
150        ZkappCommandIntermediateState {
151            kind,
152            spec,
153            state_before: State {
154                global: global_before.clone(),
155                local: local_before.clone(),
156            },
157            state_after: State {
158                global: global_after.clone(),
159                local: local_after.clone(),
160            },
161            connecting_ledger: connecting_ledger.clone(),
162        }
163    }
164
165    // Note: Unlike OCaml, the returned value (the list) is not reversed, but we keep the same method name
166    pub fn group_by_zkapp_command_rev<'a, I, GlobalState, LocalState, ConnectingLedgerHash>(
167        zkapp_command: I,
168        stmtss: Vec<Vec<(GlobalState, LocalState, ConnectingLedgerHash)>>,
169    ) -> Vec<ZkappCommandIntermediateState<GlobalState, LocalState, ConnectingLedgerHash>>
170    where
171        I: IntoIterator<Item = &'a ZkAppCommand>,
172        GlobalState: Clone,
173        LocalState: Clone,
174        ConnectingLedgerHash: Clone,
175    {
176        let all_account_updates_list = zkapp_command
177            .into_iter()
178            .map(|zkapp_command| zkapp_command.all_account_updates_list());
179
180        let zkapp_account_updatess = std::iter::once(vec![])
181            .chain(all_account_updates_list)
182            .collect::<Vec<_>>();
183
184        let mut acc = Vec::<
185            ZkappCommandIntermediateState<GlobalState, LocalState, ConnectingLedgerHash>,
186        >::with_capacity(32);
187
188        // Convert to slices, to allow matching below
189        let zkapp_account_updatess = zkapp_account_updatess
190            .iter()
191            .map(|v| v.as_slice())
192            .collect::<Vec<_>>();
193        let stmtss = stmtss.iter().map(|v| v.as_slice()).collect::<Vec<_>>();
194
195        #[rustfmt::skip]
196        fn group_by_zkapp_command_rev_impl<G, L, C>(
197            zkapp_commands: &[&[AccountUpdate]],
198            stmtss: &[&[(G, L, C)]],
199            acc: &mut Vec<ZkappCommandIntermediateState<G, L, C>>,
200        )
201        where
202            G: Clone,
203            L: Clone,
204            C: Clone,
205        {
206            use Kind::{New, Same, TwoNew};
207            use Control::{Proof, Signature, NoneGiven};
208
209            let to_spec = |c: &Control| SegmentBasic::of_controls(&[c]);
210            let to_specs = |c1: &Control, c2: &Control| SegmentBasic::of_controls(&[c1, c2]);
211
212            fn prepend<'a, T: ?Sized>(value: &'a T, list: &[&'a T]) -> Vec<&'a T> {
213                // `T` here is actually a slice
214                let mut new_list = Vec::with_capacity(list.len() + 1);
215                new_list.push(value);
216                new_list.extend(list);
217                new_list
218            }
219
220            // I don't take responsibility for this code, see OCaml comments
221            // <https://github.com/MinaProtocol/mina/blob/78535ae3a73e0e90c5f66155365a934a15535779/src/lib/mina_base/zkapp_command.ml#L1590>
222            match (zkapp_commands, stmtss) {
223                ([] | [[]], [ _ ]) => {
224                    // eprintln!("GROUP 1");
225                },
226                ([[ AccountUpdate { authorization: a1, .. } ]], [[ before, after ]]) => {
227                    // eprintln!("GROUP 2");
228                    acc.push(intermediate_state(Same, to_spec(a1), before, after));
229                }
230                ([[], [AccountUpdate { authorization: a1, .. }]], [[ _ ], [ before, after ]]) => {
231                    // eprintln!("GROUP 3");
232                    acc.push(intermediate_state(New, to_spec(a1), before, after));
233                }
234                ([[AccountUpdate { authorization: a1 @ Proof(_), .. }, zkapp_command @ ..], zkapp_commands @ ..],
235                 [stmts @ [ before, after, ..], stmtss @ .. ]
236                ) => {
237                    // eprintln!("GROUP 4");
238                    let stmts = &stmts[1..];
239                    let zkapp_commands = prepend(zkapp_command, zkapp_commands);
240                    let stmtss = prepend(stmts, stmtss);
241
242                    acc.push(intermediate_state(Same, to_spec(a1), before, after));
243                    group_by_zkapp_command_rev_impl(zkapp_commands.as_slice(), stmtss.as_slice(), acc);
244                }
245                ([[], [AccountUpdate { authorization: a1 @ Proof(_), .. }, zkapp_command @ .. ], zkapp_commands @ ..],
246                 [[ _ ], stmts @ [ before, after, ..], stmtss @ ..]
247                ) => {
248                    // eprintln!("GROUP 5");
249                    let stmts = &stmts[1..];
250                    let zkapp_commands = prepend(zkapp_command, zkapp_commands);
251                    let stmtss = prepend(stmts, stmtss);
252
253                    acc.push(intermediate_state(New, to_spec(a1), before, after));
254                    group_by_zkapp_command_rev_impl(zkapp_commands.as_slice(), stmtss.as_slice(), acc);
255                }
256                ([zkapp_command @ [AccountUpdate { authorization: a1, .. }, AccountUpdate { authorization: Proof(_), .. }, ..], zkapp_commands @ ..],
257                 [stmts @ [before, after, ..], stmtss @ ..]
258                ) => {
259                    // eprintln!("GROUP 6");
260                    let stmts = &stmts[1..];
261                    let zkapp_command = &zkapp_command[1..];
262                    let zkapp_commands = prepend(zkapp_command, zkapp_commands);
263                    let stmtss = prepend(stmts, stmtss);
264
265                    acc.push(intermediate_state(Same, to_spec(a1), before, after));
266                    group_by_zkapp_command_rev_impl(zkapp_commands.as_slice(), stmtss.as_slice(), acc);
267                }
268                (zkapp_commands @ [[AccountUpdate { authorization: a1, .. }], zkapp_command @ [], [AccountUpdate { authorization: Proof(_), .. }, ..], ..],
269                 [stmts @ [before, after, ..], stmtss @ ..]
270                ) => {
271                    // eprintln!("GROUP 7");
272                    let stmts = &stmts[1..];
273                    let zkapp_commands = &zkapp_commands[2..];
274                    let zkapp_commands = prepend(*zkapp_command, zkapp_commands);
275                    let stmtss = prepend(stmts, stmtss);
276
277                    acc.push(intermediate_state(Same, to_spec(a1), before, after));
278                    group_by_zkapp_command_rev_impl(zkapp_commands.as_slice(), stmtss.as_slice(), acc);
279                }
280                ([[AccountUpdate { authorization: a1 @ (Signature(_) | NoneGiven), .. },
281                   AccountUpdate { authorization: a2 @ (Signature(_) | NoneGiven), .. },
282                   zkapp_command @ ..], zkapp_commands @ ..],
283                 [stmts @ [before, _, after, ..], stmtss @ ..]
284                ) => {
285                    // eprintln!("GROUP 8");
286                    let stmts = &stmts[2..];
287                    let zkapp_commands = prepend(zkapp_command, zkapp_commands);
288                    let stmtss = prepend(stmts, stmtss);
289
290                    acc.push(intermediate_state(Same, to_specs(a1, a2), before, after));
291                    group_by_zkapp_command_rev_impl(zkapp_commands.as_slice(), stmtss.as_slice(), acc);
292                }
293                ([[], zkapp_command @ [AccountUpdate { authorization: a1, .. }, AccountUpdate { authorization: Proof(_), .. }, ..], zkapp_commands @ ..],
294                 [[ _ ], stmts @ [before, after, ..], stmtss @ ..]
295                ) => {
296                    // eprintln!("GROUP 9");
297                    let stmts = &stmts[1..];
298                    let zkapp_command = &zkapp_command[1..];
299                    let zkapp_commands = prepend(zkapp_command, zkapp_commands);
300                    let stmtss = prepend(stmts, stmtss);
301
302                    acc.push(intermediate_state(New, to_spec(a1), before, after));
303                    group_by_zkapp_command_rev_impl(zkapp_commands.as_slice(), stmtss.as_slice(), acc);
304                }
305                ([[], [AccountUpdate { authorization: a1 @ (Signature(_) | NoneGiven), .. },
306                       AccountUpdate { authorization: a2 @ (Signature(_) | NoneGiven), .. },
307                       zkapp_command @ ..], zkapp_commands @ ..],
308                 [[ _ ], stmts @ [before, _, after, ..], stmtss @ ..] ) => {
309                    // eprintln!("GROUP 10");
310                    let stmts = &stmts[2..];
311                    let zkapp_commands = prepend(zkapp_command, zkapp_commands);
312                    let stmtss = prepend(stmts, stmtss);
313
314                    acc.push(intermediate_state(New, to_specs(a1, a2), before, after));
315                    group_by_zkapp_command_rev_impl(zkapp_commands.as_slice(), stmtss.as_slice(), acc);
316                }
317                ([[AccountUpdate { authorization: a1 @ (Signature(_) | NoneGiven), .. }],
318                  [AccountUpdate { authorization: a2 @ (Signature(_) | NoneGiven), .. }, zkapp_command @ ..],
319                  zkapp_commands @ ..],
320                 [[before, _after1], stmts @ [_before2, after, ..], stmtss @ .. ]
321                ) => {
322                    // eprintln!("GROUP 11");
323                    let stmts = &stmts[1..];
324                    let zkapp_commands = prepend(zkapp_command, zkapp_commands);
325                    let stmtss = prepend(stmts, stmtss);
326
327                    acc.push(intermediate_state(New, to_specs(a1, a2), before, after));
328                    group_by_zkapp_command_rev_impl(zkapp_commands.as_slice(), stmtss.as_slice(), acc);
329                }
330                (zkapp_commands @ [[], [AccountUpdate { authorization: a1, .. }, zkapp_command @ ..],
331                  [AccountUpdate { authorization: Proof(_), .. }, ..], ..],
332                 stmtss @ [[ _ ], [before], stmts @ [after], _, ..]
333                ) => {
334                    // eprintln!("GROUP 12");
335                    let stmtss = &stmtss[3..];
336                    let zkapp_commands = &zkapp_commands[2..];
337                    let zkapp_commands = prepend(zkapp_command, zkapp_commands);
338                    let stmtss = prepend(*stmts, stmtss);
339
340                    acc.push(intermediate_state(New, to_spec(a1), before, after));
341                    group_by_zkapp_command_rev_impl(zkapp_commands.as_slice(), stmtss.as_slice(), acc);
342                }
343                ([[], [AccountUpdate { authorization: a1 @ (Signature(_) | NoneGiven), .. }],
344                  [AccountUpdate { authorization: a2 @ (Signature(_) | NoneGiven), .. }, zkapp_command @ ..],
345                  zkapp_commands @ ..],
346                 [[ _ ], [before, _after1], stmts @ [_before2, after, ..], stmtss @ ..]
347                ) => {
348                    // eprintln!("GROUP 13");
349                    let stmts = &stmts[1..];
350                    let zkapp_commands = prepend(zkapp_command, zkapp_commands);
351                    let stmtss = prepend(stmts, stmtss);
352
353                    acc.push(intermediate_state(TwoNew, to_specs(a1, a2), before, after));
354                    group_by_zkapp_command_rev_impl(zkapp_commands.as_slice(), stmtss.as_slice(), acc);
355                }
356                ([[AccountUpdate { authorization: a1, .. }]], [[before, after, ..], ..]) => {
357                    // eprintln!("GROUP 14");
358                    acc.push(intermediate_state(Same, to_spec(a1), before, after));
359                }
360                ([[], [AccountUpdate { authorization: a1, .. }], [], ..], [[ _ ], [before, after, ..], ..]) => {
361                    // eprintln!("GROUP 15");
362                    acc.push(intermediate_state(New, to_spec(a1), before, after));
363                }
364                _ => panic!("invalid")
365            }
366        }
367
368        group_by_zkapp_command_rev_impl(&zkapp_account_updatess, &stmtss, &mut acc);
369        acc
370    }
371}
372
373pub type StartData = StartDataSkeleton<
374    ZkAppCommand, // account_updates
375    bool,         // will_succeed
376>;
377
378#[derive(Clone, Debug)]
379pub struct StartDataSkeleton<AccountUpdates, WillSucceed> {
380    pub account_updates: AccountUpdates,
381    pub memo_hash: Fp,
382    pub will_succeed: WillSucceed,
383}
384
385// TODO: De-duplicate with the one in `transaction_logic.rs`
386#[derive(Debug, Clone, PartialEq)]
387pub struct WithStackHash<T> {
388    pub elt: T,
389    pub stack_hash: Fp,
390}
391
392fn accumulate_call_stack_hashes(
393    hash_frame: impl Fn(&WithHash<StackFrame>) -> Fp,
394    frames: &[WithHash<StackFrame>],
395) -> Vec<WithStackHash<WithHash<StackFrame>>> {
396    match frames {
397        [] => vec![],
398        [f, fs @ ..] => {
399            let h_f = hash_frame(f);
400            let mut tl = accumulate_call_stack_hashes(hash_frame, fs);
401            let h_tl = match tl.as_slice() {
402                [] => Fp::zero(),
403                [t, ..] => t.stack_hash,
404            };
405
406            tl.insert(
407                0,
408                WithStackHash {
409                    elt: f.clone(),
410                    stack_hash: hash_with_kimchi(&MINA_ACCOUNT_UPDATE_CONS, &[h_f, h_tl]),
411                },
412            );
413
414            tl
415        }
416    }
417}
418
419pub type LocalStateForWitness = LocalStateSkeleton<
420    SparseLedger,                                       // ledger
421    StackFrame,                                         // stack_frame
422    WithHash<Vec<WithStackHash<WithHash<StackFrame>>>>, // call_stack
423    TransactionCommitment,                              // commitments
424    Signed<Amount>,                                     // excess & supply_increase
425    Vec<Vec<TransactionFailure>>,                       // failure_status_tbl
426    bool,                                               // success & will_succeed
427    Index,                                              // account_update_index
428>;
429
430#[derive(Debug)]
431pub struct ZkappCommandSegmentWitness<'a> {
432    pub global_first_pass_ledger: SparseLedger,
433    pub global_second_pass_ledger: SparseLedger,
434    pub local_state_init: LocalStateForWitness,
435    pub start_zkapp_command: Vec<StartData>,
436    pub state_body: &'a v2::MinaStateProtocolStateBodyValueStableV2,
437    pub init_stack: Stack,
438    pub block_global_slot: Slot,
439}
440
441pub struct ZkappCommandsWithContext<'a> {
442    pub pending_coinbase_init_stack: pending_coinbase::Stack,
443    pub pending_coinbase_of_statement: pending_coinbase::StackState,
444    pub first_pass_ledger: SparseLedger,
445    pub second_pass_ledger: SparseLedger,
446    pub connecting_ledger_hash: Fp,
447    pub zkapp_command: &'a ZkAppCommand,
448}
449
450pub struct ZkappCommandWitnessesParams<'a> {
451    pub global_slot: Slot,
452    pub state_body: &'a v2::MinaStateProtocolStateBodyValueStableV2,
453    pub fee_excess: Signed<Amount>,
454    pub zkapp_commands_with_context: Vec<ZkappCommandsWithContext<'a>>,
455}
456
457pub fn zkapp_command_witnesses_exn(
458    params: ZkappCommandWitnessesParams<'_>,
459) -> Result<
460    Vec<(
461        ZkappCommandSegmentWitness<'_>,
462        group::SegmentBasic,
463        Statement<SokDigest>,
464    )>,
465    InvalidBigInt,
466> {
467    let ZkappCommandWitnessesParams {
468        global_slot,
469        state_body,
470        fee_excess,
471        zkapp_commands_with_context,
472    } = params;
473
474    let supply_increase = Signed::<Amount>::zero();
475    let state_view = protocol_state_body_view(state_body)?;
476
477    let (_, _, will_succeeds, mut states) = zkapp_commands_with_context.iter().fold(
478        (fee_excess, supply_increase, vec![], vec![]),
479        |acc, v| {
480            let (fee_excess, supply_increase, mut will_succeeds, mut statess) = acc;
481
482            let ZkappCommandsWithContext {
483                pending_coinbase_init_stack: _,
484                pending_coinbase_of_statement: _,
485                first_pass_ledger,
486                second_pass_ledger,
487                connecting_ledger_hash,
488                zkapp_command,
489            } = v;
490
491            let mut states = Vec::with_capacity(16);
492            let txn_applied = {
493                let partial_txn = first_pass_ledger
494                    .clone()
495                    .apply_zkapp_first_pass_unchecked_with_states(
496                        &mut states,
497                        global_slot,
498                        &state_view,
499                        fee_excess,
500                        supply_increase,
501                        second_pass_ledger,
502                        zkapp_command,
503                    )
504                    .unwrap();
505
506                second_pass_ledger
507                    .clone()
508                    .apply_zkapp_second_pass_unchecked_with_states(&mut states, partial_txn)
509                    .unwrap()
510            };
511
512            let will_succeed = txn_applied.command.status.is_applied();
513
514            let states_with_connecting_ledger = states
515                .iter()
516                .cloned()
517                .map(|(global, local)| (global, local, *connecting_ledger_hash))
518                .collect::<Vec<_>>();
519
520            let final_state = {
521                let (global_state, _local_state, _connecting_ledger) =
522                    states_with_connecting_ledger.last().unwrap();
523                global_state
524            };
525
526            let fee_excess = final_state.fee_excess;
527            let supply_increase = final_state.supply_increase;
528            will_succeeds.push(will_succeed);
529            statess.push(states_with_connecting_ledger);
530
531            (fee_excess, supply_increase, will_succeeds, statess)
532        },
533    );
534
535    states.insert(0, vec![states[0][0].clone()]);
536    let states = group::group_by_zkapp_command_rev(
537        zkapp_commands_with_context.iter().map(|v| v.zkapp_command),
538        states,
539    );
540
541    let (mut commitment, mut full_commitment) = {
542        let LocalState {
543            transaction_commitment,
544            full_transaction_commitment,
545            ..
546        } = LocalState::dummy();
547        (
548            TransactionCommitment(transaction_commitment),
549            TransactionCommitment(full_transaction_commitment),
550        )
551    };
552
553    let remaining_zkapp_command = {
554        let zkapp_commands = zkapp_commands_with_context
555            .iter()
556            .zip(will_succeeds)
557            .map(|(v, will_succeed)| {
558                let ZkappCommandsWithContext {
559                    pending_coinbase_init_stack,
560                    pending_coinbase_of_statement,
561                    first_pass_ledger: _,
562                    second_pass_ledger: _,
563                    connecting_ledger_hash: _,
564                    zkapp_command,
565                } = v;
566
567                (
568                    pending_coinbase_init_stack,
569                    pending_coinbase_of_statement,
570                    StartData {
571                        account_updates: (*zkapp_command).clone(),
572                        memo_hash: zkapp_command.memo.hash(),
573                        will_succeed,
574                    },
575                )
576            })
577            .collect::<Vec<_>>();
578
579        zkapp_commands
580    };
581    let mut remaining_zkapp_command = remaining_zkapp_command.as_slice();
582
583    let mut pending_coinbase_init_stack = Stack::empty();
584    let mut pending_coinbase_stack_state = StackState {
585        source: Stack::empty(),
586        target: Stack::empty(),
587    };
588
589    let w = Vec::with_capacity(32);
590    let result = states.into_iter().fold(w, |mut witnesses, s| {
591        let ZkappCommandIntermediateState {
592            kind,
593            spec,
594            state_before:
595                State {
596                    global: source_global,
597                    local: mut source_local,
598                },
599            state_after:
600                State {
601                    global: target_global,
602                    local: mut target_local,
603                },
604            connecting_ledger,
605        } = s;
606
607        source_local.failure_status_tbl = vec![];
608        target_local.failure_status_tbl = vec![];
609
610        let current_commitment = commitment;
611        let current_full_commitment = full_commitment;
612
613        let (
614            start_zkapp_command,
615            next_commitment,
616            next_full_commitment,
617            pending_coinbase_init_stack,
618            pending_coinbase_stack_state,
619        ) = {
620            type TC = TransactionCommitment;
621
622            let empty_if_last = |mk: Box<dyn Fn() -> (TC, TC) + '_>| -> (TC, TC) {
623                let calls = target_local.stack_frame.calls.0.as_slice();
624                let call_stack = target_local.call_stack.0.as_slice();
625
626                match (calls, call_stack) {
627                    ([], []) => (TC::empty(), TC::empty()),
628                    _ => mk(),
629                }
630            };
631
632            let mk_next_commitment = |zkapp_command: &ZkAppCommand| {
633                empty_if_last(Box::new(|| {
634                    let next_commitment = zkapp_command.commitment();
635                    let memo_hash = zkapp_command.memo.hash();
636                    let fee_payer_hash =
637                        AccountUpdate::of_fee_payer(zkapp_command.fee_payer.clone()).digest();
638                    let next_full_commitment =
639                        next_commitment.create_complete(memo_hash, fee_payer_hash);
640
641                    (next_commitment, next_full_commitment)
642                }))
643            };
644
645            match kind {
646                group::Kind::Same => {
647                    let (next_commitment, next_full_commitment) =
648                        empty_if_last(Box::new(|| (current_commitment, current_full_commitment)));
649                    (
650                        Vec::new(),
651                        next_commitment,
652                        next_full_commitment,
653                        pending_coinbase_init_stack.clone(),
654                        pending_coinbase_stack_state.clone(),
655                    )
656                }
657                group::Kind::New => match remaining_zkapp_command {
658                    [v, rest @ ..] => {
659                        let (
660                            pending_coinbase_init_stack1,
661                            pending_coinbase_stack_state1,
662                            zkapp_command,
663                        ) = v;
664
665                        let (commitment2, full_commitment2) =
666                            mk_next_commitment(&zkapp_command.account_updates);
667
668                        remaining_zkapp_command = rest;
669                        commitment = commitment2;
670                        full_commitment = full_commitment2;
671                        pending_coinbase_init_stack = (*pending_coinbase_init_stack1).clone();
672                        pending_coinbase_stack_state = (*pending_coinbase_stack_state1).clone();
673
674                        (
675                            vec![zkapp_command.clone()],
676                            commitment2,
677                            full_commitment2,
678                            pending_coinbase_init_stack.clone(),
679                            pending_coinbase_stack_state.clone(),
680                        )
681                    }
682                    _ => panic!("Not enough remaining zkapp_command"),
683                },
684                group::Kind::TwoNew => match remaining_zkapp_command {
685                    [v1, v2, rest @ ..] => {
686                        let (
687                            pending_coinbase_init_stack1,
688                            pending_coinbase_stack_state1,
689                            zkapp_command1,
690                        ) = v1;
691                        let (
692                            _pending_coinbase_init_stack2,
693                            pending_coinbase_stack_state2,
694                            zkapp_command2,
695                        ) = v2;
696
697                        let (commitment2, full_commitment2) =
698                            mk_next_commitment(&zkapp_command2.account_updates);
699
700                        remaining_zkapp_command = rest;
701                        commitment = commitment2;
702                        full_commitment = full_commitment2;
703                        pending_coinbase_init_stack = (*pending_coinbase_init_stack1).clone();
704                        pending_coinbase_stack_state = StackState {
705                            target: pending_coinbase_stack_state2.target.clone(),
706                            ..(*pending_coinbase_stack_state1).clone()
707                        };
708
709                        (
710                            vec![zkapp_command1.clone(), zkapp_command2.clone()],
711                            commitment2,
712                            full_commitment2,
713                            pending_coinbase_init_stack.clone(),
714                            pending_coinbase_stack_state.clone(),
715                        )
716                    }
717                    _ => panic!("Not enough remaining zkapp_command"),
718                },
719            }
720        };
721
722        let hash_local_state = |local: &LocalStateEnv<SparseLedger>| {
723            let call_stack = local
724                .call_stack
725                .iter()
726                .map(|v| WithHash::of_data(v.clone(), |v| v.digest()))
727                .collect::<Vec<_>>();
728            let call_stack = accumulate_call_stack_hashes(|x| x.hash, &call_stack);
729
730            let LocalStateEnv {
731                stack_frame,
732                call_stack: _,
733                transaction_commitment,
734                full_transaction_commitment,
735                excess,
736                supply_increase,
737                ledger,
738                success,
739                account_update_index,
740                failure_status_tbl,
741                will_succeed,
742            } = local.clone();
743
744            LocalStateForWitness {
745                stack_frame,
746                call_stack: WithHash {
747                    data: call_stack,
748                    hash: Fp::zero(), // TODO
749                },
750                transaction_commitment: TransactionCommitment(transaction_commitment),
751                full_transaction_commitment: TransactionCommitment(full_transaction_commitment),
752                excess,
753                supply_increase,
754                ledger,
755                success,
756                account_update_index,
757                failure_status_tbl,
758                will_succeed,
759            }
760        };
761
762        let source_local = LocalStateForWitness {
763            transaction_commitment: current_commitment,
764            full_transaction_commitment: current_full_commitment,
765            ..hash_local_state(&source_local)
766        };
767
768        let target_local = LocalStateForWitness {
769            transaction_commitment: next_commitment,
770            full_transaction_commitment: next_full_commitment,
771            ..hash_local_state(&target_local)
772        };
773
774        let w = ZkappCommandSegmentWitness {
775            global_first_pass_ledger: source_global.first_pass_ledger.clone(),
776            global_second_pass_ledger: source_global.second_pass_ledger.clone(),
777            local_state_init: source_local.clone(),
778            start_zkapp_command,
779            state_body,
780            init_stack: pending_coinbase_init_stack,
781            block_global_slot: global_slot,
782        };
783
784        let fee_excess = {
785            let fee_excess = target_global
786                .fee_excess
787                .add(&source_global.fee_excess.negate())
788                .expect("unexpected fee excess");
789            FeeExcess {
790                fee_token_l: TokenId::default(),
791                fee_excess_l: fee_excess.to_fee(),
792                fee_token_r: TokenId::default(),
793                fee_excess_r: Signed::zero(),
794            }
795        };
796
797        let supply_increase = target_global
798            .supply_increase
799            .add(&source_global.supply_increase.negate())
800            .expect("unexpected supply increase");
801
802        let call_stack_hash = |s: &Vec<WithStackHash<WithHash<StackFrame>>>| {
803            s.first().map(|v| v.stack_hash).unwrap_or_else(Fp::zero)
804        };
805
806        let statement = {
807            let target_first_pass_ledger_root =
808                target_global.first_pass_ledger.clone().merkle_root();
809
810            let (source_local_ledger, target_local_ledger) = (
811                source_local.ledger.clone().merkle_root(),
812                target_local.ledger.clone().merkle_root(),
813            );
814
815            Statement::<SokDigest> {
816                source: Registers {
817                    first_pass_ledger: source_global.first_pass_ledger.clone().merkle_root(),
818                    second_pass_ledger: source_global.second_pass_ledger.clone().merkle_root(),
819                    pending_coinbase_stack: pending_coinbase_stack_state.source.clone(),
820                    local_state: {
821                        let LocalStateForWitness {
822                            stack_frame,
823                            call_stack,
824                            transaction_commitment,
825                            full_transaction_commitment,
826                            excess,
827                            supply_increase,
828                            ledger: _,
829                            success,
830                            account_update_index,
831                            failure_status_tbl,
832                            will_succeed,
833                        } = source_local;
834
835                        LocalState {
836                            stack_frame: stack_frame.digest(),
837                            call_stack: call_stack_hash(&call_stack),
838                            transaction_commitment: transaction_commitment.0,
839                            full_transaction_commitment: full_transaction_commitment.0,
840                            ledger: source_local_ledger,
841                            excess,
842                            supply_increase,
843                            success,
844                            account_update_index,
845                            failure_status_tbl,
846                            will_succeed,
847                        }
848                    },
849                },
850                target: Registers {
851                    first_pass_ledger: target_first_pass_ledger_root,
852                    second_pass_ledger: target_global.second_pass_ledger.clone().merkle_root(),
853                    pending_coinbase_stack: pending_coinbase_stack_state.target.clone(),
854                    local_state: {
855                        let LocalStateForWitness {
856                            stack_frame,
857                            call_stack,
858                            transaction_commitment,
859                            full_transaction_commitment,
860                            excess,
861                            supply_increase,
862                            ledger: _,
863                            success,
864                            account_update_index,
865                            failure_status_tbl,
866                            will_succeed,
867                        } = target_local;
868
869                        LocalState {
870                            stack_frame: stack_frame.digest(),
871                            call_stack: call_stack_hash(&call_stack),
872                            transaction_commitment: transaction_commitment.0,
873                            full_transaction_commitment: full_transaction_commitment.0,
874                            ledger: target_local_ledger,
875                            excess,
876                            supply_increase,
877                            success,
878                            account_update_index,
879                            failure_status_tbl,
880                            will_succeed,
881                        }
882                    },
883                },
884                connecting_ledger_left: connecting_ledger,
885                connecting_ledger_right: connecting_ledger,
886                supply_increase,
887                fee_excess,
888                sok_digest: SokDigest::default(),
889            }
890        };
891
892        witnesses.insert(0, (w, spec, statement));
893        witnesses
894    });
895
896    Ok(result)
897}
898
899#[derive(Clone, Debug)]
900pub enum IsStart {
901    Yes,
902    No,
903    ComputeInCircuit,
904}
905
906#[derive(Clone, Debug)]
907pub struct Spec {
908    pub auth_type: ControlTag,
909    pub is_start: IsStart,
910}
911
912fn basic_spec(s: &SegmentBasic) -> Box<[Spec]> {
913    let opt_signed = || Spec {
914        auth_type: ControlTag::Signature,
915        is_start: IsStart::ComputeInCircuit,
916    };
917
918    match s {
919        SegmentBasic::OptSignedOptSigned => Box::new([opt_signed(), opt_signed()]),
920        SegmentBasic::OptSigned => Box::new([opt_signed()]),
921        SegmentBasic::Proved => Box::new([Spec {
922            auth_type: ControlTag::Proof,
923            is_start: IsStart::No,
924        }]),
925    }
926}
927
928fn read_witnesses<F: FieldWitness>(path: &str) -> Vec<F> {
929    let f = std::fs::read_to_string(
930        std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
931            .join(devnet_circuit_directory())
932            .join("witnesses")
933            .join(path),
934        // .join("zkapp_fps.txt"),
935    )
936    .unwrap();
937
938    let fps = f
939        .lines()
940        .filter(|s| !s.is_empty())
941        .map(|s| F::from_str(s).unwrap_or_else(|_| panic!()))
942        .collect::<Vec<_>>();
943
944    fps
945}
946
947struct CheckProtocolStateParams<'a> {
948    pending_coinbase_stack_init: Stack,
949    pending_coinbase_stack_before: Stack,
950    pending_coinbase_stack_after: Stack,
951    block_global_slot: CheckedSlot<Fp>,
952    state_body: &'a ProtocolStateBody,
953}
954
955fn check_protocol_state(params: CheckProtocolStateParams, w: &mut Witness<Fp>) {
956    let CheckProtocolStateParams {
957        pending_coinbase_stack_init,
958        pending_coinbase_stack_before,
959        pending_coinbase_stack_after,
960        block_global_slot,
961        state_body,
962    } = params;
963
964    let state_body_hash = state_body.checked_hash_with_param(&MINA_PROTO_STATE_BODY, w);
965    let global_slot = block_global_slot;
966    let computed_pending_coinbase_stack_after =
967        pending_coinbase_stack_init.checked_push_state(state_body_hash, global_slot, w);
968
969    let _correct_coinbase_target_stack =
970        computed_pending_coinbase_stack_after.equal_var(&pending_coinbase_stack_after, w);
971
972    let _valid_init_state = {
973        let equal_source = pending_coinbase_stack_init.equal_var(&pending_coinbase_stack_before, w);
974
975        let equal_source_with_state =
976            computed_pending_coinbase_stack_after.equal_var(&pending_coinbase_stack_before, w);
977
978        equal_source.or(&equal_source_with_state, w)
979    };
980}
981
982/// With root hash
983#[derive(Clone)]
984pub struct LedgerWithHash {
985    pub ledger: SparseLedger,
986    pub hash: Fp,
987}
988
989impl ToFieldElements<Fp> for LedgerWithHash {
990    fn to_field_elements(&self, fields: &mut Vec<Fp>) {
991        let Self { ledger: _, hash } = self;
992        hash.to_field_elements(fields);
993    }
994}
995
996impl Check<Fp> for LedgerWithHash {
997    fn check(&self, _w: &mut Witness<Fp>) {
998        // Nothing
999    }
1000}
1001
1002pub struct ZkappSingleData {
1003    spec: Spec,
1004    zkapp_input: Rc<RefCell<Option<ZkappStatement>>>,
1005    must_verify: Rc<RefCell<Boolean>>,
1006}
1007
1008impl ZkappSingleData {
1009    pub fn spec(&self) -> &Spec {
1010        &self.spec
1011    }
1012    pub fn set_zkapp_input(&self, x: ZkappStatement) {
1013        let mut zkapp_input = self.zkapp_input.borrow_mut();
1014        zkapp_input.replace(x);
1015    }
1016    pub fn set_must_verify(&self, x: Boolean) {
1017        let mut must_verify = self.must_verify.borrow_mut();
1018        *must_verify = x;
1019    }
1020}
1021
1022pub type LocalStateForProof = LocalStateSkeleton<
1023    LedgerWithHash,                                     // ledger
1024    StackFrameChecked,                                  // stack_frame
1025    WithHash<Vec<WithStackHash<WithHash<StackFrame>>>>, // call_stack
1026    Fp,                                                 // commitments
1027    CheckedSigned<Fp, CheckedAmount<Fp>>,               // fee_excess & supply_increase
1028    (),                                                 // failure_status_tbl
1029    Boolean,                                            // success & will_succeed
1030    CheckedIndex<Fp>,                                   // account_update_index
1031>;
1032
1033pub type GlobalStateForProof = GlobalStateSkeleton<
1034    LedgerWithHash,                       // ledger
1035    CheckedSigned<Fp, CheckedAmount<Fp>>, // fee_excess & supply_increase
1036    CheckedSlot<Fp>,                      // block_global_slot
1037>;
1038
1039pub type StartDataForProof = StartDataSkeleton<
1040    WithHash<CallForest<AccountUpdate>>, // account_updates
1041    CircuitVar<Boolean>,                 // will_succeed
1042>;
1043
1044fn zkapp_main(
1045    statement: Statement<SokDigest>,
1046    witness: &ZkappCommandSegmentWitness,
1047    spec: &[Spec],
1048    w: &mut Witness<Fp>,
1049) -> Result<(Option<ZkappStatement>, Boolean), InvalidBigInt> {
1050    w.exists(&statement);
1051
1052    dummy_constraints(w);
1053    let state_body: &ProtocolStateBody = &w.exists(witness.state_body.try_into()?);
1054    let block_global_slot = w.exists(witness.block_global_slot).to_checked();
1055    let pending_coinbase_stack_init = w.exists(witness.init_stack.clone());
1056
1057    check_protocol_state(
1058        CheckProtocolStateParams {
1059            pending_coinbase_stack_init,
1060            pending_coinbase_stack_before: statement.source.pending_coinbase_stack.clone(),
1061            pending_coinbase_stack_after: statement.target.pending_coinbase_stack.clone(),
1062            block_global_slot,
1063            state_body,
1064        },
1065        w,
1066    );
1067
1068    let (mut global, mut local) = {
1069        let g = GlobalStateForProof {
1070            first_pass_ledger: LedgerWithHash {
1071                ledger: witness.global_first_pass_ledger.clone(),
1072                hash: statement.source.first_pass_ledger,
1073            },
1074            second_pass_ledger: LedgerWithHash {
1075                ledger: witness.global_second_pass_ledger.clone(),
1076                hash: statement.source.second_pass_ledger,
1077            },
1078            fee_excess: CheckedSigned::constant_zero(),
1079            supply_increase: CheckedSigned::constant_zero(),
1080            protocol_state: state_body.view(),
1081            block_global_slot,
1082        };
1083
1084        let l = zkapp_logic::LocalState::<ZkappSnark> {
1085            stack_frame: witness
1086                .local_state_init
1087                .stack_frame
1088                .unhash(statement.source.local_state.stack_frame, w),
1089            call_stack: WithHash {
1090                hash: statement.source.local_state.call_stack,
1091                data: witness.local_state_init.call_stack.data.clone(),
1092            },
1093            transaction_commitment: statement.source.local_state.transaction_commitment,
1094            full_transaction_commitment: statement.source.local_state.full_transaction_commitment,
1095            excess: statement.source.local_state.excess.to_checked(),
1096            supply_increase: statement.source.local_state.supply_increase.to_checked(),
1097            ledger: LedgerWithHash {
1098                ledger: witness.local_state_init.ledger.copy_content(),
1099                hash: statement.source.local_state.ledger,
1100            },
1101            success: statement.source.local_state.success.to_boolean().var(),
1102            account_update_index: statement
1103                .source
1104                .local_state
1105                .account_update_index
1106                .to_checked(),
1107            failure_status_tbl: (),
1108            will_succeed: statement.source.local_state.will_succeed.to_boolean().var(),
1109        };
1110
1111        (g, l)
1112    };
1113
1114    let init_fee_excess = global.fee_excess.clone();
1115
1116    let mut start_zkapp_command = witness.start_zkapp_command.as_slice();
1117    let zkapp_input = Rc::new(RefCell::new(None));
1118    let must_verify = Rc::new(RefCell::new(Boolean::True));
1119
1120    spec.iter().rev().for_each(|account_update_spec| {
1121        enum StartOrSkip<T> {
1122            Start(T),
1123            Skip,
1124        }
1125
1126        let mut finish = |v: StartOrSkip<&StartData>, (global_state, local_state)| {
1127            let ps = match v {
1128                StartOrSkip::Skip => CallForest::empty(),
1129                StartOrSkip::Start(p) => p.account_updates.all_account_updates(),
1130            };
1131
1132            let h = w.exists(ps.hash());
1133
1134            // We decompose this way because of OCaml evaluation order
1135            let will_succeed = w.exists(match v {
1136                StartOrSkip::Start(p) => p.will_succeed.to_boolean().constant(),
1137                StartOrSkip::Skip => Boolean::False.constant(),
1138            });
1139            let memo_hash = w.exists(match v {
1140                StartOrSkip::Skip => Fp::zero(),
1141                StartOrSkip::Start(p) => p.memo_hash,
1142            });
1143
1144            let start_data = StartDataForProof {
1145                account_updates: WithHash { data: ps, hash: h },
1146                memo_hash,
1147                will_succeed,
1148            };
1149
1150            {
1151                let is_start = match account_update_spec.is_start {
1152                    IsStart::Yes => zkapp_logic::IsStart::Yes(start_data),
1153                    IsStart::No => zkapp_logic::IsStart::No,
1154                    IsStart::ComputeInCircuit => zkapp_logic::IsStart::Compute(start_data),
1155                };
1156
1157                let single_data = ZkappSingleData {
1158                    spec: account_update_spec.clone(),
1159                    zkapp_input: Rc::clone(&zkapp_input),
1160                    must_verify: Rc::clone(&must_verify),
1161                };
1162
1163                zkapp_logic::apply::<ZkappSnark>(
1164                    ApplyZkappParams {
1165                        is_start,
1166                        global_state,
1167                        local_state,
1168                        single_data,
1169                    },
1170                    w,
1171                )
1172            }
1173        };
1174
1175        let new_acc = match account_update_spec.is_start {
1176            IsStart::No => {
1177                let is_start = zkapp_logic::IsStart::No;
1178
1179                let single_data = ZkappSingleData {
1180                    spec: account_update_spec.clone(),
1181                    zkapp_input: Rc::clone(&zkapp_input),
1182                    must_verify: Rc::clone(&must_verify),
1183                };
1184
1185                zkapp_logic::apply::<ZkappSnark>(
1186                    ApplyZkappParams {
1187                        is_start,
1188                        global_state: &mut global,
1189                        local_state: &mut local,
1190                        single_data,
1191                    },
1192                    w,
1193                )
1194            }
1195            IsStart::ComputeInCircuit => {
1196                let v = match start_zkapp_command {
1197                    [] => StartOrSkip::Skip,
1198                    [p, ps @ ..] => {
1199                        let should_pop = local.stack_frame.data.calls.data.is_empty();
1200
1201                        if should_pop {
1202                            start_zkapp_command = ps;
1203                            StartOrSkip::Start(p)
1204                        } else {
1205                            StartOrSkip::Skip
1206                        }
1207                    }
1208                };
1209                finish(v, (&mut global, &mut local))
1210            }
1211            IsStart::Yes => {
1212                assert!(local.stack_frame.data.calls.data.is_empty());
1213
1214                let v = match start_zkapp_command {
1215                    [] => unreachable!(),
1216                    [p, ps @ ..] => {
1217                        start_zkapp_command = ps;
1218                        StartOrSkip::Start(p)
1219                    }
1220                };
1221                finish(v, (&mut global, &mut local))
1222            }
1223        };
1224
1225        new_acc.unwrap() // TODO: Remove unwrap
1226    });
1227
1228    let on_true = local.stack_frame.hash(w);
1229    let _local_state_ledger = w.exists_no_check(match local.success.as_boolean() {
1230        Boolean::True => on_true,
1231        Boolean::False => statement.target.local_state.stack_frame,
1232    });
1233
1234    // Call to `Local_state.Checked.assert_equal` only pushes those
1235    // 2 values
1236    w.exists_no_check(local.excess.force_value());
1237    w.exists_no_check(local.supply_increase.force_value());
1238
1239    // Call to `Amount.Signed.Checked.assert_equal` only pushes this value
1240    w.exists_no_check(global.supply_increase.force_value());
1241
1242    // Call to `Fee_excess.assert_equal_checked` only pushes those 2 values
1243    w.exists_no_check(global.fee_excess.force_value());
1244    w.exists_no_check(init_fee_excess.force_value());
1245
1246    let zkapp_input = Rc::into_inner(zkapp_input).unwrap().into_inner();
1247    let must_verify = Rc::into_inner(must_verify).unwrap().into_inner();
1248
1249    Ok((zkapp_input, must_verify))
1250}
1251
1252pub struct LedgerProof {
1253    pub statement: Statement<SokDigest>,
1254    pub proof: WrapProof,
1255}
1256
1257impl From<&LedgerProof> for v2::LedgerProofProdStableV2 {
1258    fn from(value: &LedgerProof) -> Self {
1259        let LedgerProof { statement, proof } = value;
1260        Self(v2::TransactionSnarkStableV2 {
1261            statement: statement.into(),
1262            proof: v2::TransactionSnarkProofStableV2(proof.into()),
1263        })
1264    }
1265}
1266
1267fn first_account_update<'a>(witness: &'a ZkappCommandSegmentWitness) -> Option<&'a AccountUpdate> {
1268    match witness.local_state_init.stack_frame.calls.0.as_slice() {
1269        [] => witness
1270            .start_zkapp_command
1271            .iter()
1272            .find_map(|s| s.account_updates.account_updates.first())
1273            .map(|v| &v.elt.account_update),
1274        [first, ..] => Some(&first.elt.account_update),
1275    }
1276}
1277
1278fn account_update_proof(p: &AccountUpdate) -> Option<&v2::PicklesProofProofsVerifiedMaxStableV2> {
1279    match &p.authorization {
1280        Control::Proof(proof) => Some(proof),
1281        Control::Signature(_) | Control::NoneGiven => None,
1282    }
1283}
1284
1285fn snapp_proof_data<'a>(
1286    witness: &'a ZkappCommandSegmentWitness,
1287) -> Option<(
1288    &'a v2::PicklesProofProofsVerifiedMaxStableV2,
1289    crate::VerificationKey,
1290)> {
1291    let p = first_account_update(witness)?;
1292    let pi = account_update_proof(p)?;
1293    let vk = {
1294        let account_id = AccountId::create(p.body.public_key.clone(), p.body.token_id.clone());
1295        let addr = witness.local_state_init.ledger.find_index_exn(account_id);
1296        let account = witness.local_state_init.ledger.get_exn(&addr);
1297        account
1298            .zkapp
1299            .and_then(|z| z.verification_key)
1300            .expect("No verification key found in the account")
1301    };
1302    Some((pi, vk.vk().clone()))
1303}
1304
1305fn of_zkapp_command_segment_exn<StepConstants, WrapConstants>(
1306    statement: Statement<SokDigest>,
1307    zkapp_witness: &ZkappCommandSegmentWitness,
1308    spec: &SegmentBasic,
1309    step_prover: &Prover<Fp>,
1310    tx_wrap_prover: &Prover<Fq>,
1311    fps_path: Option<String>,
1312    fqs_path: Option<String>,
1313) -> anyhow::Result<LedgerProof>
1314where
1315    StepConstants: ProofConstants,
1316    WrapConstants: ProofConstants + ForWrapData,
1317{
1318    use SegmentBasic::*;
1319
1320    let s = basic_spec(spec);
1321    let mut w = Witness::new::<StepConstants>();
1322
1323    if let Some(path) = fps_path.as_ref() {
1324        w.ocaml_aux = read_witnesses(path);
1325    };
1326
1327    let (zkapp_input, must_verify) = zkapp_main(statement.clone(), zkapp_witness, &s, &mut w)?;
1328
1329    let StepProof {
1330        statement: step_statement,
1331        prev_evals,
1332        proof_with_public: proof,
1333    } = match spec {
1334        OptSignedOptSigned | OptSigned => step::<StepConstants, 0>(
1335            StepParams {
1336                app_state: Rc::new(statement.clone()),
1337                rule: InductiveRule::empty(),
1338                for_step_datas: [],
1339                indexes: [],
1340                prev_challenge_polynomial_commitments: vec![],
1341                hack_feature_flags: OptFlag::No,
1342                step_prover,
1343                wrap_prover: tx_wrap_prover,
1344                only_verify_constraints: false,
1345            },
1346            &mut w,
1347        )?,
1348        Proved => {
1349            let (proof, vk) = snapp_proof_data(zkapp_witness).unwrap();
1350            let proof = proof.into();
1351
1352            let dlog_plonk_index_cvar = vk.wrap_index.to_cvar(CircuitVar::Var);
1353            let verifier_index = make_zkapp_verifier_index(&vk);
1354
1355            let zkapp_data = make_step_zkapp_data(&dlog_plonk_index_cvar);
1356            let for_step_datas = [&zkapp_data];
1357
1358            let indexes = [(&verifier_index, &dlog_plonk_index_cvar)];
1359            let prev_challenge_polynomial_commitments = extract_recursion_challenges(&[&proof])?;
1360
1361            step::<StepConstants, 1>(
1362                StepParams {
1363                    app_state: Rc::new(statement.clone()),
1364                    rule: InductiveRule {
1365                        previous_proof_statements: [PreviousProofStatement {
1366                            public_input: Rc::new(zkapp_input.unwrap()),
1367                            proof: &proof,
1368                            proof_must_verify: must_verify.var(),
1369                        }],
1370                        public_output: (),
1371                        auxiliary_output: (),
1372                    },
1373                    for_step_datas,
1374                    indexes,
1375                    prev_challenge_polynomial_commitments,
1376                    hack_feature_flags: OptFlag::Maybe,
1377                    step_prover,
1378                    wrap_prover: tx_wrap_prover,
1379                    only_verify_constraints: false,
1380                },
1381                &mut w,
1382            )?
1383        }
1384    };
1385
1386    let dlog_plonk_index = super::merge::dlog_plonk_index(tx_wrap_prover);
1387
1388    let mut w: Witness<Fq> = Witness::new::<WrapConstants>();
1389
1390    if let Some(path) = fqs_path.as_ref() {
1391        w.ocaml_aux = read_witnesses(path);
1392    };
1393
1394    let proof = wrap::wrap::<WrapConstants>(
1395        WrapParams {
1396            app_state: Rc::new(statement.clone()),
1397            proof_with_public: &proof,
1398            step_statement,
1399            prev_evals: &prev_evals,
1400            dlog_plonk_index: &dlog_plonk_index,
1401            step_prover_index: &step_prover.index,
1402            wrap_prover: tx_wrap_prover,
1403        },
1404        &mut w,
1405    )?;
1406
1407    Ok(LedgerProof { statement, proof })
1408}
1409
1410impl From<&WrapProof> for v2::PicklesProofProofsVerified2ReprStableV2 {
1411    fn from(value: &WrapProof) -> Self {
1412        let WrapProof {
1413            proof:
1414                ProofWithPublic {
1415                    proof:
1416                        kimchi::proof::ProverProof {
1417                            commitments:
1418                                kimchi::proof::ProverCommitments {
1419                                    w_comm,
1420                                    z_comm,
1421                                    t_comm,
1422                                    lookup,
1423                                },
1424                            proof:
1425                                OpeningProof {
1426                                    lr,
1427                                    delta,
1428                                    z1,
1429                                    z2,
1430                                    sg,
1431                                },
1432                            evals,
1433                            ft_eval1,
1434                            prev_challenges: _,
1435                        },
1436                    public_input: _,
1437                },
1438            statement:
1439                WrapStatement {
1440                    proof_state,
1441                    messages_for_next_step_proof,
1442                },
1443            prev_evals,
1444        } = value;
1445
1446        assert!(lookup.is_none());
1447
1448        use mina_p2p_messages::{bigint::BigInt, pseq::PaddedSeq};
1449        use std::array;
1450
1451        let to_tuple = |g: &GroupAffine<Fp>| -> (BigInt, BigInt) { (g.x.into(), g.y.into()) };
1452
1453        v2::PicklesProofProofsVerified2ReprStableV2 {
1454            statement: v2::PicklesProofProofsVerified2ReprStableV2Statement {
1455                proof_state: {
1456                    let WrapProofState {
1457                        deferred_values:
1458                            DeferredValues {
1459                                plonk,
1460                                combined_inner_product: _,
1461                                b: _,
1462                                xi: _,
1463                                bulletproof_challenges,
1464                                branch_data,
1465                            },
1466                        sponge_digest_before_evaluations,
1467                        messages_for_next_wrap_proof:
1468                            MessagesForNextWrapProof {
1469                                challenge_polynomial_commitment,
1470                                old_bulletproof_challenges,
1471                            },
1472                    } = proof_state;
1473
1474                    v2::PicklesProofProofsVerified2ReprStableV2StatementProofState {
1475                        deferred_values: {
1476                            let to_padded = |v: [u64; 2]| -> PaddedSeq<
1477                                v2::LimbVectorConstantHex64StableV1,
1478                                2,
1479                            > {
1480                                use v2::LimbVectorConstantHex64StableV1 as V;
1481                                PaddedSeq([V(v[0].into()), V(v[1].into())])
1482                            };
1483
1484                            v2::PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValues {
1485                                plonk: v2::PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonk {
1486                                    alpha: v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge {
1487                                        inner: to_padded(plonk.alpha),
1488                                    },
1489                                    beta: to_padded(plonk.beta),
1490                                    gamma: to_padded(plonk.gamma),
1491                                    zeta: v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge {
1492                                        inner: to_padded(plonk.zeta),
1493                                    },
1494                                    // <https://github.com/MinaProtocol/mina/blob/dc6bf78b8ddbbca3a1a248971b76af1514bf05aa/src/lib/pickles/composition_types/composition_types.ml#L200-L202>
1495                                    joint_combiner: plonk.lookup.map(|joint_combiner| {
1496                                        v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge {
1497                                            inner: to_padded(joint_combiner),
1498                                        }
1499                                    }),
1500                                    feature_flags: (&plonk.feature_flags).into(),
1501                                },
1502                                bulletproof_challenges: PaddedSeq(array::from_fn(|i| {
1503                                    v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A {
1504                                        prechallenge: v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge {
1505                                            inner: {
1506                                                let bigint: BigInteger256 = bulletproof_challenges[i].into();
1507                                                let bigint = bigint.0;
1508                                                PaddedSeq([v2::LimbVectorConstantHex64StableV1(bigint[0].into()), v2::LimbVectorConstantHex64StableV1(bigint[1].into())])
1509                                            },
1510                                        },
1511                                    }
1512                                })),
1513                                branch_data: branch_data.clone(),
1514                            }
1515                        },
1516                        sponge_digest_before_evaluations:
1517                            v2::CompositionTypesDigestConstantStableV1({
1518                                let bigint: BigInteger256 =
1519                                    (*sponge_digest_before_evaluations).into();
1520                                let bigint = bigint.0;
1521                                PaddedSeq(
1522                                    bigint
1523                                        .each_ref()
1524                                        .map(|v| v2::LimbVectorConstantHex64StableV1(v.into())),
1525                                )
1526                            }),
1527                        messages_for_next_wrap_proof:
1528                            v2::PicklesProofProofsVerified2ReprStableV2MessagesForNextWrapProof {
1529                                challenge_polynomial_commitment: {
1530                                    let GroupAffine::<Fq> { x, y, .. } =
1531                                        challenge_polynomial_commitment.to_affine();
1532                                    (x.into(), y.into())
1533                                },
1534                                old_bulletproof_challenges: PaddedSeq(array::from_fn(|i| {
1535                                    v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2(
1536                                    PaddedSeq(array::from_fn(|j| v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A {
1537                                        prechallenge: v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge {
1538                                            inner: {
1539                                                let bigint: BigInteger256 = old_bulletproof_challenges[i][j].into();
1540                                                let bigint = bigint.0;
1541                                                PaddedSeq([v2::LimbVectorConstantHex64StableV1(bigint[0].into()), v2::LimbVectorConstantHex64StableV1(bigint[1].into())])
1542                                            },
1543                                        },
1544                                    }))
1545                                )
1546                                })),
1547                            },
1548                    }
1549                },
1550                messages_for_next_step_proof: {
1551                    let ReducedMessagesForNextStepProof {
1552                        app_state: _,
1553                        challenge_polynomial_commitments,
1554                        old_bulletproof_challenges,
1555                    } = messages_for_next_step_proof;
1556
1557                    v2::PicklesProofProofsVerified2ReprStableV2MessagesForNextStepProof {
1558                        app_state: (),
1559                        challenge_polynomial_commitments: challenge_polynomial_commitments.iter().map(|curve| {
1560                            let GroupAffine::<Fp> { x, y, .. } = curve.to_affine();
1561                            (x.into(), y.into())
1562                        }).collect(),
1563                        old_bulletproof_challenges: old_bulletproof_challenges.iter().map(|v| {
1564                            PaddedSeq(array::from_fn(|i| v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A {
1565                                prechallenge: v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge {
1566                                    inner: {
1567                                        let bigint: BigInteger256 = v[i].into();
1568                                        let bigint = bigint.0;
1569                                        PaddedSeq([v2::LimbVectorConstantHex64StableV1(bigint[0].into()), v2::LimbVectorConstantHex64StableV1(bigint[1].into())])
1570                                    },
1571                                },
1572                            }))
1573                        }).collect(),
1574                    }
1575                },
1576            },
1577            prev_evals: {
1578                let AllEvals {
1579                    ft_eval1,
1580                    evals:
1581                        EvalsWithPublicInput {
1582                            evals,
1583                            public_input,
1584                        },
1585                } = prev_evals;
1586
1587                v2::PicklesProofProofsVerified2ReprStableV2PrevEvals {
1588                    evals: v2::PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals {
1589                        public_input: {
1590                            let (a, b) = public_input;
1591                            assert_eq!(a.len(), 1);
1592                            assert_eq!(b.len(), 1);
1593                            (a[0].into(), b[0].into())
1594                        },
1595                        evals: prev_evals_to_p2p(evals),
1596                    },
1597                    ft_eval1: ft_eval1.into(),
1598                }
1599            },
1600            proof: v2::PicklesWrapWireProofStableV1 {
1601                commitments: v2::PicklesWrapWireProofCommitmentsStableV1 {
1602                    w_comm: PaddedSeq(w_comm.each_ref().map(|w| to_tuple(&w.chunks[0]))),
1603                    z_comm: to_tuple(&z_comm.chunks[0]),
1604                    t_comm: PaddedSeq(array::from_fn(|i| to_tuple(&t_comm.chunks[i]))),
1605                },
1606                evaluations: {
1607                    let kimchi::proof::ProofEvaluations {
1608                        w,
1609                        z,
1610                        s,
1611                        coefficients,
1612                        generic_selector,
1613                        poseidon_selector,
1614                        complete_add_selector,
1615                        mul_selector,
1616                        emul_selector,
1617                        endomul_scalar_selector,
1618                        ..
1619                    } = evals;
1620
1621                    let to_tuple = |point: &PointEvaluations<Vec<Fq>>| -> (BigInt, BigInt) {
1622                        (point.zeta[0].into(), point.zeta_omega[0].into())
1623                    };
1624
1625                    v2::PicklesWrapWireProofEvaluationsStableV1 {
1626                        w: PaddedSeq(w.each_ref().map(to_tuple)),
1627                        coefficients: PaddedSeq(coefficients.each_ref().map(to_tuple)),
1628                        z: to_tuple(z),
1629                        s: PaddedSeq(s.each_ref().map(to_tuple)),
1630                        generic_selector: to_tuple(generic_selector),
1631                        poseidon_selector: to_tuple(poseidon_selector),
1632                        complete_add_selector: to_tuple(complete_add_selector),
1633                        mul_selector: to_tuple(mul_selector),
1634                        emul_selector: to_tuple(emul_selector),
1635                        endomul_scalar_selector: to_tuple(endomul_scalar_selector),
1636                    }
1637                },
1638                ft_eval1: ft_eval1.into(),
1639                bulletproof: v2::PicklesWrapWireProofStableV1Bulletproof {
1640                    lr: lr.iter().map(|(a, b)| (to_tuple(a), to_tuple(b))).collect(),
1641                    z_1: z1.into(),
1642                    z_2: z2.into(),
1643                    delta: to_tuple(delta),
1644                    challenge_polynomial_commitment: to_tuple(sg),
1645                },
1646            },
1647        }
1648    }
1649}
1650
1651fn of_zkapp_command_segment(
1652    statement: Statement<SokDigest>,
1653    zkapp_witness: &ZkappCommandSegmentWitness,
1654    spec: &SegmentBasic,
1655    step_opt_signed_opt_signed_prover: &Prover<Fp>,
1656    step_opt_signed_prover: &Prover<Fp>,
1657    step_proof_prover: &Prover<Fp>,
1658    tx_wrap_prover: &Prover<Fq>,
1659    opt_signed_path: Option<&str>,
1660    proved_path: Option<&str>,
1661) -> anyhow::Result<LedgerProof> {
1662    let (step_prover, step_path, wrap_path) = match spec {
1663        SegmentBasic::OptSignedOptSigned => (step_opt_signed_opt_signed_prover, None, None),
1664        SegmentBasic::OptSigned => {
1665            let fps_path = opt_signed_path.map(|p| format!("{}_fps.txt", p));
1666            let fqs_path = opt_signed_path.map(|p| format!("{}_fqs.txt", p));
1667            (step_opt_signed_prover, fps_path, fqs_path)
1668        }
1669        SegmentBasic::Proved => {
1670            let fps_path = proved_path.map(|p| format!("{}_fps.txt", p));
1671            let fqs_path = proved_path.map(|p| format!("{}_fqs.txt", p));
1672            (step_proof_prover, fps_path, fqs_path)
1673        }
1674    };
1675
1676    let of_zkapp_command_segment_exn = match spec {
1677        SegmentBasic::OptSignedOptSigned => {
1678            of_zkapp_command_segment_exn::<StepZkappOptSignedOptSignedProof, WrapZkappProof>
1679        }
1680        SegmentBasic::OptSigned => {
1681            of_zkapp_command_segment_exn::<StepZkappOptSignedProof, WrapZkappOptSignedProof>
1682        }
1683        SegmentBasic::Proved => {
1684            of_zkapp_command_segment_exn::<StepZkappProvedProof, WrapZkappProvedProof>
1685        }
1686    };
1687
1688    of_zkapp_command_segment_exn(
1689        statement,
1690        zkapp_witness,
1691        spec,
1692        step_prover,
1693        tx_wrap_prover,
1694        step_path,
1695        wrap_path,
1696    )
1697}
1698
1699pub fn generate_zkapp_proof(params: ZkappParams) -> anyhow::Result<LedgerProof> {
1700    let ZkappParams {
1701        statement,
1702        tx_witness,
1703        message,
1704        step_opt_signed_opt_signed_prover,
1705        step_opt_signed_prover,
1706        step_proof_prover,
1707        tx_wrap_prover,
1708        merge_step_prover,
1709        opt_signed_path,
1710        proved_path,
1711    } = params;
1712
1713    let zkapp = match &tx_witness.transaction {
1714        v2::MinaTransactionTransactionStableV2::Command(cmd) => {
1715            let v2::MinaBaseUserCommandStableV2::ZkappCommand(zkapp_command) = &**cmd else {
1716                unreachable!();
1717            };
1718            zkapp_command
1719        }
1720        _ => unreachable!(),
1721    };
1722    let zkapp_command: ZkAppCommand = zkapp.try_into()?;
1723
1724    let witnesses_specs_stmts = zkapp_command_witnesses_exn(ZkappCommandWitnessesParams {
1725        global_slot: Slot::from_u32(tx_witness.block_global_slot.as_u32()),
1726        state_body: &tx_witness.protocol_state_body,
1727        fee_excess: Signed::zero(),
1728        zkapp_commands_with_context: vec![ZkappCommandsWithContext {
1729            pending_coinbase_init_stack: (&tx_witness.init_stack).try_into()?,
1730            pending_coinbase_of_statement: pending_coinbase::StackState {
1731                source: (&statement.source.pending_coinbase_stack).try_into()?,
1732                target: (&statement.target.pending_coinbase_stack).try_into()?,
1733            },
1734            first_pass_ledger: (&tx_witness.first_pass_ledger).try_into()?,
1735            second_pass_ledger: (&tx_witness.second_pass_ledger).try_into()?,
1736            connecting_ledger_hash: statement.connecting_ledger_left.to_field()?,
1737            zkapp_command: &zkapp_command,
1738        }],
1739    })?;
1740
1741    let of_zkapp_command_segment = |statement: Statement<SokDigest>,
1742                                    zkapp_witness: &ZkappCommandSegmentWitness<'_>,
1743                                    spec: &SegmentBasic| {
1744        of_zkapp_command_segment(
1745            statement,
1746            zkapp_witness,
1747            spec,
1748            step_opt_signed_opt_signed_prover,
1749            step_opt_signed_prover,
1750            step_proof_prover,
1751            tx_wrap_prover,
1752            opt_signed_path,
1753            proved_path,
1754        )
1755    };
1756
1757    let sok_digest = message.digest();
1758    let mut witnesses_specs_stmts = witnesses_specs_stmts.into_iter().rev();
1759    let (zkapp_witness, spec, statement) = witnesses_specs_stmts.next().unwrap(); // last one
1760
1761    let first_proof = of_zkapp_command_segment(
1762        statement.with_digest(sok_digest.clone()),
1763        &zkapp_witness,
1764        &spec,
1765    );
1766
1767    witnesses_specs_stmts.fold(
1768        first_proof,
1769        |prev_proof, (zkapp_witness, spec, statement)| {
1770            let prev_proof = prev_proof?;
1771            let curr_proof = of_zkapp_command_segment(
1772                statement.with_digest(sok_digest.clone()),
1773                &zkapp_witness,
1774                &spec,
1775            )?;
1776
1777            merge_zkapp_proofs(
1778                prev_proof,
1779                curr_proof,
1780                message,
1781                merge_step_prover,
1782                tx_wrap_prover,
1783            )
1784        },
1785    )
1786}
1787
1788fn merge_zkapp_proofs(
1789    prev: LedgerProof,
1790    curr: LedgerProof,
1791    message: &SokMessage,
1792    merge_step_prover: &Prover<Fp>,
1793    tx_wrap_prover: &Prover<Fq>,
1794) -> anyhow::Result<LedgerProof> {
1795    let merged_statement = prev
1796        .statement
1797        .clone()
1798        .without_digest()
1799        .merge(&curr.statement.clone().without_digest())
1800        .unwrap();
1801
1802    let prev: v2::LedgerProofProdStableV2 = (&prev).into();
1803    let curr: v2::LedgerProofProdStableV2 = (&curr).into();
1804
1805    let mut w: Witness<Fp> = Witness::new::<StepMergeProof>();
1806
1807    let sok_digest = message.digest();
1808    let statement_with_sok = merged_statement.clone().with_digest(sok_digest);
1809
1810    let wrap_proof = generate_merge_proof(
1811        MergeParams {
1812            statement: merged_statement,
1813            proofs: &[prev, curr],
1814            message,
1815            step_prover: merge_step_prover,
1816            wrap_prover: tx_wrap_prover,
1817            only_verify_constraints: false,
1818            expected_step_proof: None,
1819            ocaml_wrap_witness: None,
1820        },
1821        &mut w,
1822    )?;
1823
1824    Ok(LedgerProof {
1825        statement: statement_with_sok,
1826        proof: wrap_proof,
1827    })
1828}