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 all 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 @@ -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