mina_tree/scan_state/transaction_logic/zkapp_command/
verifiable.rs

1use mina_curves::pasta::Fp;
2use mina_p2p_messages::v2::MinaBaseZkappCommandVerifiableStableV1;
3use std::collections::HashMap;
4
5use super::{
6    AccountId, AccountUpdate, AuthorizationKind, CallForest, Control, FeePayer, Memo, SetOrKeep,
7    VerificationKeyWire,
8};
9use crate::sparse_ledger::LedgerIntf;
10
11#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
12#[serde(try_from = "MinaBaseZkappCommandVerifiableStableV1")]
13#[serde(into = "MinaBaseZkappCommandVerifiableStableV1")]
14pub struct ZkAppCommand {
15    pub fee_payer: FeePayer,
16    pub account_updates: CallForest<(AccountUpdate, Option<VerificationKeyWire>)>,
17    pub memo: Memo,
18}
19
20fn ok_if_vk_hash_expected(
21    got: VerificationKeyWire,
22    expected: Fp,
23) -> Result<VerificationKeyWire, String> {
24    if got.hash() == expected {
25        return Ok(got.clone());
26    }
27    Err(format!(
28        "Expected vk hash doesn't match hash in vk we received\
29                 expected: {:?}\
30                 got: {:?}",
31        expected, got
32    ))
33}
34
35pub fn find_vk_via_ledger<L>(
36    ledger: L,
37    expected_vk_hash: Fp,
38    account_id: &AccountId,
39) -> Result<VerificationKeyWire, String>
40where
41    L: LedgerIntf + Clone,
42{
43    let vk = ledger
44        .location_of_account(account_id)
45        .and_then(|location| ledger.get(&location))
46        .and_then(|account| {
47            account
48                .zkapp
49                .as_ref()
50                .and_then(|zkapp| zkapp.verification_key.clone())
51        });
52
53    match vk {
54        Some(vk) => ok_if_vk_hash_expected(vk, expected_vk_hash),
55        None => Err(format!(
56            "No verification key found for proved account update\
57                             account_id: {:?}",
58            account_id
59        )),
60    }
61}
62
63fn check_authorization(p: &AccountUpdate) -> Result<(), String> {
64    use AuthorizationKind as AK;
65    use Control as C;
66
67    match (&p.authorization, &p.body.authorization_kind) {
68        (C::NoneGiven, AK::NoneGiven)
69        | (C::Proof(_), AK::Proof(_))
70        | (C::Signature(_), AK::Signature) => Ok(()),
71        _ => Err(format!(
72            "Authorization kind does not match the authorization\
73                         expected={:#?}\
74                         got={:#?}",
75            p.body.authorization_kind, p.authorization
76        )),
77    }
78}
79
80/// Ensures that there's a verification_key available for all account_updates
81/// and creates a valid command associating the correct keys with each
82/// account_id.
83///
84/// If an account_update replaces the verification_key (or deletes it),
85/// subsequent account_updates use the replaced key instead of looking in the
86/// ledger for the key (ie set by a previous transaction).
87pub fn create(
88    zkapp: &super::ZkAppCommand,
89    is_failed: bool,
90    find_vk: impl Fn(Fp, &AccountId) -> Result<VerificationKeyWire, String>,
91) -> Result<ZkAppCommand, String> {
92    let super::ZkAppCommand {
93        fee_payer,
94        account_updates,
95        memo,
96    } = zkapp;
97
98    let mut tbl = HashMap::with_capacity(128);
99    // Keep track of the verification keys that have been set so far
100    // during this transaction.
101    let mut vks_overridden: HashMap<AccountId, Option<VerificationKeyWire>> =
102        HashMap::with_capacity(128);
103
104    let account_updates = account_updates.try_map_to(|p| {
105        let account_id = p.account_id();
106
107        check_authorization(p)?;
108
109        let result = match (&p.body.authorization_kind, is_failed) {
110            (AuthorizationKind::Proof(vk_hash), false) => {
111                let prioritized_vk = {
112                    // only lookup _past_ vk setting, ie exclude the new one we
113                    // potentially set in this account_update (use the non-'
114                    // vks_overrided) .
115
116                    match vks_overridden.get(&account_id) {
117                        Some(Some(vk)) => ok_if_vk_hash_expected(vk.clone(), *vk_hash)?,
118                        Some(None) => {
119                            // we explicitly have erased the key
120                            return Err(format!(
121                                "No verification key found for proved account \
122                                                update: the verification key was removed by a \
123                                                previous account update\
124                                                account_id={:?}",
125                                account_id
126                            ));
127                        }
128                        None => {
129                            // we haven't set anything; lookup the vk in the fallback
130                            find_vk(*vk_hash, &account_id)?
131                        }
132                    }
133                };
134
135                tbl.insert(account_id, prioritized_vk.hash());
136
137                Ok((p.clone(), Some(prioritized_vk)))
138            }
139
140            _ => Ok((p.clone(), None)),
141        };
142
143        // NOTE: we only update the overriden map AFTER verifying the update to make sure
144        // that the verification for the VK update itself is done against the previous VK.
145        if let SetOrKeep::Set(vk_next) = &p.body.update.verification_key {
146            vks_overridden.insert(p.account_id().clone(), Some(vk_next.clone()));
147        }
148
149        result
150    })?;
151
152    Ok(ZkAppCommand {
153        fee_payer: fee_payer.clone(),
154        account_updates,
155        memo: memo.clone(),
156    })
157}