Skip to content

Commit

Permalink
Merge branch 'master' into release-please--branches--master
Browse files Browse the repository at this point in the history
  • Loading branch information
Savio-Sou authored Oct 20, 2023
2 parents 4e7c57d + 989e80d commit 7026d84
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 21 deletions.
6 changes: 3 additions & 3 deletions compiler/noirc_frontend/src/hir/def_map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
})
})
Expand Down
5 changes: 5 additions & 0 deletions compiler/noirc_frontend/src/hir/resolution/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -294,6 +296,9 @@ impl From<ResolverError> 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),
}
}
}
94 changes: 94 additions & 0 deletions compiler/noirc_frontend/src/hir/resolution/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions compiler/noirc_frontend/src/hir_def/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions compiler/noirc_frontend/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<String>> {
self.secondary.iter().find_map(|attr| match attr {
Expand Down
4 changes: 4 additions & 0 deletions tooling/backend_interface/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
38 changes: 38 additions & 0 deletions tooling/backend_interface/src/cli/proof_as_fields.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<FieldElement>, 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)))
}
}
}
39 changes: 39 additions & 0 deletions tooling/backend_interface/src/cli/vk_as_fields.rs
Original file line number Diff line number Diff line change
@@ -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<FieldElement>), 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<FieldElement> = 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)))
}
}
}
62 changes: 51 additions & 11 deletions tooling/backend_interface/src/proof_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<FieldElement> =
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);

Expand All @@ -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>, FieldElement, Vec<FieldElement>), 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 {
Expand Down
Loading

0 comments on commit 7026d84

Please sign in to comment.