mina_tree/verifier/
common.rs1use 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
40pub 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 continue;
107 }
108 let Some(vk) = vk_opt else {
109 return CheckResult::MissingVerificationKey(vec![
110 p.account_id().public_key,
111 ]);
112 };
113 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 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
144fn 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()); let sv: CurvePoint = CurvePoint::generator().mul(*s).into_affine();
159 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
168pub 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()); let sv: CurvePoint = CurvePoint::generator().mul(*s).into_affine();
194 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}