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): Support extracting intermediate witnesses from a circuit using logs #961

Closed
wants to merge 8 commits into from
Closed
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ pkg/
*.tr
*.pk
*.vk
**/Verifier.toml
**/Verifier.toml
*.trace
26 changes: 11 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ edition = "2021"
rust-version = "1.66"

[workspace.dependencies]
acvm = "0.6.0"
acvm = { features = ["bn254"], git = "https://github.com/noir-lang/acvm", branch = "mv/traces2" }
arena = { path = "crates/arena" }
fm = { path = "crates/fm" }
iter-extended = { path = "crates/iter-extended" }
Expand Down
4 changes: 2 additions & 2 deletions crates/nargo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ color-eyre = "0.6.2"


# Backends
aztec_backend = { optional = true, package = "barretenberg_static_lib", git = "https://github.com/noir-lang/aztec_backend", rev = "2cb523d2ab95249157b22e198d9dcd6841c3eed8" }
aztec_wasm_backend = { optional = true, package = "barretenberg_wasm", git = "https://github.com/noir-lang/aztec_backend", rev = "2cb523d2ab95249157b22e198d9dcd6841c3eed8" }
aztec_backend = { optional = true, package = "barretenberg_static_lib", git = "https://github.com/noir-lang/aztec_backend", branch = "mv/trace" }
aztec_wasm_backend = { optional = true, package = "barretenberg_wasm", git = "https://github.com/noir-lang/aztec_backend", branch = "mv/trace" }
marlin_arkworks_backend = { optional = true, git = "https://github.com/noir-lang/marlin_arkworks_backend", rev = "144378edad821bfaa52bf2cacca8ecc87514a4fc" }

[features]
Expand Down
14 changes: 10 additions & 4 deletions crates/nargo/src/cli/execute_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use std::path::Path;

use acvm::acir::circuit::directives::SolvedLog;
use acvm::PartialWitnessGenerator;
use clap::Args;
use noirc_abi::input_parser::{Format, InputValue};
use noirc_abi::{InputMap, WitnessMap};
use noirc_driver::{CompileOptions, CompiledProgram};

use super::fs::logs::handle_logs;
use super::fs::{inputs::read_inputs_from_file, witness::save_witness_to_dir};
use super::NargoConfig;
use crate::{
Expand Down Expand Up @@ -53,7 +55,9 @@ fn execute_with_path(
let (inputs_map, _) =
read_inputs_from_file(program_dir, PROVER_INPUT_FILE, Format::Toml, &compiled_program.abi)?;

let solved_witness = execute_program(&compiled_program, &inputs_map)?;
let (solved_witness, logs) = execute_program(&compiled_program, &inputs_map)?;

handle_logs(logs, compile_options.debug_file.clone(), program_dir)?;

let public_abi = compiled_program.abi.public_abi();
let (_, return_value) = public_abi.decode(&solved_witness)?;
Expand All @@ -64,11 +68,13 @@ fn execute_with_path(
pub(crate) fn execute_program(
compiled_program: &CompiledProgram,
inputs_map: &InputMap,
) -> Result<WitnessMap, CliError> {
) -> Result<(WitnessMap, Vec<SolvedLog>), CliError> {
let mut solved_witness = compiled_program.abi.encode(inputs_map, None)?;

let mut logs = Vec::new();

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

Ok(solved_witness)
Ok((solved_witness, logs))
}
67 changes: 67 additions & 0 deletions crates/nargo/src/cli/fs/logs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use std::{
collections::HashMap,
path::{Path, PathBuf},
};

use super::{create_named_dir, write_to_file};
use crate::CliError;
use acvm::acir::circuit::directives::{SolvedLog, SolvedLogOutputInfo};
use iter_extended::vecmap;
use noirc_abi::format_field_string;

pub(crate) fn handle_logs<P: AsRef<Path>>(
logs: Vec<SolvedLog>,
debug_file_name: Option<String>,
path: P,
) -> Result<(), CliError> {
let mut traces: HashMap<String, String> = HashMap::new();
for log in logs {
let output_string = match log.output_info {
SolvedLogOutputInfo::FinalizedOutput(output_string) => output_string.clone(),
SolvedLogOutputInfo::WitnessValues(field_elements) => {
if field_elements.len() == 1 {
let element = &field_elements[0];
format_field_string(*element)
} else {
// If multiple field elements are fetched for a solved log,
// it assumed that an array is meant to be printed to standard output
//
// Collect all field element values corresponding to the given witness indices (whose values were solved during PWG)
// and convert them to hex strings.
let elements_as_hex = vecmap(field_elements, format_field_string);
let comma_separated_elements = elements_as_hex.join(", ");

"[".to_owned() + &comma_separated_elements + "]"
}
}
};
if let Some(trace_label) = log.trace_label {
// We can have multiples traces with the same label.
// Below we group traces into a singular list containing all traces with a specific label
if let Some(val) = traces.get_mut(&trace_label) {
// If there are multiples traces with the same label we want to maintain the order of the first insertion.
// Thus, we alter the value in the traces map rather than inserting a new value
*val = val.clone() + ", " + &output_string;
} else {
traces.insert(trace_label, output_string);
};
} else {
println!("{output_string}")
}
}

match debug_file_name {
Some(file_name) if !traces.is_empty() => {
let mut debug_dir = PathBuf::from(path.as_ref());
debug_dir.push("debug");
let mut trace_path = create_named_dir(debug_dir.as_ref(), "debug");
trace_path.push(file_name);
trace_path.set_extension("trace");

write_to_file(&serde_json::to_vec(&traces).unwrap(), &trace_path);
}
_ => (),
}

Ok(())
}
1 change: 1 addition & 0 deletions crates/nargo/src/cli/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::errors::CliError;

pub(super) mod inputs;
pub(super) mod keys;
pub(super) mod logs;
pub(super) mod program;
pub(super) mod proof;
pub(super) mod witness;
Expand Down
5 changes: 3 additions & 2 deletions crates/nargo/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf};

use color_eyre::eyre;

mod fs;
pub mod fs;

mod check_cmd;
mod codegen_verifier_cmd;
Expand Down Expand Up @@ -83,7 +83,8 @@ pub fn prove_and_verify(proof_name: &str, prg_dir: &Path, show_ssa: bool) -> boo
use tempdir::TempDir;

let tmp_dir = TempDir::new("p_and_v_tests").unwrap();
let compile_options = CompileOptions { show_ssa, allow_warnings: false, show_output: false };
let compile_options =
CompileOptions { show_ssa, allow_warnings: false, show_output: false, debug_file: None };

match prove_cmd::prove_with_path(
Some(proof_name.to_owned()),
Expand Down
5 changes: 4 additions & 1 deletion crates/nargo/src/cli/prove_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use clap::Args;
use noirc_abi::input_parser::Format;
use noirc_driver::CompileOptions;

use super::fs::logs::handle_logs;
use super::fs::{
inputs::{read_inputs_from_file, write_inputs_to_file},
keys::fetch_pk_and_vk,
Expand Down Expand Up @@ -94,7 +95,9 @@ pub(crate) fn prove_with_path<P: AsRef<Path>>(
&compiled_program.abi,
)?;

let solved_witness = execute_program(&compiled_program, &inputs_map)?;
let (solved_witness, logs) = execute_program(&compiled_program, &inputs_map)?;

handle_logs(logs, compile_options.debug_file.clone(), &program_dir)?;

// Write public inputs into Verifier.toml
let public_abi = compiled_program.abi.clone().public_abi();
Expand Down
16 changes: 13 additions & 3 deletions crates/nargo/src/cli/test_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};

use crate::{errors::CliError, resolver::Resolver};

use super::{add_std_lib, NargoConfig};
use super::{add_std_lib, fs::logs::handle_logs, NargoConfig};

/// Run the tests for this program
#[derive(Debug, Clone, Args)]
Expand Down Expand Up @@ -50,7 +50,7 @@ fn run_tests(
writeln!(writer, "Testing {test_name}...").expect("Failed to write to stdout");
writer.flush().ok();

match run_test(test_name, test_function, &driver, compile_options) {
match run_test(test_name, test_function, &driver, compile_options, program_dir) {
Ok(_) => {
writer.set_color(ColorSpec::new().set_fg(Some(Color::Green))).ok();
writeln!(writer, "ok").ok();
Expand Down Expand Up @@ -78,6 +78,7 @@ fn run_test(
main: FuncId,
driver: &Driver,
config: &CompileOptions,
program_dir: &Path,
) -> Result<(), CliError> {
let backend = crate::backends::ConcreteBackend;

Expand All @@ -86,16 +87,25 @@ fn run_test(
.map_err(|_| CliError::Generic(format!("Test '{test_name}' failed to compile")))?;

let mut solved_witness = BTreeMap::new();
let mut logs = Vec::new();
let test_debug_file_name =
config.debug_file.as_ref().map(|debug_file| format!("{debug_file}-{test_name}"));

// Run the backend to ensure the PWG evaluates functions like std::hash::pedersen,
// otherwise constraints involving these expressions will not error.
if let Err(error) = backend.solve(&mut solved_witness, program.circuit.opcodes) {
if let Err(error) = backend.solve(&mut solved_witness, program.circuit.opcodes, &mut logs) {
handle_logs(logs, test_debug_file_name, program_dir)?;

let writer = StandardStream::stderr(ColorChoice::Always);
let mut writer = writer.lock();
writer.set_color(ColorSpec::new().set_fg(Some(Color::Red))).ok();
writeln!(writer, "failed").ok();
writer.reset().ok();
return Err(error.into());
}
if config.show_output {
handle_logs(logs, test_debug_file_name, program_dir)?;
}

Ok(())
}
13 changes: 12 additions & 1 deletion crates/nargo/tests/test_data/strings/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ fn main(message : pub str<11>, y : Field, hex_as_string : str<4>, hex_as_field :
std::println(10);

std::println(z); // x * 5 in println not yet supported

std::trace("zz", z);

std::println(x);

let array = [1, 2, 3, 5, 8];
Expand All @@ -21,7 +24,11 @@ fn main(message : pub str<11>, y : Field, hex_as_string : str<4>, hex_as_field :

let hash = std::hash::pedersen([x]);
std::println(hash);


std::trace("hello hash", hash);
std::trace("hello hash", hex_as_field);
std::trace("hello hash", "goodbye hash");

constrain hex_as_string == "0x41";
// constrain hex_as_string != 0x41; This will fail with a type mismatch between str[4] and Field
constrain hex_as_field == 0x41;
Expand All @@ -45,8 +52,12 @@ fn test_prints_array() {

std::println(array);

std::trace("yoyo", array);

let hash = std::hash::pedersen(array);
std::println(hash);

std::trace("yoyo", hash);
}

struct Test {
Expand Down
12 changes: 12 additions & 0 deletions crates/noirc_abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,18 @@ pub fn decode_string_value(field_elements: &[FieldElement]) -> String {
final_string.to_owned()
}

/// This trims any leading zeroes.
/// A singular '0' will be prepended as well if the trimmed string has an odd length.
/// A hex string's length needs to be even to decode into bytes, as two digits correspond to
/// one byte.
pub fn format_field_string(field: FieldElement) -> String {
let mut trimmed_field = field.to_hex().trim_start_matches('0').to_owned();
if trimmed_field.len() % 2 != 0 {
trimmed_field = "0".to_owned() + &trimmed_field
};
"0x".to_owned() + &trimmed_field
}

#[cfg(test)]
mod test {
use std::collections::BTreeMap;
Expand Down
Loading