From d4111ae0db2b08733db66dc6436e4f549daeeb59 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans <desenfans.olivier@gmail.com> Date: Thu, 15 Feb 2024 15:53:45 +0100 Subject: [PATCH] Feature: add verifier methods (#3) Problems: * there is no sync implementation of the verifier functions * the verifier functions are located in the prover module. Solution: 1. Create a verifier module 2. Add sync implementations of the verifier functions. There are now 3x2 (sync + async) verifier methods: * `run_verifier_from_command_line`: thin wrapper around `cpu_air_verifier` * `run_verifier`: verify a proof file * `run_verifier_with_annotations`: verify a proof file and generate annotations. --- src/lib.rs | 1 + src/prover.rs | 64 +------------ src/verifier.rs | 235 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 61 deletions(-) create mode 100644 src/verifier.rs diff --git a/src/lib.rs b/src/lib.rs index 3e16102..2a1f261 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,4 @@ pub mod json; pub mod models; pub mod prover; pub(crate) mod test_utils; +pub mod verifier; diff --git a/src/prover.rs b/src/prover.rs index d09d5f1..67c70e2 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -1,14 +1,11 @@ -use cairo_vm::air_private_input::AirPrivateInput; use std::path::Path; +use cairo_vm::air_private_input::AirPrivateInput; use tempfile::tempdir; -use crate::models::{ - Proof, ProofAnnotations, ProverConfig, ProverParameters, ProverWorkingDirectory, PublicInput, -}; - -use crate::error::{ProverError, VerifierError}; +use crate::error::ProverError; use crate::json::{read_json_from_file, write_json_to_file}; +use crate::models::{Proof, ProverConfig, ProverParameters, ProverWorkingDirectory, PublicInput}; /// Call the Stone Prover from the command line. /// @@ -91,35 +88,6 @@ pub async fn run_prover_from_command_line_async( Ok(()) } -/// Call the Stone Verifier from the command line, asynchronously. -/// -/// Input files must be prepared by the caller. -/// -/// * `in_file`: Path to the proof generated from the prover. Corresponds to its "--out-file". -/// * `annotation_file`: Path to the annotations file, which will be generated as output. -/// * `extra_output_file`: Path to the extra annotations file, which will be generated as output. -pub async fn run_verifier_from_command_line_async( - in_file: &Path, - annotation_file: &Path, - extra_output_file: &Path, -) -> Result<(), VerifierError> { - let output = tokio::process::Command::new("cpu_air_verifier") - .arg("--in_file") - .arg(in_file) - .arg("--annotation_file") - .arg(annotation_file) - .arg("--extra_output_file") - .arg(extra_output_file) - .output() - .await?; - - if !output.status.success() { - return Err(VerifierError::CommandError(output)); - } - - Ok(()) -} - fn prepare_prover_files( public_input: &PublicInput, private_input: &AirPrivateInput, @@ -258,32 +226,6 @@ pub async fn run_prover_async( Ok((proof, prover_working_dir)) } -/// Run the Stone Verifier on the specified program execution, asynchronously. -/// -/// The main difference from the synchronous implementation is that the verifier process -/// is spawned asynchronously using `tokio::process::Command`. -/// -/// This function abstracts the method used to call the verifier. At the moment we invoke -/// the verifier as a subprocess but other methods can be implemented (ex: FFI). -/// -/// * `in_file`: Path to the proof generated from the prover. Corresponds to its "--out-file". -/// * `annotation_file`: Path to the annotations file, which will be generated as output. -/// * `extra_output_file`: Path to the extra annotations file, which will be generated as output. -pub async fn run_verifier_async( - in_file: &Path, - annotation_file: &Path, - extra_output_file: &Path, -) -> Result<ProofAnnotations, VerifierError> { - // Call the verifier - run_verifier_from_command_line_async(in_file, annotation_file, extra_output_file).await?; - - let annotations = ProofAnnotations { - annotation_file: annotation_file.into(), - extra_output_file: extra_output_file.into(), - }; - Ok(annotations) -} - #[cfg(test)] mod test { use rstest::rstest; diff --git a/src/verifier.rs b/src/verifier.rs new file mode 100644 index 0000000..76746f1 --- /dev/null +++ b/src/verifier.rs @@ -0,0 +1,235 @@ +use std::path::Path; + +use crate::error::VerifierError; +use crate::models::ProofAnnotations; + +/// Run the Stone Verifier on the specified program execution, asynchronously. +/// +/// The main difference from the synchronous implementation is that the verifier process +/// is spawned asynchronously using `tokio::process::Command`. +/// +/// This function abstracts the method used to call the verifier. At the moment we invoke +/// the verifier as a subprocess but other methods can be implemented (ex: FFI). +/// +/// * `in_file`: Path to the proof generated from the prover. Corresponds to its "--out-file". +pub fn run_verifier(in_file: &Path) -> Result<(), VerifierError> { + run_verifier_from_command_line(in_file, None, None) +} + +/// Run the Stone Verifier on the specified program execution. +/// +/// This function abstracts the method used to call the verifier. At the moment we invoke +/// the verifier as a subprocess but other methods can be implemented (ex: FFI). +/// +/// * `in_file`: Path to the proof generated from the prover. Corresponds to its "--out-file". +/// * `annotation_file`: Path to the annotations file, which will be generated as output. +/// * `extra_output_file`: Path to the extra annotations file, which will be generated as output. +pub fn run_verifier_with_annotations( + in_file: &Path, + annotation_file: &Path, + extra_output_file: &Path, +) -> Result<(), VerifierError> { + run_verifier_from_command_line(in_file, Some(annotation_file), Some(extra_output_file)) +} + +/// Call the Stone Verifier from the command line, asynchronously. +/// +/// Input files must be prepared by the caller. +/// +/// * `in_file`: Path to the proof generated from the prover. Corresponds to its "--out-file". +/// * `annotation_file`: Path to the annotations file, which will be generated as output. +/// * `extra_output_file`: Path to the extra annotations file, which will be generated as output. +pub fn run_verifier_from_command_line( + in_file: &Path, + annotation_file: Option<&Path>, + extra_output_file: Option<&Path>, +) -> Result<(), VerifierError> { + let mut command = std::process::Command::new("cpu_air_verifier"); + command + .arg("cpu_air_verifier") + .arg("--in_file") + .arg(in_file); + + if let Some(annotation_file) = annotation_file { + command.arg("--annotation_file").arg(annotation_file); + } + + if let Some(extra_output_file) = extra_output_file { + command.arg("--extra_output_file").arg(extra_output_file); + } + + let output = command.output()?; + + if !output.status.success() { + return Err(VerifierError::CommandError(output)); + } + + Ok(()) +} + +/// Run the Stone Verifier on the specified program execution, asynchronously. +/// +/// The main difference from the synchronous implementation is that the verifier process +/// is spawned asynchronously using `tokio::process::Command`. +/// +/// This function abstracts the method used to call the verifier. At the moment we invoke +/// the verifier as a subprocess but other methods can be implemented (ex: FFI). +/// +/// * `in_file`: Path to the proof generated from the prover. Corresponds to its "--out-file". +pub async fn run_verifier_async(in_file: &Path) -> Result<(), VerifierError> { + run_verifier_from_command_line_async(in_file, None, None).await +} + +/// Run the Stone Verifier on the specified program execution, asynchronously. +/// +/// The main difference from the synchronous implementation is that the verifier process +/// is spawned asynchronously using `tokio::process::Command`. +/// +/// This function abstracts the method used to call the verifier. At the moment we invoke +/// the verifier as a subprocess but other methods can be implemented (ex: FFI). +/// +/// * `in_file`: Path to the proof generated from the prover. Corresponds to its "--out-file". +/// * `annotation_file`: Path to the annotations file, which will be generated as output. +/// * `extra_output_file`: Path to the extra annotations file, which will be generated as output. +pub async fn run_verifier_with_annotations_async( + in_file: &Path, + annotation_file: &Path, + extra_output_file: &Path, +) -> Result<ProofAnnotations, VerifierError> { + run_verifier_from_command_line_async(in_file, Some(annotation_file), Some(extra_output_file)) + .await?; + + let annotations = ProofAnnotations { + annotation_file: annotation_file.into(), + extra_output_file: extra_output_file.into(), + }; + Ok(annotations) +} + +/// Call the Stone Verifier from the command line, asynchronously. +/// +/// Input files must be prepared by the caller. +/// +/// * `in_file`: Path to the proof generated from the prover. Corresponds to its "--out-file". +/// * `annotation_file`: Path to the annotations file, which will be generated as output. +/// * `extra_output_file`: Path to the extra annotations file, which will be generated as output. +pub async fn run_verifier_from_command_line_async( + in_file: &Path, + annotation_file: Option<&Path>, + extra_output_file: Option<&Path>, +) -> Result<(), VerifierError> { + let mut command = tokio::process::Command::new("cpu_air_verifier"); + command + .arg("cpu_air_verifier") + .arg("--in_file") + .arg(in_file); + + if let Some(annotation_file) = annotation_file { + command.arg("--annotation_file").arg(annotation_file); + } + + if let Some(extra_output_file) = extra_output_file { + command.arg("--extra_output_file").arg(extra_output_file); + } + + let output = command.output().await?; + + if !output.status.success() { + return Err(VerifierError::CommandError(output)); + } + + Ok(()) +} + +#[cfg(test)] +mod test { + use rstest::rstest; + + use crate::test_utils::{prover_in_path, prover_test_case, ProverTestCase}; + + use super::*; + + /// Check that the Stone Verifier command-line wrapper works. + #[rstest] + fn test_run_verifier_from_command_line( + prover_test_case: ProverTestCase, + #[from(prover_in_path)] _path: (), + ) { + let proof_file = prover_test_case.proof_file; + run_verifier_from_command_line(proof_file.as_path(), None, None) + .expect("Proof file is valid"); + } + + #[rstest] + fn test_run_verifier(prover_test_case: ProverTestCase, #[from(prover_in_path)] _path: ()) { + let proof_file = prover_test_case.proof_file; + run_verifier(proof_file.as_path()).expect("Proof file is valid"); + } + + #[rstest] + fn test_run_verifier_with_annotations( + prover_test_case: ProverTestCase, + #[from(prover_in_path)] _path: (), + ) { + let output_dir = tempfile::tempdir().unwrap(); + let annotation_file = output_dir.path().join("annotations.json"); + let extra_output_file = output_dir.path().join("extra_output_file.json"); + + run_verifier_with_annotations( + prover_test_case.proof_file.as_path(), + annotation_file.as_path(), + extra_output_file.as_path(), + ) + .expect("Proof is valid"); + // TODO: generate fixtures to compare the generated files + assert!(annotation_file.exists()); + assert!(extra_output_file.exists()); + } + + /// Check that the Stone Verifier command-line wrapper works. + #[rstest] + #[tokio::test] + async fn test_run_verifier_from_command_line_async( + prover_test_case: ProverTestCase, + #[from(prover_in_path)] _path: (), + ) { + let proof_file = prover_test_case.proof_file; + run_verifier_from_command_line_async(proof_file.as_path(), None, None) + .await + .expect("Proof file is valid"); + } + + #[rstest] + #[tokio::test] + async fn test_run_verifier_async( + prover_test_case: ProverTestCase, + #[from(prover_in_path)] _path: (), + ) { + let proof_file = prover_test_case.proof_file; + run_verifier_async(proof_file.as_path()) + .await + .expect("Proof file is valid"); + } + + #[rstest] + #[tokio::test] + async fn test_run_verifier_with_annotations_async( + prover_test_case: ProverTestCase, + #[from(prover_in_path)] _path: (), + ) { + let output_dir = tempfile::tempdir().unwrap(); + let annotation_file = output_dir.path().join("annotations.json"); + let extra_output_file = output_dir.path().join("extra_output_file.json"); + + run_verifier_with_annotations_async( + prover_test_case.proof_file.as_path(), + annotation_file.as_path(), + extra_output_file.as_path(), + ) + .await + .expect("Proof is valid"); + // TODO: generate fixtures to compare the generated files + assert!(annotation_file.exists()); + assert!(extra_output_file.exists()); + } +}