mina_tree/proofs/
zkapp.rs

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