diff --git a/crates/nargo/src/cli/compile_cmd.rs b/crates/nargo/src/cli/compile_cmd.rs index f9edbf63084..1e109c27f33 100644 --- a/crates/nargo/src/cli/compile_cmd.rs +++ b/crates/nargo/src/cli/compile_cmd.rs @@ -1,6 +1,5 @@ use std::path::PathBuf; -use acvm::acir::native_types::Witness; use acvm::ProofSystemCompiler; use clap::ArgMatches; @@ -8,7 +7,8 @@ use clap::ArgMatches; use std::path::Path; use crate::{ - constants::{ACIR_EXT, TARGET_DIR, WITNESS_EXT}, + cli::execute_cmd::save_witness_to_dir, + constants::{ACIR_EXT, TARGET_DIR}, errors::CliError, resolver::Resolver, }; @@ -54,14 +54,11 @@ pub fn generate_circuit_and_witness_to_disk>( println!("{:?}", std::fs::canonicalize(&circuit_path)); if generate_witness { - let solved_witness = - super::prove_cmd::parse_and_solve_witness(program_dir, &compiled_program)?; - let buf = Witness::to_bytes(&solved_witness); + let (_, solved_witness) = + super::execute_cmd::execute_program(program_dir, &compiled_program)?; circuit_path.pop(); - circuit_path.push(circuit_name); - circuit_path.set_extension(WITNESS_EXT); - write_to_file(buf.as_slice(), &circuit_path); + save_witness_to_dir(solved_witness, circuit_name, &circuit_path)?; } Ok(circuit_path) diff --git a/crates/nargo/src/cli/execute_cmd.rs b/crates/nargo/src/cli/execute_cmd.rs new file mode 100644 index 00000000000..61ad940a71d --- /dev/null +++ b/crates/nargo/src/cli/execute_cmd.rs @@ -0,0 +1,146 @@ +use std::path::{Path, PathBuf}; + +use acvm::acir::native_types::Witness; +use acvm::FieldElement; +use acvm::PartialWitnessGenerator; +use clap::ArgMatches; +use noirc_abi::errors::AbiError; +use noirc_abi::input_parser::{Format, InputValue}; +use noirc_abi::{Abi, MAIN_RETURN_NAME}; +use noirc_driver::CompiledProgram; + +use super::{create_named_dir, read_inputs_from_file, write_to_file}; +use super::{InputMap, WitnessMap}; +use crate::{ + constants::{PROVER_INPUT_FILE, TARGET_DIR, WITNESS_EXT}, + errors::CliError, +}; + +pub(crate) fn run(args: ArgMatches) -> Result<(), CliError> { + let args = args.subcommand_matches("execute").unwrap(); + let witness_name = args.value_of("witness_name"); + let show_ssa = args.is_present("show-ssa"); + let allow_warnings = args.is_present("allow-warnings"); + let (return_value, solved_witness) = execute(show_ssa, allow_warnings)?; + + println!("Circuit witness successfully solved"); + if let Some(return_value) = return_value { + println!("Circuit output: {return_value:?}"); + } + if let Some(witness_name) = witness_name { + let mut witness_dir = std::env::current_dir().unwrap(); + witness_dir.push(TARGET_DIR); + + let witness_path = save_witness_to_dir(solved_witness, witness_name, witness_dir)?; + + println!("Witness saved to {}", witness_path.display()); + } + Ok(()) +} + +/// In Barretenberg, the proof system adds a zero witness in the first index, +/// So when we add witness values, their index start from 1. +const WITNESS_OFFSET: u32 = 1; + +fn execute( + show_ssa: bool, + allow_warnings: bool, +) -> Result<(Option, WitnessMap), CliError> { + let current_dir = std::env::current_dir().unwrap(); + + let compiled_program = + super::compile_cmd::compile_circuit(¤t_dir, show_ssa, allow_warnings)?; + + execute_program(current_dir, &compiled_program) +} + +pub(crate) fn execute_program>( + inputs_dir: P, + compiled_program: &CompiledProgram, +) -> Result<(Option, WitnessMap), CliError> { + // Parse the initial witness values from Prover.toml + let witness_map = read_inputs_from_file( + inputs_dir, + PROVER_INPUT_FILE, + Format::Toml, + compiled_program.abi.as_ref().unwrap().clone(), + )?; + + // Solve the remaining witnesses + let solved_witness = solve_witness(compiled_program, &witness_map)?; + + let public_inputs = extract_public_inputs(compiled_program, &solved_witness)?; + let return_value = public_inputs.get(MAIN_RETURN_NAME).cloned(); + + Ok((return_value, solved_witness)) +} + +pub(crate) fn extract_public_inputs( + compiled_program: &CompiledProgram, + solved_witness: &WitnessMap, +) -> Result { + let encoded_public_inputs: Vec = compiled_program + .circuit + .public_inputs + .0 + .iter() + .map(|index| solved_witness[index]) + .collect(); + + let public_abi = compiled_program.abi.as_ref().unwrap().clone().public_abi(); + + public_abi.decode(&encoded_public_inputs) +} + +pub(crate) fn solve_witness( + compiled_program: &CompiledProgram, + input_map: &InputMap, +) -> Result { + let abi = compiled_program.abi.as_ref().unwrap().clone(); + let mut solved_witness = + input_map_to_witness_map(abi, input_map).map_err(|error| match error { + AbiError::UndefinedInput(_) => { + CliError::Generic(format!("{error} in the {PROVER_INPUT_FILE}.toml file.")) + } + _ => CliError::from(error), + })?; + + let backend = crate::backends::ConcreteBackend; + backend.solve(&mut solved_witness, compiled_program.circuit.opcodes.clone())?; + + Ok(solved_witness) +} + +/// Given an InputMap and an Abi, produce a WitnessMap +/// +/// In particular, this method shows one how to associate values in a Toml/JSON +/// file with witness indices +fn input_map_to_witness_map(abi: Abi, input_map: &InputMap) -> Result { + // The ABI map is first encoded as a vector of field elements + let encoded_inputs = abi.encode(input_map, true)?; + + Ok(encoded_inputs + .into_iter() + .enumerate() + .map(|(index, witness_value)| { + let witness = Witness::new(WITNESS_OFFSET + (index as u32)); + (witness, witness_value) + }) + .collect()) +} + +pub(crate) fn save_witness_to_dir>( + witness: WitnessMap, + witness_name: &str, + witness_dir: P, +) -> Result { + let mut witness_path = create_named_dir(witness_dir.as_ref(), "witness"); + witness_path.push(witness_name); + witness_path.set_extension(WITNESS_EXT); + + let buf = Witness::to_bytes(&witness); + + write_to_file(buf.as_slice(), &witness_path); + + Ok(witness_path) +} diff --git a/crates/nargo/src/cli/mod.rs b/crates/nargo/src/cli/mod.rs index f58788d3930..8e5028bac5b 100644 --- a/crates/nargo/src/cli/mod.rs +++ b/crates/nargo/src/cli/mod.rs @@ -26,6 +26,7 @@ use crate::errors::CliError; mod check_cmd; mod compile_cmd; mod contract_cmd; +mod execute_cmd; mod gates_cmd; mod new_cmd; mod prove_cmd; @@ -109,6 +110,17 @@ pub fn start_cli() { .subcommand( App::new("gates") .about("Counts the occurrences of different gates in circuit") + .arg(show_ssa.clone()) + .arg(allow_warnings.clone()), + ) + .subcommand( + App::new("execute") + .about("Executes a circuit to calculate its return value") + .arg( + Arg::with_name("witness_name") + .long("witness_name") + .help("Write the execution witness to named file"), + ) .arg(show_ssa) .arg(allow_warnings), ) @@ -123,6 +135,7 @@ pub fn start_cli() { Some("compile") => compile_cmd::run(matches), Some("verify") => verify_cmd::run(matches), Some("gates") => gates_cmd::run(matches), + Some("execute") => execute_cmd::run(matches), Some("test") => test_cmd::run(matches), Some(x) => Err(CliError::Generic(format!("unknown command : {x}"))), _ => unreachable!(), @@ -162,7 +175,7 @@ pub fn read_inputs_from_file>( file_name: &str, format: Format, abi: Abi, -) -> Result, CliError> { +) -> Result { let file_path = { let mut dir_path = path.as_ref().to_path_buf(); dir_path.push(file_name); @@ -178,7 +191,7 @@ pub fn read_inputs_from_file>( } fn write_inputs_to_file>( - w_map: &BTreeMap, + w_map: &InputMap, path: P, file_name: &str, format: Format, diff --git a/crates/nargo/src/cli/prove_cmd.rs b/crates/nargo/src/cli/prove_cmd.rs index 2194f99794c..15e855cb7b6 100644 --- a/crates/nargo/src/cli/prove_cmd.rs +++ b/crates/nargo/src/cli/prove_cmd.rs @@ -1,19 +1,14 @@ use std::path::{Path, PathBuf}; -use acvm::acir::native_types::Witness; -use acvm::FieldElement; -use acvm::PartialWitnessGenerator; use acvm::ProofSystemCompiler; use clap::ArgMatches; -use noirc_abi::errors::AbiError; use noirc_abi::input_parser::Format; -use noirc_abi::Abi; -use super::{create_named_dir, read_inputs_from_file, write_inputs_to_file, write_to_file}; -use super::{InputMap, WitnessMap}; +use super::execute_cmd::{execute_program, extract_public_inputs}; +use super::{create_named_dir, write_inputs_to_file, write_to_file}; use crate::cli::dedup_public_input_indices; use crate::{ - constants::{PROOFS_DIR, PROOF_EXT, PROVER_INPUT_FILE, VERIFIER_INPUT_FILE}, + constants::{PROOFS_DIR, PROOF_EXT, VERIFIER_INPUT_FILE}, errors::CliError, }; @@ -26,10 +21,6 @@ pub(crate) fn run(args: ArgMatches) -> Result<(), CliError> { prove(proof_name, show_ssa, allow_warnings) } -/// In Barretenberg, the proof system adds a zero witness in the first index, -/// So when we add witness values, their index start from 1. -const WITNESS_OFFSET: u32 = 1; - fn prove(proof_name: Option<&str>, show_ssa: bool, allow_warnings: bool) -> Result<(), CliError> { let current_dir = std::env::current_dir().unwrap(); @@ -48,8 +39,19 @@ pub fn prove_with_path>( show_ssa: bool, allow_warnings: bool, ) -> Result, CliError> { - let (compiled_program, solved_witness) = - compile_circuit_and_witness(program_dir, show_ssa, allow_warnings)?; + let mut compiled_program = + super::compile_cmd::compile_circuit(program_dir.as_ref(), show_ssa, allow_warnings)?; + let (_, solved_witness) = execute_program(&program_dir, &compiled_program)?; + + // Write public inputs into Verifier.toml + let public_inputs = extract_public_inputs(&compiled_program, &solved_witness)?; + write_inputs_to_file(&public_inputs, &program_dir, VERIFIER_INPUT_FILE, Format::Toml)?; + + // Since the public outputs are added onto the public inputs list, there can be duplicates. + // We keep the duplicates for when one is encoding the return values into the Verifier.toml, + // however we must remove these duplicates when creating a proof. + compiled_program.circuit.public_inputs = + dedup_public_input_indices(compiled_program.circuit.public_inputs); let backend = crate::backends::ConcreteBackend; let proof = backend.prove_with_meta(compiled_program.circuit, solved_witness); @@ -66,96 +68,6 @@ pub fn prove_with_path>( } } -pub fn compile_circuit_and_witness>( - program_dir: P, - show_ssa: bool, - allow_unused_variables: bool, -) -> Result<(noirc_driver::CompiledProgram, WitnessMap), CliError> { - let mut compiled_program = super::compile_cmd::compile_circuit( - program_dir.as_ref(), - show_ssa, - allow_unused_variables, - )?; - let solved_witness = parse_and_solve_witness(program_dir, &compiled_program)?; - - // Since the public outputs are added into the public inputs list - // There can be duplicates. We keep the duplicates for when one is - // encoding the return values into the Verifier.toml - // However, for creating a proof, we remove these duplicates. - compiled_program.circuit.public_inputs = - dedup_public_input_indices(compiled_program.circuit.public_inputs); - - Ok((compiled_program, solved_witness)) -} - -pub fn parse_and_solve_witness>( - program_dir: P, - compiled_program: &noirc_driver::CompiledProgram, -) -> Result { - let abi = compiled_program.abi.as_ref().expect("compiled program is missing an abi object"); - // Parse the initial witness values from Prover.toml - let witness_map = - read_inputs_from_file(&program_dir, PROVER_INPUT_FILE, Format::Toml, abi.clone())?; - - // Solve the remaining witnesses - let solved_witness = solve_witness(compiled_program, &witness_map)?; - - // We allow the user to optionally not provide a value for the circuit's return value, so this may be missing from - // `witness_map`. We must then decode these from the circuit's witness values. - let encoded_public_inputs: Vec = compiled_program - .circuit - .public_inputs - .0 - .iter() - .map(|index| solved_witness[index]) - .collect(); - - let public_abi = abi.clone().public_abi(); - let public_inputs = public_abi.decode(&encoded_public_inputs)?; - - // Write public inputs into Verifier.toml - write_inputs_to_file(&public_inputs, &program_dir, VERIFIER_INPUT_FILE, Format::Toml)?; - - Ok(solved_witness) -} - -fn solve_witness( - compiled_program: &noirc_driver::CompiledProgram, - input_map: &InputMap, -) -> Result { - let abi = compiled_program.abi.as_ref().unwrap().clone(); - let mut solved_witness = - input_map_to_witness_map(abi, input_map).map_err(|error| match error { - AbiError::UndefinedInput(_) => { - CliError::Generic(format!("{error} in the {VERIFIER_INPUT_FILE}.toml file.")) - } - _ => CliError::from(error), - })?; - - let backend = crate::backends::ConcreteBackend; - backend.solve(&mut solved_witness, compiled_program.circuit.opcodes.clone())?; - - Ok(solved_witness) -} - -/// Given an InputMap and an Abi, produce a WitnessMap -/// -/// In particular, this method shows one how to associate values in a Toml/JSON -/// file with witness indices -fn input_map_to_witness_map(abi: Abi, input_map: &InputMap) -> Result { - // The ABI map is first encoded as a vector of field elements - let encoded_inputs = abi.encode(input_map, true)?; - - Ok(encoded_inputs - .into_iter() - .enumerate() - .map(|(index, witness_value)| { - let witness = Witness::new(WITNESS_OFFSET + (index as u32)); - (witness, witness_value) - }) - .collect()) -} - fn save_proof_to_dir>( proof: Vec, proof_name: &str,