Skip to main content

mina_tree/verifier/
common.rs

1use crate::{
2    decompress_pk,
3    scan_state::transaction_logic::{
4        transaction_union_payload::TransactionUnionPayload,
5        valid, verifiable,
6        zkapp_command::{self, valid::of_verifiable, AccountUpdate},
7        zkapp_statement::{TransactionCommitment, ZkappStatement},
8        TransactionStatus, WithStatus,
9    },
10    VerificationKey,
11};
12use ark_ec::{AffineRepr, CurveGroup};
13use ark_ff::PrimeField;
14use mina_p2p_messages::v2::PicklesProofProofsVerifiedMaxStableV2;
15use mina_signer::{CompressedPubKey, PubKey, Signature};
16use poseidon::hash::hash_with_kimchi;
17use std::sync::Arc;
18
19#[derive(Debug)]
20pub enum CheckResult {
21    Valid(valid::UserCommand),
22    ValidAssuming(
23        (
24            valid::UserCommand,
25            Vec<(
26                VerificationKey,
27                ZkappStatement,
28                Arc<PicklesProofProofsVerifiedMaxStableV2>,
29            )>,
30        ),
31    ),
32    InvalidKeys(Vec<CompressedPubKey>),
33    InvalidSignature(Vec<CompressedPubKey>),
34    InvalidProof(String),
35    MissingVerificationKey(Vec<CompressedPubKey>),
36    UnexpectedVerificationKey(Vec<CompressedPubKey>),
37    MismatchedAuthorizationKind(Vec<CompressedPubKey>),
38}
39
40/// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/verifier/common.ml#L29>
41pub fn check(cmd: WithStatus<verifiable::UserCommand>) -> CheckResult {
42    use verifiable::UserCommand::{SignedCommand, ZkAppCommand};
43
44    match cmd.data {
45        SignedCommand(cmd) => {
46            if !cmd.check_valid_keys() {
47                let public_keys = cmd.public_keys().into_iter().cloned().collect();
48                return CheckResult::InvalidKeys(public_keys);
49            }
50            match verifiable::check_only_for_signature(cmd) {
51                Ok(cmd) => CheckResult::Valid(cmd),
52                Err(cmd) => {
53                    CheckResult::InvalidSignature(cmd.public_keys().into_iter().cloned().collect())
54                }
55            }
56        }
57        ZkAppCommand(zkapp_command_with_vk) => {
58            let zkapp_command::verifiable::ZkAppCommand {
59                fee_payer,
60                account_updates,
61                memo,
62            } = &*zkapp_command_with_vk;
63
64            let account_updates_hash = account_updates.hash();
65            let tx_commitment = TransactionCommitment::create(account_updates_hash);
66
67            let memo_hash = memo.hash();
68            let fee_payer_hash = AccountUpdate::of_fee_payer(fee_payer.clone()).digest();
69            let full_tx_commitment = tx_commitment.create_complete(memo_hash, fee_payer_hash);
70
71            let Some(pk) = decompress_pk(&fee_payer.body.public_key) else {
72                return CheckResult::InvalidKeys(vec![fee_payer.body.public_key.clone()]);
73            };
74
75            if !verify_signature(&fee_payer.authorization, &pk, &full_tx_commitment) {
76                return CheckResult::InvalidSignature(vec![pk.into_compressed()]);
77            }
78
79            let zkapp_command_with_hashes_list =
80                ZkappStatement::zkapp_statements_of_forest_prime(account_updates.clone())
81                    .to_zkapp_command_with_hashes_list();
82
83            let mut valid_assuming = Vec::with_capacity(16);
84            for ((p, (vk_opt, stmt)), _at_account_update) in zkapp_command_with_hashes_list {
85                let commitment = if p.body.use_full_commitment {
86                    full_tx_commitment
87                } else {
88                    tx_commitment
89                };
90
91                use zkapp_command::{AuthorizationKind as AK, Control as C};
92                match (&p.authorization, &p.body.authorization_kind) {
93                    (C::Signature(s), AK::Signature) => {
94                        let pk = decompress_pk(&p.body.public_key).unwrap();
95                        if !verify_signature(s, &pk, &commitment) {
96                            return CheckResult::InvalidSignature(vec![pk.into_compressed()]);
97                        }
98                        continue;
99                    }
100                    (C::NoneGiven, AK::NoneGiven) => {
101                        continue;
102                    }
103                    (C::Proof(pi), AK::Proof(vk_hash)) => {
104                        if let TransactionStatus::Failed(_) = cmd.status {
105                            // Don't verify the proof if it has failed.
106                            continue;
107                        }
108                        let Some(vk) = vk_opt else {
109                            return CheckResult::MissingVerificationKey(vec![
110                                p.account_id().public_key,
111                            ]);
112                        };
113                        // check that vk expected for proof is the one being used
114                        if vk_hash != &vk.hash() {
115                            return CheckResult::UnexpectedVerificationKey(vec![
116                                p.account_id().public_key,
117                            ]);
118                        }
119                        valid_assuming.push((vk.vk().clone(), stmt, pi.clone()));
120                    }
121                    _ => {
122                        return CheckResult::MismatchedAuthorizationKind(vec![
123                            p.account_id().public_key,
124                        ]);
125                    }
126                }
127            }
128
129            let v: valid::UserCommand = {
130                // Verification keys should be present if it reaches here
131                let zkapp = of_verifiable(*zkapp_command_with_vk);
132                valid::UserCommand::ZkAppCommand(Box::new(zkapp))
133            };
134
135            if valid_assuming.is_empty() {
136                CheckResult::Valid(v)
137            } else {
138                CheckResult::ValidAssuming((v, valid_assuming))
139            }
140        }
141    }
142}
143
144/// Verify zkapp signature/statement with new style (chunked inputs)
145fn verify_signature(signature: &Signature, pubkey: &PubKey, msg: &TransactionCommitment) -> bool {
146    use ark_ff::{BigInteger, Zero};
147    use core::ops::{Mul, Neg};
148    use mina_curves::pasta::{Fq, Pallas};
149    use mina_signer::CurvePoint;
150
151    let Pallas { x, y, .. } = pubkey.point();
152    let Signature { rx, s } = signature;
153
154    let signature_prefix = mina_core::NetworkConfig::global().signature_prefix;
155    let hash = hash_with_kimchi(signature_prefix, &[**msg, *x, *y, *rx]);
156    let hash: Fq = Fq::from(hash.into_bigint()); // Never fail, `Fq` is larger than `Fp`
157
158    let sv: CurvePoint = CurvePoint::generator().mul(*s).into_affine();
159    // Perform addition and infinity check in projective coordinates for performance
160    let rv = pubkey.point().mul(hash).neg() + sv;
161    if rv.is_zero() {
162        return false;
163    }
164    let rv = rv.into_affine();
165    rv.y.into_bigint().is_even() && rv.x == *rx
166}
167
168/// Verify signature with legacy style
169pub fn legacy_verify_signature(
170    signature: &Signature,
171    pubkey: &PubKey,
172    msg: &TransactionUnionPayload,
173) -> bool {
174    use ::poseidon::hash::legacy;
175    use ark_ff::{BigInteger, Zero};
176    use core::ops::{Mul, Neg};
177    use mina_curves::pasta::{Fq, Pallas};
178    use mina_signer::CurvePoint;
179
180    let Pallas { x, y, .. } = pubkey.point();
181    let Signature { rx, s } = signature;
182
183    let signature_prefix = mina_core::NetworkConfig::global().legacy_signature_prefix;
184
185    let mut inputs = msg.to_input_legacy();
186    inputs.append_field(*x);
187    inputs.append_field(*y);
188    inputs.append_field(*rx);
189
190    let hash = legacy::hash_with_kimchi(signature_prefix, &inputs.to_fields());
191    let hash: Fq = Fq::from(hash.into_bigint()); // Never fail, `Fq` is larger than `Fp`
192
193    let sv: CurvePoint = CurvePoint::generator().mul(*s).into_affine();
194    // Perform addition and infinity check in projective coordinates for performance
195    let rv = pubkey.point().mul(hash).neg() + sv;
196    if rv.is_zero() {
197        return false;
198    }
199    let rv = rv.into_affine();
200    rv.y.into_bigint().is_even() && rv.x == *rx
201}