diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index 006e00d2d07..345e5447bf5 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -173,9 +173,9 @@ impl CrateDefMap { .value_definitions() .filter_map(|id| { id.as_function().map(|function_id| { - let attributes = interner.function_attributes(&function_id); - let is_entry_point = !attributes.has_contract_library_method() - && !attributes.is_test_function(); + let is_entry_point = interner + .function_attributes(&function_id) + .is_contract_entry_point(); ContractFunctionMeta { function_id, is_entry_point } }) }) diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index 80a0b557465..afdf65e5c5c 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -78,6 +78,8 @@ pub enum ResolverError { InvalidClosureEnvironment { typ: Type, span: Span }, #[error("{name} is private and not visible from the current module")] PrivateFunctionCalled { name: String, span: Span }, + #[error("Only sized types may be used in the entry point to a program")] + InvalidTypeForEntryPoint { span: Span }, } impl ResolverError { @@ -294,6 +296,9 @@ impl From for Diagnostic { ResolverError::PrivateFunctionCalled { span, name } => Diagnostic::simple_warning( format!("{name} is private and not visible from the current module"), format!("{name} is private"), span), + ResolverError::InvalidTypeForEntryPoint { span } => Diagnostic::simple_error( + "Only sized types may be used in the entry point to a program".to_string(), + "Slices, references, or any type containing them may not be used in main or a contract function".to_string(), span), } } } diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index ca30b31e78d..a4a85d85929 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -736,6 +736,10 @@ impl<'a> Resolver<'a> { }); } + if self.is_entry_point_function(func) { + self.verify_type_valid_for_program_input(&typ); + } + let pattern = self.resolve_pattern(pattern, DefinitionKind::Local(None)); let typ = self.resolve_type_inner(typ, &mut generics); @@ -810,6 +814,14 @@ impl<'a> Resolver<'a> { } } + fn is_entry_point_function(&self, func: &NoirFunction) -> bool { + if self.in_contract() { + func.attributes().is_contract_entry_point() + } else { + func.name() == MAIN_FUNCTION + } + } + /// True if the `distinct` keyword is allowed on a function's return type fn distinct_allowed(&self, func: &NoirFunction) -> bool { if self.in_contract() { @@ -1627,6 +1639,88 @@ impl<'a> Resolver<'a> { } HirLiteral::FmtStr(str, fmt_str_idents) } + + /// Only sized types are valid to be used as main's parameters or the parameters to a contract + /// function. If the given type is not sized (e.g. contains a slice or NamedGeneric type), an + /// error is issued. + fn verify_type_valid_for_program_input(&mut self, typ: &UnresolvedType) { + match &typ.typ { + UnresolvedTypeData::FieldElement + | UnresolvedTypeData::Integer(_, _) + | UnresolvedTypeData::Bool + | UnresolvedTypeData::Unit + | UnresolvedTypeData::Error => (), + + UnresolvedTypeData::MutableReference(_) + | UnresolvedTypeData::Function(_, _, _) + | UnresolvedTypeData::FormatString(_, _) + | UnresolvedTypeData::TraitAsType(..) + | UnresolvedTypeData::Unspecified => { + let span = typ.span.expect("Function parameters should always have spans"); + self.push_err(ResolverError::InvalidTypeForEntryPoint { span }); + } + + UnresolvedTypeData::Array(length, element) => { + if let Some(length) = length { + self.verify_type_expression_valid_for_program_input(length); + } else { + let span = typ.span.expect("Function parameters should always have spans"); + self.push_err(ResolverError::InvalidTypeForEntryPoint { span }); + } + self.verify_type_valid_for_program_input(element); + } + UnresolvedTypeData::Expression(expression) => { + self.verify_type_expression_valid_for_program_input(expression); + } + UnresolvedTypeData::String(length) => { + if let Some(length) = length { + self.verify_type_expression_valid_for_program_input(length); + } else { + let span = typ.span.expect("Function parameters should always have spans"); + self.push_err(ResolverError::InvalidTypeForEntryPoint { span }); + } + } + UnresolvedTypeData::Named(path, generics) => { + // Since the type is named, we need to resolve it to see what it actually refers to + // in order to check whether it is valid. Since resolving it may lead to a + // resolution error, we have to truncate our error count to the previous count just + // in case. This is to ensure resolution errors are not issued twice when this type + // is later resolved properly. + let error_count = self.errors.len(); + let resolved = self.resolve_named_type(path.clone(), generics.clone(), &mut vec![]); + self.errors.truncate(error_count); + + if !resolved.is_valid_for_program_input() { + let span = typ.span.expect("Function parameters should always have spans"); + self.push_err(ResolverError::InvalidTypeForEntryPoint { span }); + } + } + UnresolvedTypeData::Tuple(elements) => { + for element in elements { + self.verify_type_valid_for_program_input(element); + } + } + } + } + + fn verify_type_expression_valid_for_program_input(&mut self, expr: &UnresolvedTypeExpression) { + match expr { + UnresolvedTypeExpression::Constant(_, _) => (), + UnresolvedTypeExpression::Variable(path) => { + let error_count = self.errors.len(); + let resolved = self.resolve_named_type(path.clone(), vec![], &mut vec![]); + self.errors.truncate(error_count); + + if !resolved.is_valid_for_program_input() { + self.push_err(ResolverError::InvalidTypeForEntryPoint { span: path.span() }); + } + } + UnresolvedTypeExpression::BinaryOperation(lhs, _, rhs, _) => { + self.verify_type_expression_valid_for_program_input(lhs); + self.verify_type_expression_valid_for_program_input(rhs); + } + } + } } /// Gives an error if a user tries to create a mutable reference diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index 1f7baa0afaf..110334f331e 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -520,6 +520,46 @@ impl Type { } } } + + /// True if this type can be used as a parameter to `main` or a contract function. + /// This is only false for unsized types like slices or slices that do not make sense + /// as a program input such as named generics or mutable references. + /// + /// This function should match the same check done in `create_value_from_type` in acir_gen. + /// If this function does not catch a case where a type should be valid, it will later lead to a + /// panic in that function instead of a user-facing compiler error message. + pub(crate) fn is_valid_for_program_input(&self) -> bool { + match self { + // Type::Error is allowed as usual since it indicates an error was already issued and + // we don't need to issue further errors about this likely unresolved type + Type::FieldElement + | Type::Integer(_, _) + | Type::Bool + | Type::Unit + | Type::Constant(_) + | Type::Error => true, + + Type::FmtString(_, _) + | Type::TypeVariable(_, _) + | Type::NamedGeneric(_, _) + | Type::Function(_, _, _) + | Type::MutableReference(_) + | Type::Forall(_, _) + | Type::TraitAsType(..) + | Type::NotConstant => false, + + Type::Array(length, element) => { + length.is_valid_for_program_input() && element.is_valid_for_program_input() + } + Type::String(length) => length.is_valid_for_program_input(), + Type::Tuple(elements) => elements.iter().all(|elem| elem.is_valid_for_program_input()), + Type::Struct(definition, generics) => definition + .borrow() + .get_fields(generics) + .into_iter() + .all(|(_, field)| field.is_valid_for_program_input()), + } + } } impl std::fmt::Display for Type { diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index c91d1610f48..fe3e9476318 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -398,6 +398,13 @@ impl Attributes { matches!(self.function, Some(FunctionAttribute::Test(_))) } + /// True if these attributes mean the given function is an entry point function if it was + /// defined within a contract. Note that this does not check if the function is actually part + /// of a contract. + pub fn is_contract_entry_point(&self) -> bool { + !self.has_contract_library_method() && !self.is_test_function() + } + /// Returns note if a deprecated secondary attribute is found pub fn get_deprecated_note(&self) -> Option> { self.secondary.iter().find_map(|attr| match attr { diff --git a/tooling/backend_interface/src/cli/mod.rs b/tooling/backend_interface/src/cli/mod.rs index d1eebb1ba8e..3ea65f28103 100644 --- a/tooling/backend_interface/src/cli/mod.rs +++ b/tooling/backend_interface/src/cli/mod.rs @@ -3,17 +3,21 @@ mod contract; mod gates; mod info; +mod proof_as_fields; mod prove; mod verify; mod version; +mod vk_as_fields; mod write_vk; pub(crate) use contract::ContractCommand; pub(crate) use gates::GatesCommand; pub(crate) use info::InfoCommand; +pub(crate) use proof_as_fields::ProofAsFieldsCommand; pub(crate) use prove::ProveCommand; pub(crate) use verify::VerifyCommand; pub(crate) use version::VersionCommand; +pub(crate) use vk_as_fields::VkAsFieldsCommand; pub(crate) use write_vk::WriteVkCommand; #[test] diff --git a/tooling/backend_interface/src/cli/proof_as_fields.rs b/tooling/backend_interface/src/cli/proof_as_fields.rs new file mode 100644 index 00000000000..7eb1c1ef35c --- /dev/null +++ b/tooling/backend_interface/src/cli/proof_as_fields.rs @@ -0,0 +1,38 @@ +use std::path::{Path, PathBuf}; + +use acvm::FieldElement; + +use crate::BackendError; + +use super::string_from_stderr; + +/// `ProofAsFieldsCommand` will call the barretenberg binary +/// to split a proof into a representation as [`FieldElement`]s. +pub(crate) struct ProofAsFieldsCommand { + pub(crate) proof_path: PathBuf, + pub(crate) vk_path: PathBuf, +} + +impl ProofAsFieldsCommand { + pub(crate) fn run(self, binary_path: &Path) -> Result, BackendError> { + let mut command = std::process::Command::new(binary_path); + + command + .arg("proof_as_fields") + .arg("-p") + .arg(self.proof_path) + .arg("-k") + .arg(self.vk_path) + .arg("-o") + .arg("-"); + + let output = command.output()?; + if output.status.success() { + let string_output = String::from_utf8(output.stdout).unwrap(); + serde_json::from_str(&string_output) + .map_err(|err| BackendError::CommandFailed(err.to_string())) + } else { + Err(BackendError::CommandFailed(string_from_stderr(&output.stderr))) + } + } +} diff --git a/tooling/backend_interface/src/cli/vk_as_fields.rs b/tooling/backend_interface/src/cli/vk_as_fields.rs new file mode 100644 index 00000000000..1b0212241c4 --- /dev/null +++ b/tooling/backend_interface/src/cli/vk_as_fields.rs @@ -0,0 +1,39 @@ +use std::path::{Path, PathBuf}; + +use acvm::FieldElement; + +use crate::BackendError; + +use super::string_from_stderr; + +/// VkAsFieldsCommand will call the barretenberg binary +/// to split a verification key into a representation as [`FieldElement`]s. +/// +/// The hash of the verification key will also be returned. +pub(crate) struct VkAsFieldsCommand { + pub(crate) vk_path: PathBuf, +} + +impl VkAsFieldsCommand { + pub(crate) fn run( + self, + binary_path: &Path, + ) -> Result<(FieldElement, Vec), BackendError> { + let mut command = std::process::Command::new(binary_path); + + command.arg("vk_as_fields").arg("-k").arg(self.vk_path).arg("-o").arg("-"); + + let output = command.output()?; + if output.status.success() { + let string_output = String::from_utf8(output.stdout).unwrap(); + let mut fields: Vec = serde_json::from_str(&string_output) + .map_err(|err| BackendError::CommandFailed(err.to_string()))?; + + // The first element of this vector is the hash of the verification key, we want to split that off. + let hash = fields.remove(0); + Ok((hash, fields)) + } else { + Err(BackendError::CommandFailed(string_from_stderr(&output.stderr))) + } + } +} diff --git a/tooling/backend_interface/src/proof_system.rs b/tooling/backend_interface/src/proof_system.rs index 7d6e7d51888..c4fc6e743e5 100644 --- a/tooling/backend_interface/src/proof_system.rs +++ b/tooling/backend_interface/src/proof_system.rs @@ -7,7 +7,10 @@ use acvm::FieldElement; use acvm::Language; use tempfile::tempdir; -use crate::cli::{GatesCommand, InfoCommand, ProveCommand, VerifyCommand, WriteVkCommand}; +use crate::cli::{ + GatesCommand, InfoCommand, ProofAsFieldsCommand, ProveCommand, VerifyCommand, + VkAsFieldsCommand, WriteVkCommand, +}; use crate::{Backend, BackendError, BackendOpcodeSupport}; impl Backend { @@ -86,17 +89,9 @@ impl Backend { let temp_directory = tempdir().expect("could not create a temporary directory"); let temp_directory = temp_directory.path().to_path_buf(); - // Unlike when proving, we omit any unassigned witnesses. - // Witness values should be ordered by their index but we skip over any indices without an assignment. - let flattened_public_inputs: Vec = - public_inputs.into_iter().map(|(_, el)| el).collect(); - - let proof_with_public_inputs = bb_abstraction_leaks::prepend_public_inputs( - proof.to_vec(), - flattened_public_inputs.to_vec(), - ); - // Create a temporary file for the proof + let proof_with_public_inputs = + bb_abstraction_leaks::prepend_public_inputs(proof.to_vec(), public_inputs); let proof_path = temp_directory.join("proof").with_extension("proof"); write_to_file(&proof_with_public_inputs, &proof_path); @@ -119,6 +114,51 @@ impl Backend { VerifyCommand { crs_path: self.crs_directory(), is_recursive, proof_path, vk_path } .run(binary_path) } + + pub fn get_intermediate_proof_artifacts( + &self, + circuit: &Circuit, + proof: &[u8], + public_inputs: WitnessMap, + ) -> Result<(Vec, FieldElement, Vec), BackendError> { + let binary_path = self.assert_binary_exists()?; + self.assert_correct_version()?; + + let temp_directory = tempdir().expect("could not create a temporary directory"); + let temp_directory = temp_directory.path().to_path_buf(); + + // Create a temporary file for the circuit + // + let bytecode_path = temp_directory.join("circuit").with_extension("bytecode"); + let serialized_circuit = serialize_circuit(circuit); + write_to_file(&serialized_circuit, &bytecode_path); + + // Create the verification key and write it to the specified path + let vk_path = temp_directory.join("vk"); + + WriteVkCommand { + crs_path: self.crs_directory(), + bytecode_path, + vk_path_output: vk_path.clone(), + } + .run(binary_path)?; + + // Create a temporary file for the proof + + let proof_with_public_inputs = + bb_abstraction_leaks::prepend_public_inputs(proof.to_vec(), public_inputs); + let proof_path = temp_directory.join("proof").with_extension("proof"); + write_to_file(&proof_with_public_inputs, &proof_path); + + // Now ready to generate intermediate artifacts. + + let proof_as_fields = + ProofAsFieldsCommand { proof_path, vk_path: vk_path.clone() }.run(binary_path)?; + + let (vk_hash, vk_as_fields) = VkAsFieldsCommand { vk_path }.run(binary_path)?; + + Ok((proof_as_fields, vk_hash, vk_as_fields)) + } } pub(super) fn write_to_file(bytes: &[u8], path: &Path) -> String { diff --git a/tooling/bb_abstraction_leaks/src/lib.rs b/tooling/bb_abstraction_leaks/src/lib.rs index e0fdc467c53..fec53809ad4 100644 --- a/tooling/bb_abstraction_leaks/src/lib.rs +++ b/tooling/bb_abstraction_leaks/src/lib.rs @@ -1,7 +1,7 @@ #![warn(unused_crate_dependencies, unused_extern_crates)] #![warn(unreachable_pub)] -use acvm::FieldElement; +use acvm::{acir::native_types::WitnessMap, FieldElement}; pub const ACVM_BACKEND_BARRETENBERG: &str = "acvm-backend-barretenberg"; pub const BB_DOWNLOAD_URL: &str = env!("BB_BINARY_URL"); @@ -23,13 +23,11 @@ pub fn remove_public_inputs(num_pub_inputs: usize, proof: &[u8]) -> Vec { } /// Prepends a set of public inputs to a proof. -pub fn prepend_public_inputs(proof: Vec, public_inputs: Vec) -> Vec { - if public_inputs.is_empty() { - return proof; - } - +pub fn prepend_public_inputs(proof: Vec, public_inputs: WitnessMap) -> Vec { + // We omit any unassigned witnesses. + // Witness values should be ordered by their index but we skip over any indices without an assignment. let public_inputs_bytes = - public_inputs.into_iter().flat_map(|assignment| assignment.to_be_bytes()); + public_inputs.into_iter().flat_map(|(_, assignment)| assignment.to_be_bytes()); public_inputs_bytes.chain(proof).collect() }