Skip to content

Commit

Permalink
Feature: add verifier methods (#3)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
odesenfans authored Feb 15, 2024
1 parent 899e192 commit d4111ae
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 61 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pub mod json;
pub mod models;
pub mod prover;
pub(crate) mod test_utils;
pub mod verifier;
64 changes: 3 additions & 61 deletions src/prover.rs
Original file line number Diff line number Diff line change
@@ -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.
///
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
235 changes: 235 additions & 0 deletions src/verifier.rs
Original file line number Diff line number Diff line change
@@ -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());
}
}

0 comments on commit d4111ae

Please sign in to comment.