Skip to content

Commit

Permalink
feat(nargo): add nargo execute command (#725)
Browse files Browse the repository at this point in the history
feat(nargo): add option to save witness to file in execute command
  • Loading branch information
TomAFrench authored Feb 4, 2023
1 parent 47682c2 commit 9d6be60
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 114 deletions.
13 changes: 5 additions & 8 deletions crates/nargo/src/cli/compile_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use std::path::PathBuf;

use acvm::acir::native_types::Witness;
use acvm::ProofSystemCompiler;

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,
};
Expand Down Expand Up @@ -54,14 +54,11 @@ pub fn generate_circuit_and_witness_to_disk<P: AsRef<Path>>(
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)
Expand Down
146 changes: 146 additions & 0 deletions crates/nargo/src/cli/execute_cmd.rs
Original file line number Diff line number Diff line change
@@ -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<InputValue>, WitnessMap), CliError> {
let current_dir = std::env::current_dir().unwrap();

let compiled_program =
super::compile_cmd::compile_circuit(&current_dir, show_ssa, allow_warnings)?;

execute_program(current_dir, &compiled_program)
}

pub(crate) fn execute_program<P: AsRef<Path>>(
inputs_dir: P,
compiled_program: &CompiledProgram,
) -> Result<(Option<InputValue>, 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<InputMap, AbiError> {
let encoded_public_inputs: Vec<FieldElement> = 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<WitnessMap, CliError> {
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<WitnessMap, AbiError> {
// 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<P: AsRef<Path>>(
witness: WitnessMap,
witness_name: &str,
witness_dir: P,
) -> Result<PathBuf, CliError> {
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)
}
17 changes: 15 additions & 2 deletions crates/nargo/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
)
Expand All @@ -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!(),
Expand Down Expand Up @@ -162,7 +175,7 @@ pub fn read_inputs_from_file<P: AsRef<Path>>(
file_name: &str,
format: Format,
abi: Abi,
) -> Result<BTreeMap<String, InputValue>, CliError> {
) -> Result<InputMap, CliError> {
let file_path = {
let mut dir_path = path.as_ref().to_path_buf();
dir_path.push(file_name);
Expand All @@ -178,7 +191,7 @@ pub fn read_inputs_from_file<P: AsRef<Path>>(
}

fn write_inputs_to_file<P: AsRef<Path>>(
w_map: &BTreeMap<String, InputValue>,
w_map: &InputMap,
path: P,
file_name: &str,
format: Format,
Expand Down
120 changes: 16 additions & 104 deletions crates/nargo/src/cli/prove_cmd.rs
Original file line number Diff line number Diff line change
@@ -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,
};

Expand All @@ -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();

Expand All @@ -48,8 +39,19 @@ pub fn prove_with_path<P: AsRef<Path>>(
show_ssa: bool,
allow_warnings: bool,
) -> Result<Option<PathBuf>, 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);
Expand All @@ -66,96 +68,6 @@ pub fn prove_with_path<P: AsRef<Path>>(
}
}

pub fn compile_circuit_and_witness<P: AsRef<Path>>(
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<P: AsRef<Path>>(
program_dir: P,
compiled_program: &noirc_driver::CompiledProgram,
) -> Result<WitnessMap, CliError> {
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<FieldElement> = 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<WitnessMap, CliError> {
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<WitnessMap, AbiError> {
// 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<P: AsRef<Path>>(
proof: Vec<u8>,
proof_name: &str,
Expand Down

0 comments on commit 9d6be60

Please sign in to comment.