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 Structured Reference String (SRS) for proof verification.
38/// Lazily created and cached globally. Immutable version for verifiers.
39///
40/// TODO: Use directly from proof-systems (<https://github.com/o1-labs/mina-rust/issues/1749>)
41pub 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
51/// Returns the SRS on the other curve (Mutex-wrapped version for prover).
52///
53/// TODO: Use directly from proof-systems (<https://github.com/o1-labs/mina-rust/issues/1749>)
54pub 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
64/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/transaction_snark/transaction_snark.ml#L3492>
65fn 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        // for (proof, msg) in ts {
75        //     let LedgerProof(TransactionSnark {
76        //         statement,
77        //         proof: p,
78        //     }) = &proof;
79        //     let (stmt, p) = (statement, &**p);
80        //     if !crate::proofs::verification::verify_transaction([(stmt, p)], verifier_index) {
81        //         let a: mina_p2p_messages::v2::LedgerProofProdStableV2 = (&proof).into();
82        //         let b: mina_p2p_messages::v2::MinaBaseSokMessageStableV1 = (&msg).into();
83        //         let mut file = std::fs::File::create("ledger_proof2.bin").unwrap();
84        //         binprot::BinProtWrite::binprot_write(&a, &mut file).unwrap();
85        //         file.sync_all().unwrap();
86        //         let mut file = std::fs::File::create("sok_msg2.bin").unwrap();
87        //         binprot::BinProtWrite::binprot_write(&b, &mut file).unwrap();
88        //         file.sync_all().unwrap();
89        //         panic!();
90        //     }
91        // }
92
93        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/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/verifier/dummy.ml#L59C1-L75C81>
108#[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
122/// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/verifier/verifier_intf.ml#L10C1-L36C29>
123pub type VerifyCommandsResult = Result<valid::UserCommand, VerifierError>;
124
125#[derive(Debug, thiserror::Error)]
126pub enum VerifierError {
127    // TODO(adonagy): print something here as well?
128    #[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        // Implement verification later
158        //
159        // <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/pickles/pickles.ml#L1122>
160        // <https://viable-systems.slack.com/archives/D01SVA87PQC/p1671715846448749>
161        Ok(Ok(()))
162    }
163
164    /// <https://github.com/MinaProtocol/mina/blob/bfd1009abdbee78979ff0343cc73a3480e862f58/src/lib/verifier/prod.ml#L138>
165    #[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
230// #[derive(Debug, derive_more::From)]
231// pub enum VerifierError {
232//     CheckError(CheckResult),
233//     VerificationFailed(String),
234// }
235
236pub 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    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/verifier/common.ml#L29>
279    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                                // Don't verify the proof if it has failed.
344                                continue;
345                            }
346                            let Some(vk) = vk_opt else {
347                                return CheckResult::MissingVerificationKey(vec![
348                                    p.account_id().public_key,
349                                ]);
350                            };
351                            // check that vk expected for proof is the one being used
352                            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                    // Verification keys should be present if it reaches here
369                    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    /// Verify zkapp signature/statement with new style (chunked inputs)
383    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()); // Never fail, `Fq` is larger than `Fp`
399
400        let sv: CurvePoint = CurvePoint::generator().mul(*s).into_affine();
401        // Perform addition and infinity check in projective coordinates for performance
402        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    /// Verify signature with legacy style
411    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()); // Never fail, `Fq` is larger than `Fp`
434
435        let sv: CurvePoint = CurvePoint::generator().mul(*s).into_affine();
436        // Perform addition and infinity check in projective coordinates for performance
437        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}