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