Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nargo): add nargo execute command #725

Merged
merged 17 commits into from
Feb 4, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -57,14 +57,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
140 changes: 140 additions & 0 deletions crates/nargo/src/cli/execute_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use std::collections::BTreeMap;
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::MAIN_RETURN_NAME;
use noirc_driver::CompiledProgram;

use crate::{
constants::{PROVER_INPUT_FILE, TARGET_DIR, WITNESS_EXT},
errors::CliError,
};

use super::{create_named_dir, read_inputs_from_file, write_to_file};

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>, BTreeMap<Witness, FieldElement>), CliError> {
let curr_dir = std::env::current_dir().unwrap();

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

execute_program(curr_dir, &compiled_program)
}

pub(crate) fn execute_program<P: AsRef<Path>>(
inputs_dir: P,
compiled_program: &CompiledProgram,
) -> Result<(Option<InputValue>, BTreeMap<Witness, FieldElement>), 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: &noirc_driver::CompiledProgram,
solved_witness: &BTreeMap<Witness, FieldElement>,
) -> Result<BTreeMap<String, InputValue>, 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: &noirc_driver::CompiledProgram,
witness_map: &BTreeMap<String, InputValue>,
) -> Result<BTreeMap<Witness, FieldElement>, CliError> {
// Note that this currently accepts an input for the return value witness.
// This doesn't really match with the expected usage of `nargo execute` as it's intended to calculate this.
// See: https://github.com/noir-lang/noir/issues/624
let abi = compiled_program.abi.as_ref().unwrap();
let encoded_inputs = abi.clone().encode(witness_map, true).map_err(|error| match error {
AbiError::UndefinedInput(_) => {
CliError::Generic(format!("{error} in the {PROVER_INPUT_FILE}.toml file."))
}
_ => CliError::from(error),
})?;

let mut solved_witness: BTreeMap<Witness, FieldElement> = encoded_inputs
.into_iter()
.enumerate()
.map(|(index, witness_value)| {
let witness = Witness::new(WITNESS_OFFSET + (index as u32));
(witness, witness_value)
})
.collect();

let backend = crate::backends::ConcreteBackend;
backend.solve(&mut solved_witness, compiled_program.circuit.opcodes.clone())?;

Ok(solved_witness)
}

pub(crate) fn save_witness_to_dir<P: AsRef<Path>>(
witness: BTreeMap<Witness, FieldElement>,
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)
}
14 changes: 14 additions & 0 deletions crates/nargo/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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 @@ -89,6 +90,17 @@ pub fn start_cli() {
.subcommand(
App::new("gates")
.about("Counts the occurences 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 @@ -103,6 +115,8 @@ 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(x) => Err(CliError::Generic(format!("unknown command : {x}"))),
_ => unreachable!(),
};
Expand Down
101 changes: 15 additions & 86 deletions crates/nargo/src/cli/prove_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
use std::{collections::BTreeMap, path::PathBuf};
use std::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, InputValue};
use noirc_abi::input_parser::Format;
use std::path::Path;

use super::{create_named_dir, read_inputs_from_file, write_inputs_to_file, write_to_file};
use super::execute_cmd::{execute_program, extract_public_inputs};
use super::{create_named_dir, write_inputs_to_file, write_to_file};
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 @@ -24,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 curr_dir = std::env::current_dir().unwrap();

Expand All @@ -46,8 +39,16 @@ 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 compiled_program =
super::compile_cmd::compile_circuit(program_dir.as_ref(), show_ssa, allow_warnings)?;
let (_, solved_witness) = execute_program(&program_dir, &compiled_program)?;

// 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 public_inputs = extract_public_inputs(&compiled_program, &solved_witness)?;

// Write public inputs into Verifier.toml
write_inputs_to_file(&public_inputs, &program_dir, VERIFIER_INPUT_FILE, Format::Toml)?;

let backend = crate::backends::ConcreteBackend;
let proof = backend.prove_with_meta(compiled_program.circuit, solved_witness);
Expand All @@ -64,78 +65,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, BTreeMap<Witness, FieldElement>), CliError> {
let 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)?;
Ok((compiled_program, solved_witness))
}

pub fn parse_and_solve_witness<P: AsRef<Path>>(
program_dir: P,
compiled_program: &noirc_driver::CompiledProgram,
) -> Result<BTreeMap<Witness, FieldElement>, 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,
witness_map: &BTreeMap<String, InputValue>,
) -> Result<BTreeMap<Witness, FieldElement>, CliError> {
let abi = compiled_program.abi.as_ref().unwrap();
let encoded_inputs = abi.clone().encode(witness_map, true).map_err(|error| match error {
AbiError::UndefinedInput(_) => {
CliError::Generic(format!("{error} in the {PROVER_INPUT_FILE}.toml file."))
}
_ => CliError::from(error),
})?;

let mut solved_witness: BTreeMap<Witness, FieldElement> = encoded_inputs
.into_iter()
.enumerate()
.map(|(index, witness_value)| {
let witness = Witness::new(WITNESS_OFFSET + (index as u32));
(witness, witness_value)
})
.collect();

let backend = crate::backends::ConcreteBackend;
backend.solve(&mut solved_witness, compiled_program.circuit.opcodes.clone())?;

Ok(solved_witness)
}

fn save_proof_to_dir<P: AsRef<Path>>(
proof: Vec<u8>,
proof_name: &str,
Expand Down