provekit-cli 1.0.0

ProveKit CLI for generating and verifying zero-knowledge proofs
//! Analyze ACIR circuit statistics and R1CS complexity.
//!
//! # Example
//!
//! ```sh
//! cargo run --release --bin provekit-cli circuit-stats ./target/basic.json
//! ```

mod display;
mod memory;
mod stats_collector;

use {
    super::Command,
    acir::{circuit::Program, FieldElement},
    anyhow::{Context, Result},
    argh::FromArgs,
    base64::Engine,
    provekit_r1cs_compiler::noir_to_r1cs_with_breakdown,
    stats_collector::CircuitStats,
    std::{
        fs,
        path::{Path, PathBuf},
    },
    tracing::instrument,
};

#[derive(FromArgs, PartialEq, Eq, Debug)]
#[argh(
    subcommand,
    name = "circuit-stats",
    description = "analyze ACIR circuit statistics and R1CS complexity"
)]
pub struct Args {
    #[argh(positional, description = "path to the ACIR circuit file (.json)")]
    circuit_path: PathBuf,
}

impl Command for Args {
    #[instrument(skip_all)]
    fn run(&self) -> Result<()> {
        let program = load_program(&self.circuit_path)?;
        analyze_circuit(program, &self.circuit_path)
    }
}

fn load_program(path: &Path) -> Result<Program<FieldElement>> {
    let json_string = fs::read_to_string(path)
        .with_context(|| format!("Failed to read circuit file: {}", path.display()))?;

    let json: serde_json::Value =
        serde_json::from_str(&json_string).context("Failed to parse circuit JSON")?;

    let bytecode_str = json["bytecode"]
        .as_str()
        .context("Expected 'bytecode' field in circuit JSON")?;

    let bytecode = base64::prelude::BASE64_STANDARD
        .decode(bytecode_str)
        .context("Failed to decode base64 bytecode")?;

    Program::deserialize_program(&bytecode).context("Failed to deserialize ACIR program")
}

fn analyze_circuit(program: Program<FieldElement>, path: &Path) -> Result<()> {
    anyhow::ensure!(
        program.functions.len() == 1,
        "Only single-function programs are currently supported (found {} functions)",
        program.functions.len()
    );

    let Program {
        mut functions,
        unconstrained_functions: _,
    } = program;
    let circuit = functions.pop().unwrap();

    println!("\n╔═══════════════════════════════════════════════════════════════╗");
    println!("║                   ACIR Circuit Analysis                       ║");
    println!("╚═══════════════════════════════════════════════════════════════╝");
    println!("Circuit: {}", path.display());

    display::print_io_summary(&circuit);

    let stats = CircuitStats::from_circuit(&circuit);

    display::print_acir_stats(&stats);

    let (r1cs, _witness_map, _witness_builders, breakdown) =
        noir_to_r1cs_with_breakdown(&circuit).context("Failed to compile circuit to R1CS")?;

    display::print_r1cs_breakdown(&stats, &circuit, &r1cs, &breakdown);

    Ok(())
}