mina_tree/verifier/
mod.rs

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
28// TODO: Move this into `Verifier` struct above
29pub static VERIFIER_INDEX: Lazy<Arc<VerifierIndex<Fq>>> = Lazy::new(|| {
30    TransactionVerifier::get()
31        .expect("verifier index not initialized")
32        .into()
33});
34
35/// Returns the SRS on the other curve (immutable version for verifiers)
36pub 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
46/// Returns the SRS on the other curve (Mutex-wrapped version for prover)
47pub 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
57/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/transaction_snark/transaction_snark.ml#L3492>
58fn 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        // for (proof, msg) in ts {
68        //     let LedgerProof(TransactionSnark {
69        //         statement,
70        //         proof: p,
71        //     }) = &proof;
72        //     let (stmt, p) = (statement, &**p);
73        //     if !crate::proofs::verification::verify_transaction([(stmt, p)], verifier_index) {
74        //         let a: mina_p2p_messages::v2::LedgerProofProdStableV2 = (&proof).into();
75        //         let b: mina_p2p_messages::v2::MinaBaseSokMessageStableV1 = (&msg).into();
76        //         let mut file = std::fs::File::create("ledger_proof2.bin").unwrap();
77        //         binprot::BinProtWrite::binprot_write(&a, &mut file).unwrap();
78        //         file.sync_all().unwrap();
79        //         let mut file = std::fs::File::create("sok_msg2.bin").unwrap();
80        //         binprot::BinProtWrite::binprot_write(&b, &mut file).unwrap();
81        //         file.sync_all().unwrap();
82        //         panic!();
83        //     }
84        // }
85
86        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/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/verifier/dummy.ml#L59C1-L75C81>
101#[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
115/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/verifier/verifier_intf.ml#L10C1-L36C29>
116pub type VerifyCommandsResult = Result<valid::UserCommand, VerifierError>;
117
118#[derive(Debug, thiserror::Error)]
119pub enum VerifierError {
120    // TODO(adonagy): print something here as well?
121    #[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        // Implement verification later
151        //
152        // <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/pickles/pickles.ml#L1122>
153        // <https://viable-systems.slack.com/archives/D01SVA87PQC/p1671715846448749>
154        Ok(Ok(()))
155    }
156
157    /// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/verifier/prod.ml#L138>
158    #[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
223// #[derive(Debug, derive_more::From)]
224// pub enum VerifierError {
225//     CheckError(CheckResult),
226//     VerificationFailed(String),
227// }
228
229pub 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    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/verifier/common.ml#L29>
270    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                                // Don't verify the proof if it has failed.
335                                continue;
336                            }
337                            let Some(vk) = vk_opt else {
338                                return CheckResult::MissingVerificationKey(vec![
339                                    p.account_id().public_key,
340                                ]);
341                            };
342                            // check that vk expected for proof is the one being used
343                            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                    // Verification keys should be present if it reaches here
360                    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    /// Verify zkapp signature/statement with new style (chunked inputs)
374    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(); // Never fail, `Fq` is larger than `Fp`
391
392        let sv: CurvePoint = CurvePoint::prime_subgroup_generator().mul(*s).into_affine();
393        // Perform addition and infinity check in projective coordinates for performance
394        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    /// Verify signature with legacy style
403    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(); // Never fail, `Fq` is larger than `Fp`
427
428        let sv: CurvePoint = CurvePoint::prime_subgroup_generator().mul(*s).into_affine();
429        // Perform addition and infinity check in projective coordinates for performance
430        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}