1use std::sync::{Arc, Mutex};
2
3use crate::{
4 proofs::{
5 self, field::FieldWitness, verification, verifiers::TransactionVerifier, VerifierIndex,
6 },
7 scan_state::{
8 scan_state::transaction_snark::{
9 LedgerProof, LedgerProofWithSokMessage, SokMessage, TransactionSnark,
10 },
11 transaction_logic::{valid, verifiable, zkapp_statement::ZkappStatement, WithStatus},
12 },
13 staged_ledger::staged_ledger::SkipVerification,
14 VerificationKey,
15};
16
17use self::common::CheckResult;
18
19#[derive(Debug, Clone)]
20pub struct Verifier;
21
22use mina_curves::pasta::{Fp, Fq};
23use mina_p2p_messages::v2::{
24 PicklesProofProofsVerified2ReprStableV2, PicklesProofProofsVerifiedMaxStableV2,
25};
26use mina_signer::CompressedPubKey;
27use once_cell::sync::Lazy;
28use poly_commitment::{ipa::SRS, SRS as _};
29
30pub static VERIFIER_INDEX: Lazy<Arc<VerifierIndex<Fq>>> = Lazy::new(|| {
32 TransactionVerifier::get()
33 .expect("verifier index not initialized")
34 .into()
35});
36
37pub fn get_srs<F: FieldWitness>() -> Arc<SRS<F::OtherCurve>> {
39 cache! {
40 Arc<SRS<F::OtherCurve>>,
41 {
42 let srs = SRS::<F::OtherCurve>::create(<F as proofs::field::FieldWitness>::Scalar::SRS_DEPTH);
43 Arc::new(srs)
44 }
45 }
46}
47
48pub fn get_srs_mut<F: FieldWitness>() -> Arc<Mutex<SRS<F::OtherCurve>>> {
50 cache! {
51 Arc<Mutex<SRS<F::OtherCurve>>>,
52 {
53 let srs = SRS::<F::OtherCurve>::create(<F as proofs::field::FieldWitness>::Scalar::SRS_DEPTH);
54 Arc::new(Mutex::new(srs))
55 }
56 }
57}
58
59fn verify(ts: Vec<(LedgerProof, SokMessage)>) -> Result<(), String> {
61 let srs = get_srs::<Fp>();
62
63 if ts.iter().all(|(proof, msg)| {
64 let LedgerProof(TransactionSnark { statement, .. }) = proof;
65 statement.sok_digest == msg.digest()
66 }) {
67 let verifier_index = VERIFIER_INDEX.as_ref();
68
69 let proofs = ts.iter().map(|(proof, _)| {
89 let LedgerProof(TransactionSnark { statement, proof }) = proof;
90 (statement, &**proof)
91 });
92
93 if !crate::proofs::verification::verify_transaction(proofs, verifier_index, &srs) {
94 return Err("Transaction_snark.verify: verification failed".into());
95 }
96 Ok(())
97 } else {
98 Err("Transaction_snark.verify: Mismatched sok_message".into())
99 }
100}
101
102#[cfg(test)]
104fn verify_digest_only(ts: Vec<(LedgerProof, SokMessage)>) -> Result<(), String> {
105 use crate::scan_state::scan_state::transaction_snark::SokDigest;
106
107 if ts.iter().all(|(proof, msg)| {
108 let LedgerProof(TransactionSnark { statement, .. }) = proof;
109 statement.sok_digest == SokDigest::default() || statement.sok_digest == msg.digest()
110 }) {
111 Ok(())
112 } else {
113 Err("Transaction_snark.verify: Mismatched sok_message".into())
114 }
115}
116
117pub type VerifyCommandsResult = Result<valid::UserCommand, VerifierError>;
119
120#[derive(Debug, thiserror::Error)]
121pub enum VerifierError {
122 #[error("Batch verification failed")]
124 ValidAssuming(
125 Vec<(
126 VerificationKey,
127 ZkappStatement,
128 Arc<PicklesProofProofsVerifiedMaxStableV2>,
129 )>,
130 ),
131 #[error("Invalid keys: {0:?}")]
132 InvalidKeys(Vec<CompressedPubKey>),
133 #[error("Invalid signature: {0:?}")]
134 InvalidSignature(Vec<CompressedPubKey>),
135 #[error("Invalid proof: {0}")]
136 InvalidProof(String),
137 #[error("Missing verification key: {0:?}")]
138 MissingVerificationKey(Vec<CompressedPubKey>),
139 #[error("Unexpected verification key: {0:?}")]
140 UnexpectedVerificationKey(Vec<CompressedPubKey>),
141 #[error("Mismatched verification key: {0:?}")]
142 MismatchedVerificationKey(Vec<CompressedPubKey>),
143 #[error("Authorization kind does not match the authorization - Keys {0:?}")]
144 MismatchedAuthorizationKind(Vec<CompressedPubKey>),
145}
146
147impl Verifier {
148 pub fn verify(
149 &self,
150 _proofs: &[Arc<LedgerProofWithSokMessage>],
151 ) -> Result<Result<(), ()>, String> {
152 Ok(Ok(()))
157 }
158
159 #[allow(unreachable_code)]
161 pub fn verify_transaction_snarks(
162 &self,
163 ts: Vec<(LedgerProof, SokMessage)>,
164 ) -> Result<(), String> {
165 #[cfg(test)]
166 return verify_digest_only(ts);
167
168 verify(ts)
169 }
170
171 pub fn verify_commands(
172 &self,
173 cmds: Vec<WithStatus<verifiable::UserCommand>>,
174 skip_verification: Option<SkipVerification>,
175 ) -> Vec<VerifyCommandsResult> {
176 let cs: Vec<_> = cmds.into_iter().map(common::check).collect();
177
178 let mut to_verify = cs
179 .iter()
180 .filter_map(|c| match c {
181 CheckResult::Valid(_) => None,
182 CheckResult::ValidAssuming((_, xs)) => Some(xs),
183 _ => None,
184 })
185 .flatten();
186
187 let all_verified = if skip_verification.is_some() {
188 true
189 } else {
190 let srs = get_srs::<Fp>();
191
192 to_verify.all(|(vk, zkapp_statement, proof)| {
193 let proof: PicklesProofProofsVerified2ReprStableV2 = (&**proof).into();
194 verification::verify_zkapp(vk, zkapp_statement, &proof, &srs)
195 })
196 };
197
198 cs.into_iter()
199 .map(|c| match c {
200 CheckResult::Valid(c) => Ok(c),
201 CheckResult::ValidAssuming((c, xs)) => {
202 if all_verified {
203 Ok(c)
204 } else {
205 Err(VerifierError::ValidAssuming(xs))
206 }
207 }
208 CheckResult::InvalidKeys(keys) => Err(VerifierError::InvalidKeys(keys)),
209 CheckResult::InvalidSignature(keys) => Err(VerifierError::InvalidSignature(keys)),
210 CheckResult::InvalidProof(s) => Err(VerifierError::InvalidProof(s)),
211 CheckResult::MissingVerificationKey(keys) => {
212 Err(VerifierError::MissingVerificationKey(keys))
213 }
214 CheckResult::UnexpectedVerificationKey(keys) => {
215 Err(VerifierError::UnexpectedVerificationKey(keys))
216 }
217 CheckResult::MismatchedAuthorizationKind(keys) => {
218 Err(VerifierError::MismatchedAuthorizationKind(keys))
219 }
220 })
221 .collect()
222 }
223}
224
225pub mod common {
232 use std::sync::Arc;
233
234 use ark_ec::{AffineRepr, CurveGroup};
235 use ark_ff::PrimeField;
236 use mina_p2p_messages::v2::PicklesProofProofsVerifiedMaxStableV2;
237 use mina_signer::{CompressedPubKey, PubKey, Signature};
238 use poseidon::hash::hash_with_kimchi;
239
240 use crate::{
241 decompress_pk,
242 scan_state::transaction_logic::{
243 transaction_union_payload::TransactionUnionPayload,
244 valid, verifiable,
245 zkapp_command::{self, valid::of_verifiable, AccountUpdate},
246 zkapp_statement::{TransactionCommitment, ZkappStatement},
247 TransactionStatus, WithStatus,
248 },
249 VerificationKey,
250 };
251
252 #[derive(Debug)]
253 pub enum CheckResult {
254 Valid(valid::UserCommand),
255 ValidAssuming(
256 (
257 valid::UserCommand,
258 Vec<(
259 VerificationKey,
260 ZkappStatement,
261 Arc<PicklesProofProofsVerifiedMaxStableV2>,
262 )>,
263 ),
264 ),
265 InvalidKeys(Vec<CompressedPubKey>),
266 InvalidSignature(Vec<CompressedPubKey>),
267 InvalidProof(String),
268 MissingVerificationKey(Vec<CompressedPubKey>),
269 UnexpectedVerificationKey(Vec<CompressedPubKey>),
270 MismatchedAuthorizationKind(Vec<CompressedPubKey>),
271 }
272
273 pub fn check(cmd: WithStatus<verifiable::UserCommand>) -> CheckResult {
275 use verifiable::UserCommand::{SignedCommand, ZkAppCommand};
276
277 match cmd.data {
278 SignedCommand(cmd) => {
279 if !cmd.check_valid_keys() {
280 let public_keys = cmd.public_keys().into_iter().cloned().collect();
281 return CheckResult::InvalidKeys(public_keys);
282 }
283 match verifiable::check_only_for_signature(cmd) {
284 Ok(cmd) => CheckResult::Valid(cmd),
285 Err(cmd) => CheckResult::InvalidSignature(
286 cmd.public_keys().into_iter().cloned().collect(),
287 ),
288 }
289 }
290 ZkAppCommand(zkapp_command_with_vk) => {
291 let zkapp_command::verifiable::ZkAppCommand {
292 fee_payer,
293 account_updates,
294 memo,
295 } = &*zkapp_command_with_vk;
296
297 let account_updates_hash = account_updates.hash();
298 let tx_commitment = TransactionCommitment::create(account_updates_hash);
299
300 let memo_hash = memo.hash();
301 let fee_payer_hash = AccountUpdate::of_fee_payer(fee_payer.clone()).digest();
302 let full_tx_commitment = tx_commitment.create_complete(memo_hash, fee_payer_hash);
303
304 let Some(pk) = decompress_pk(&fee_payer.body.public_key) else {
305 return CheckResult::InvalidKeys(vec![fee_payer.body.public_key.clone()]);
306 };
307
308 if !verify_signature(&fee_payer.authorization, &pk, &full_tx_commitment) {
309 return CheckResult::InvalidSignature(vec![pk.into_compressed()]);
310 }
311
312 let zkapp_command_with_hashes_list =
313 ZkappStatement::zkapp_statements_of_forest_prime(account_updates.clone())
314 .to_zkapp_command_with_hashes_list();
315
316 let mut valid_assuming = Vec::with_capacity(16);
317 for ((p, (vk_opt, stmt)), _at_account_update) in zkapp_command_with_hashes_list {
318 let commitment = if p.body.use_full_commitment {
319 full_tx_commitment
320 } else {
321 tx_commitment
322 };
323
324 use zkapp_command::{AuthorizationKind as AK, Control as C};
325 match (&p.authorization, &p.body.authorization_kind) {
326 (C::Signature(s), AK::Signature) => {
327 let pk = decompress_pk(&p.body.public_key).unwrap();
328 if !verify_signature(s, &pk, &commitment) {
329 return CheckResult::InvalidSignature(vec![pk.into_compressed()]);
330 }
331 continue;
332 }
333 (C::NoneGiven, AK::NoneGiven) => {
334 continue;
335 }
336 (C::Proof(pi), AK::Proof(vk_hash)) => {
337 if let TransactionStatus::Failed(_) = cmd.status {
338 continue;
340 }
341 let Some(vk) = vk_opt else {
342 return CheckResult::MissingVerificationKey(vec![
343 p.account_id().public_key,
344 ]);
345 };
346 if vk_hash != &vk.hash() {
348 return CheckResult::UnexpectedVerificationKey(vec![
349 p.account_id().public_key,
350 ]);
351 }
352 valid_assuming.push((vk.vk().clone(), stmt, pi.clone()));
353 }
354 _ => {
355 return CheckResult::MismatchedAuthorizationKind(vec![
356 p.account_id().public_key,
357 ]);
358 }
359 }
360 }
361
362 let v: valid::UserCommand = {
363 let zkapp = of_verifiable(*zkapp_command_with_vk);
365 valid::UserCommand::ZkAppCommand(Box::new(zkapp))
366 };
367
368 if valid_assuming.is_empty() {
369 CheckResult::Valid(v)
370 } else {
371 CheckResult::ValidAssuming((v, valid_assuming))
372 }
373 }
374 }
375 }
376
377 fn verify_signature(
379 signature: &Signature,
380 pubkey: &PubKey,
381 msg: &TransactionCommitment,
382 ) -> bool {
383 use ark_ff::{BigInteger, Zero};
384 use core::ops::{Mul, Neg};
385 use mina_curves::pasta::{Fq, Pallas};
386 use mina_signer::CurvePoint;
387
388 let Pallas { x, y, .. } = pubkey.point();
389 let Signature { rx, s } = signature;
390
391 let signature_prefix = mina_core::NetworkConfig::global().signature_prefix;
392 let hash = hash_with_kimchi(signature_prefix, &[**msg, *x, *y, *rx]);
393 let hash: Fq = Fq::from(hash.into_bigint()); let sv: CurvePoint = CurvePoint::generator().mul(*s).into_affine();
396 let rv = pubkey.point().mul(hash).neg() + sv;
398 if rv.is_zero() {
399 return false;
400 }
401 let rv = rv.into_affine();
402 rv.y.into_bigint().is_even() && rv.x == *rx
403 }
404
405 pub fn legacy_verify_signature(
407 signature: &Signature,
408 pubkey: &PubKey,
409 msg: &TransactionUnionPayload,
410 ) -> bool {
411 use ::poseidon::hash::legacy;
412 use ark_ff::{BigInteger, Zero};
413 use core::ops::{Mul, Neg};
414 use mina_curves::pasta::{Fq, Pallas};
415 use mina_signer::CurvePoint;
416
417 let Pallas { x, y, .. } = pubkey.point();
418 let Signature { rx, s } = signature;
419
420 let signature_prefix = mina_core::NetworkConfig::global().legacy_signature_prefix;
421
422 let mut inputs = msg.to_input_legacy();
423 inputs.append_field(*x);
424 inputs.append_field(*y);
425 inputs.append_field(*rx);
426
427 let hash = legacy::hash_with_kimchi(signature_prefix, &inputs.to_fields());
428 let hash: Fq = Fq::from(hash.into_bigint()); let sv: CurvePoint = CurvePoint::generator().mul(*s).into_affine();
431 let rv = pubkey.point().mul(hash).neg() + sv;
433 if rv.is_zero() {
434 return false;
435 }
436 let rv = rv.into_affine();
437 rv.y.into_bigint().is_even() && rv.x == *rx
438 }
439}