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());
+    }
+}