mina_tree/verifier/
mod.rs

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