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(abi)!: add an explicit mapping from ABI params to witness indices #851

Merged
merged 28 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2f68fe8
feat: track all parameter witnesses in evaluator
TomAFrench Jan 30, 2023
73a083e
chore: use explicit witness map for encoding/decoding witness map
TomAFrench Jan 30, 2023
123f09d
chore: move `param_witnesses` to be stored within ABI
TomAFrench Feb 15, 2023
0f829ed
feat: create separate methods for encoding to a witness vs verifier i…
TomAFrench Feb 3, 2023
f3a78f6
chore: move encode_value and decode_value back inside Abi's impl
TomAFrench Feb 15, 2023
9630143
Merge branch 'master' into abi-to-witness-map
TomAFrench Feb 15, 2023
e301a6d
chore: remove clone from `encode_to_witness`
TomAFrench Feb 15, 2023
03d63c1
chore: use try_btree_map instead of unwrapping
TomAFrench Feb 15, 2023
067a711
feat: check for unexpected inputs when encoding a witness
TomAFrench Feb 15, 2023
876a938
chore: use InputMap and WitnessMap types in ABI
TomAFrench Feb 15, 2023
3262099
chore: update stale comment
TomAFrench Feb 15, 2023
dcb07a4
chore: apply input validation checks on new encoding method
TomAFrench Feb 16, 2023
d004680
chore: clippy
TomAFrench Feb 16, 2023
5b67289
Merge branch 'master' into abi-to-witness-map
TomAFrench Feb 16, 2023
c08d154
chore: remove unnecessary clone
TomAFrench Feb 16, 2023
b71b956
feat: add smoke test for abi encoding and decoding
TomAFrench Feb 16, 2023
0a5598c
chore: remove todo
TomAFrench Feb 16, 2023
f520088
chore: return abi from compile_circuit
TomAFrench Feb 16, 2023
609ffa7
chore: simplify returning `CompiledProgram`
TomAFrench Feb 16, 2023
50af426
chore: clippy
TomAFrench Feb 16, 2023
cc9a7ff
feat: return AbiError if witness value can't be found
TomAFrench Feb 16, 2023
4b94851
chore: add documentation for Abi fields
TomAFrench Feb 16, 2023
8c4125f
fix: re-add `public_inputs` field to `Evaluator`
TomAFrench Feb 16, 2023
da6ee42
chore: remove stale code from BTreeSet experiment
TomAFrench Feb 16, 2023
1f8a9ea
Merge branch 'master' into abi-to-witness-map
TomAFrench Feb 16, 2023
2117c5b
chore: fix merge issues
TomAFrench Feb 16, 2023
4f0574a
fix: re-add deleted `num_witnesses_abi_len` init
TomAFrench Feb 16, 2023
07741a6
Merge branch 'master' into abi-to-witness-map
TomAFrench Feb 16, 2023
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
66 changes: 14 additions & 52 deletions crates/nargo/src/cli/execute_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use clap::ArgMatches;
use std::path::{Path, PathBuf};

use acvm::acir::native_types::Witness;
use acvm::{FieldElement, PartialWitnessGenerator};
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_abi::{InputMap, WitnessMap, MAIN_RETURN_NAME};
use noirc_driver::CompiledProgram;

use super::{create_named_dir, read_inputs_from_file, write_to_file, InputMap, WitnessMap};
use super::{create_named_dir, read_inputs_from_file, write_to_file};
use crate::{
cli::compile_cmd::compile_circuit,
constants::{PROVER_INPUT_FILE, TARGET_DIR, WITNESS_EXT},
Expand Down Expand Up @@ -40,10 +40,6 @@ pub(crate) fn run(args: ArgMatches) -> Result<(), CliError> {
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;
kevaundray marked this conversation as resolved.
Show resolved Hide resolved

fn execute_with_path<P: AsRef<Path>>(
program_dir: P,
show_ssa: bool,
Expand All @@ -69,66 +65,32 @@ pub(crate) fn execute_program(
// Solve the remaining witnesses
let solved_witness = solve_witness(compiled_program, inputs_map)?;

let public_inputs = extract_public_inputs(compiled_program, &solved_witness)?;
let public_abi = compiled_program.abi.as_ref().unwrap().clone().public_abi();
let public_inputs = public_abi.decode_from_witness(&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 abi = compiled_program.abi.as_ref().unwrap();

let mut solved_witness = abi.encode_to_witness(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,
Expand Down
18 changes: 3 additions & 15 deletions crates/nargo/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
use acvm::{
acir::{circuit::PublicInputs, native_types::Witness},
FieldElement,
};
use acvm::{acir::circuit::PublicInputs, FieldElement};
pub use check_cmd::check_from_path;
use clap::{App, AppSettings, Arg};
use const_format::formatcp;
use git_version::git_version;
use noirc_abi::{
input_parser::{Format, InputValue},
Abi,
};
use noirc_abi::{input_parser::Format, Abi, InputMap};
use noirc_driver::Driver;
use noirc_frontend::graph::{CrateName, CrateType};
use std::{
collections::{BTreeMap, HashMap, HashSet},
collections::{HashMap, HashSet},
fs::File,
io::Write,
path::{Path, PathBuf},
Expand All @@ -36,12 +30,6 @@ mod verify_cmd;
const SHORT_GIT_HASH: &str = git_version!(prefix = "git:");
const VERSION_STRING: &str = formatcp!("{} ({})", env!("CARGO_PKG_VERSION"), SHORT_GIT_HASH);

/// A map from the fields in an TOML/JSON file which correspond to some ABI to their values
pub type InputMap = BTreeMap<String, InputValue>;

/// A map from the witnesses in a constraint system to the field element values
pub type WitnessMap = BTreeMap<Witness, FieldElement>;

pub fn start_cli() {
let allow_warnings = Arg::with_name("allow-warnings")
.long("allow-warnings")
Expand Down
8 changes: 3 additions & 5 deletions crates/nargo/src/cli/prove_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ use super::{
write_to_file,
};
use crate::{
cli::{
execute_cmd::{execute_program, extract_public_inputs},
verify_cmd::verify_proof,
},
cli::{execute_cmd::execute_program, verify_cmd::verify_proof},
constants::{PROOFS_DIR, PROOF_EXT, PROVER_INPUT_FILE, VERIFIER_INPUT_FILE},
errors::CliError,
};
Expand Down Expand Up @@ -57,7 +54,8 @@ pub fn prove_with_path<P: AsRef<Path>>(
let (_, solved_witness) = execute_program(&compiled_program, &inputs_map)?;

// Write public inputs into Verifier.toml
let public_inputs = extract_public_inputs(&compiled_program, &solved_witness)?;
let public_abi = compiled_program.abi.as_ref().unwrap().clone().public_abi();
let public_inputs = public_abi.decode_from_witness(&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.
Expand Down
2 changes: 1 addition & 1 deletion crates/nargo/src/cli/verify_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub(crate) fn verify_proof(
) -> Result<bool, CliError> {
let public_abi = compiled_program.abi.unwrap().public_abi();
let public_inputs =
public_abi.encode(&public_inputs_map, false).map_err(|error| match error {
public_abi.encode_to_array(&public_inputs_map).map_err(|error| match error {
AbiError::UndefinedInput(_) => {
CliError::Generic(format!("{error} in the {VERIFIER_INPUT_FILE}.toml file."))
}
Expand Down
103 changes: 72 additions & 31 deletions crates/noirc_abi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#![forbid(unsafe_code)]
use std::{collections::BTreeMap, convert::TryInto, str};

use acvm::FieldElement;
use acvm::{acir::native_types::Witness, FieldElement};
use errors::AbiError;
use input_parser::InputValue;
use iter_extended::vecmap;
use iter_extended::{try_btree_map, vecmap};
use serde::{Deserialize, Serialize};
// This is the ABI used to bridge the different TOML formats for the initial
// witness, the partial witness generator and the interpreter.
Expand All @@ -15,6 +15,12 @@ pub mod errors;
pub mod input_parser;
mod serialization;

/// A map from the fields in an TOML/JSON file which correspond to some ABI to their values
pub type InputMap = BTreeMap<String, InputValue>;

/// A map from the witnesses in a constraint system to the field element values
pub type WitnessMap = BTreeMap<Witness, FieldElement>;

pub const MAIN_RETURN_NAME: &str = "return";

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -120,6 +126,7 @@ impl AbiParameter {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Abi {
pub parameters: Vec<AbiParameter>,
pub param_witnesses: BTreeMap<String, Vec<Witness>>,
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
}

impl Abi {
Expand Down Expand Up @@ -149,52 +156,73 @@ impl Abi {
pub fn public_abi(self) -> Abi {
let parameters: Vec<_> =
self.parameters.into_iter().filter(|param| param.is_public()).collect();
Abi { parameters }
let param_witnesses = self
.param_witnesses
.into_iter()
.filter(|(param_name, _)| parameters.iter().any(|param| &param.name == param_name))
.collect();
Abi { parameters, param_witnesses }
}

/// Encode a set of inputs as described in the ABI into a `WitnessMap`.
pub fn encode_to_witness(&self, input_map: &InputMap) -> Result<WitnessMap, AbiError> {
// TODO: add handling for missing inputs similarly to how we do in `encode_to_array`.
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved

self.check_for_unexpected_inputs(input_map)?;

// First encode each input separately
let encoded_input_map: BTreeMap<String, Vec<FieldElement>> =
try_btree_map(input_map, |(key, value)| {
Self::encode_value(value.clone(), key).map(|v| (key.clone(), v))
})?;

// Write input field elements into witness indices specified in `self.param_witnesses`.
let witness_map = encoded_input_map
.iter()
.flat_map(|(param_name, encoded_param_fields)| {
let param_witness_indices = &self.param_witnesses[param_name];
param_witness_indices
.iter()
.zip(encoded_param_fields.iter())
.map(|(&witness, &field_element)| (witness, field_element))
})
.collect();

Ok(witness_map)
}

/// Encode a set of inputs as described in the ABI into a vector of `FieldElement`s.
pub fn encode(
self,
inputs: &BTreeMap<String, InputValue>,
skip_output: bool,
) -> Result<Vec<FieldElement>, AbiError> {
// Condition that specifies whether we should filter the "return"
// parameter. We do this in the case that it is not in the `inputs`
// map specified.
//
// See Issue #645 : Adding a `public outputs` field into acir and
// the ABI will clean up this logic
// For prosperity; the prover does not know about a `return` value
// so we skip this when encoding the ABI
let return_condition =
|param_name: &&String| !skip_output || (param_name != &MAIN_RETURN_NAME);

let parameters = self.parameters.iter().filter(|param| return_condition(&&param.name));
let param_names: Vec<&String> = parameters.clone().map(|param| &param.name).collect();
pub fn encode_to_array(self, inputs: &InputMap) -> Result<Vec<FieldElement>, AbiError> {
let mut encoded_inputs = Vec::new();

for param in parameters {
self.check_for_unexpected_inputs(inputs)?;

for param in &self.parameters {
let value = inputs
.get(&param.name)
.ok_or_else(|| AbiError::MissingParam(param.name.to_owned()))?
.clone();

if !value.matches_abi(&param.typ) {
return Err(AbiError::TypeMismatch { param: param.to_owned(), value });
return Err(AbiError::TypeMismatch { param: param.clone(), value });
}

encoded_inputs.extend(Self::encode_value(value, &param.name)?);
}

// Check that no extra witness values have been provided.
// Any missing values should be caught by the above for-loop so this only catches extra values.
if param_names.len() != inputs.len() {
Ok(encoded_inputs)
}

/// Checks that no extra witness values have been provided.
fn check_for_unexpected_inputs(&self, inputs: &InputMap) -> Result<(), AbiError> {
let param_names = self.parameter_names();
if param_names.len() < inputs.len() {
let unexpected_params: Vec<String> =
inputs.keys().filter(|param| !param_names.contains(param)).cloned().collect();
return Err(AbiError::UnexpectedParams(unexpected_params));
}

Ok(encoded_inputs)
Ok(())
}

fn encode_value(value: InputValue, param_name: &String) -> Result<Vec<FieldElement>, AbiError> {
Expand All @@ -218,11 +246,24 @@ impl Abi {
Ok(encoded_value)
}

/// Decode a `WitnessMap` into the types specified in the ABI.
pub fn decode_from_witness(&self, witness_map: &WitnessMap) -> Result<InputMap, AbiError> {
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
let public_inputs_map =
try_btree_map(self.parameters.clone(), |AbiParameter { name, typ, .. }| {
let param_witness_values =
vecmap(self.param_witnesses[&name].clone(), |witness_index| {
witness_map[&witness_index]
});

Self::decode_value(&mut param_witness_values.into_iter(), &typ)
.map(|input_value| (name.clone(), input_value))
})?;

Ok(public_inputs_map)
}

/// Decode a vector of `FieldElements` into the types specified in the ABI.
pub fn decode(
&self,
encoded_inputs: &[FieldElement],
) -> Result<BTreeMap<String, InputValue>, AbiError> {
pub fn decode_from_array(&self, encoded_inputs: &[FieldElement]) -> Result<InputMap, AbiError> {
let input_length: u32 = encoded_inputs.len().try_into().unwrap();
if input_length != self.field_count() {
return Err(AbiError::UnexpectedInputLength {
Expand Down
32 changes: 19 additions & 13 deletions crates/noirc_driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,23 +183,29 @@ impl Driver {

// Create ABI for main function
let func_meta = self.context.def_interner.function_meta(&main_function);
let abi = func_meta.into_abi(&self.context.def_interner);
let mut abi = func_meta.into_abi(&self.context.def_interner);

let program = monomorphize(main_function, &self.context.def_interner);

let blackbox_supported = acvm::default_is_black_box_supported(np_language.clone());
match create_circuit(program, np_language, blackbox_supported, show_ssa, show_output) {
Ok(circuit) => Ok(CompiledProgram { circuit, abi: Some(abi) }),
Err(err) => {
// The FileId here will be the file id of the file with the main file
// Errors will be shown at the call site without a stacktrace
let file = err.location.map(|loc| loc.file);
let files = &self.context.file_manager;
let error = reporter::report(files, &err.into(), file, allow_warnings);
reporter::finish_report(error as u32)?;
Err(ReportedError)
}
}
let (circuit, param_witnesses) =
match create_circuit(program, np_language, blackbox_supported, show_ssa, show_output) {
Ok((circuit, param_witnesses)) => (circuit, param_witnesses),
Err(err) => {
// The FileId here will be the file id of the file with the main file
// Errors will be shown at the call site without a stacktrace
let file = err.location.map(|loc| loc.file);
let files = &self.context.file_manager;
let error = reporter::report(files, &err.into(), file, allow_warnings);
reporter::finish_report(error as u32)?;
return Err(ReportedError);
}
};

// TODO: Try to avoid this hack.
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
abi.param_witnesses = param_witnesses;

Ok(CompiledProgram { circuit, abi: Some(abi) })
}

/// Returns a list of all functions in the current crate marked with #[test]
Expand Down
Loading