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