Skip to content

Commit dc1d440

Browse files
author
AztecBot
committed
chore: Sync to noir-lang/noir
chore: Build nargo against Ubuntu 20 for better compatability (AztecProtocol/aztec-packages#4710) fix(dsl): Add full recursive verification test (AztecProtocol/aztec-packages#4658) feat!: autogenerate compute_note_hash_and_nullifier (AztecProtocol/aztec-packages#4610) chore: add struct for each bigint modulus (AztecProtocol/aztec-packages#4422) chore: switch noir pull to master branch (AztecProtocol/aztec-packages#4581)
1 parent 78ef013 commit dc1d440

File tree

22 files changed

+594
-131
lines changed

22 files changed

+594
-131
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
FROM rust:bookworm
1+
FROM rust:bullseye
22
WORKDIR /usr/src/noir
33
COPY . .
44
RUN ./scripts/bootstrap_native.sh
55

66
# When running the container, mount the users home directory to same location.
7-
FROM ubuntu:lunar
7+
FROM ubuntu:focal
88
# Install Tini as nargo doesn't handle signals properly.
99
# Install git as nargo needs it to clone.
1010
RUN apt-get update && apt-get install -y git tini && rm -rf /var/lib/apt/lists/* && apt-get clean

aztec_macros/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ repository.workspace = true
1111

1212
[dependencies]
1313
noirc_frontend.workspace = true
14+
noirc_errors.workspace = true
1415
iter-extended.workspace = true
1516
convert_case = "0.6.0"

aztec_macros/src/lib.rs

Lines changed: 198 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::vec;
33

44
use convert_case::{Case, Casing};
55
use iter_extended::vecmap;
6+
use noirc_frontend::hir::def_collector::dc_crate::{UnresolvedFunctions, UnresolvedTraitImpl};
7+
use noirc_frontend::hir::def_map::{LocalModuleId, ModuleId};
68
use noirc_frontend::macros_api::FieldElement;
79
use noirc_frontend::macros_api::{
810
BlockExpression, CallExpression, CastExpression, Distinctness, Expression, ExpressionKind,
@@ -16,9 +18,10 @@ use noirc_frontend::macros_api::{
1618
use noirc_frontend::macros_api::{CrateId, FileId};
1719
use noirc_frontend::macros_api::{MacroError, MacroProcessor};
1820
use noirc_frontend::macros_api::{ModuleDefId, NodeInterner, SortedModule, StructId};
19-
use noirc_frontend::node_interner::{TraitId, TraitImplKind};
21+
use noirc_frontend::node_interner::{FuncId, TraitId, TraitImplId, TraitImplKind};
2022
use noirc_frontend::Lambda;
21-
23+
use noirc_frontend::macros_api::parse_program;
24+
use noirc_errors::Location;
2225
pub struct AztecMacro;
2326

2427
impl MacroProcessor for AztecMacro {
@@ -31,6 +34,20 @@ impl MacroProcessor for AztecMacro {
3134
transform(ast, crate_id, context)
3235
}
3336

37+
fn process_unresolved_traits_impls(
38+
&self,
39+
crate_id: &CrateId,
40+
context: &mut HirContext,
41+
unresolved_traits_impls: &Vec<UnresolvedTraitImpl>,
42+
collected_functions: &mut Vec<UnresolvedFunctions>,
43+
) -> Result<(), (MacroError, FileId)> {
44+
if has_aztec_dependency(crate_id, context) {
45+
inject_compute_note_hash_and_nullifier(crate_id, context, unresolved_traits_impls, collected_functions)
46+
} else {
47+
Ok(())
48+
}
49+
}
50+
3451
fn process_typed_ast(
3552
&self,
3653
crate_id: &CrateId,
@@ -46,7 +63,6 @@ const MAX_CONTRACT_FUNCTIONS: usize = 2_usize.pow(FUNCTION_TREE_HEIGHT);
4663
#[derive(Debug, Clone)]
4764
pub enum AztecMacroError {
4865
AztecDepNotFound,
49-
ComputeNoteHashAndNullifierNotFound { span: Span },
5066
ContractHasTooManyFunctions { span: Span },
5167
ContractConstructorMissing { span: Span },
5268
UnsupportedFunctionArgumentType { span: Span, typ: UnresolvedTypeData },
@@ -63,11 +79,6 @@ impl From<AztecMacroError> for MacroError {
6379
secondary_message: None,
6480
span: None,
6581
},
66-
AztecMacroError::ComputeNoteHashAndNullifierNotFound { span } => MacroError {
67-
primary_message: "compute_note_hash_and_nullifier function not found. Define it in your contract. For more information go to https://docs.aztec.network/developers/debugging/aztecnr-errors#compute_note_hash_and_nullifier-function-not-found-define-it-in-your-contract".to_owned(),
68-
secondary_message: None,
69-
span: Some(span),
70-
},
7182
AztecMacroError::ContractHasTooManyFunctions { span } => MacroError {
7283
primary_message: format!("Contract can only have a maximum of {} functions", MAX_CONTRACT_FUNCTIONS),
7384
secondary_message: None,
@@ -313,15 +324,17 @@ fn check_for_aztec_dependency(
313324
crate_id: &CrateId,
314325
context: &HirContext,
315326
) -> Result<(), (MacroError, FileId)> {
316-
let crate_graph = &context.crate_graph[crate_id];
317-
let has_aztec_dependency = crate_graph.dependencies.iter().any(|dep| dep.as_name() == "aztec");
318-
if has_aztec_dependency {
327+
if has_aztec_dependency(crate_id, context) {
319328
Ok(())
320329
} else {
321-
Err((AztecMacroError::AztecDepNotFound.into(), crate_graph.root_file_id))
330+
Err((AztecMacroError::AztecDepNotFound.into(), context.crate_graph[crate_id].root_file_id))
322331
}
323332
}
324333

334+
fn has_aztec_dependency(crate_id: &CrateId, context: &HirContext) -> bool {
335+
context.crate_graph[crate_id].dependencies.iter().any(|dep| dep.as_name() == "aztec")
336+
}
337+
325338
// Check to see if the user has defined a storage struct
326339
fn check_for_storage_definition(module: &SortedModule) -> bool {
327340
module.types.iter().any(|r#struct| r#struct.name.0.contents == "Storage")
@@ -338,27 +351,27 @@ fn check_for_storage_implementation(module: &SortedModule) -> bool {
338351
}
339352

340353
// Check if "compute_note_hash_and_nullifier(AztecAddress,Field,Field,Field,[Field; N]) -> [Field; 4]" is defined
341-
fn check_for_compute_note_hash_and_nullifier_definition(module: &SortedModule) -> bool {
342-
module.functions.iter().any(|func| {
343-
func.def.name.0.contents == "compute_note_hash_and_nullifier"
344-
&& func.def.parameters.len() == 5
345-
&& match &func.def.parameters[0].typ.typ {
354+
fn check_for_compute_note_hash_and_nullifier_definition(functions_data: &Vec<(LocalModuleId, FuncId, NoirFunction)>, module_id: LocalModuleId) -> bool {
355+
functions_data.iter().filter(|func_data| func_data.0 == module_id).any(|func_data| {
356+
func_data.2.def.name.0.contents == "compute_note_hash_and_nullifier"
357+
&& func_data.2.def.parameters.len() == 5
358+
&& match &func_data.2.def.parameters[0].typ.typ {
346359
UnresolvedTypeData::Named(path, _, _) => path.segments.last().unwrap().0.contents == "AztecAddress",
347360
_ => false,
348361
}
349-
&& func.def.parameters[1].typ.typ == UnresolvedTypeData::FieldElement
350-
&& func.def.parameters[2].typ.typ == UnresolvedTypeData::FieldElement
351-
&& func.def.parameters[3].typ.typ == UnresolvedTypeData::FieldElement
362+
&& func_data.2.def.parameters[1].typ.typ == UnresolvedTypeData::FieldElement
363+
&& func_data.2.def.parameters[2].typ.typ == UnresolvedTypeData::FieldElement
364+
&& func_data.2.def.parameters[3].typ.typ == UnresolvedTypeData::FieldElement
352365
// checks if the 5th parameter is an array and the Box<UnresolvedType> in
353366
// Array(Option<UnresolvedTypeExpression>, Box<UnresolvedType>) contains only fields
354-
&& match &func.def.parameters[4].typ.typ {
367+
&& match &func_data.2.def.parameters[4].typ.typ {
355368
UnresolvedTypeData::Array(_, inner_type) => {
356369
matches!(inner_type.typ, UnresolvedTypeData::FieldElement)
357370
},
358371
_ => false,
359372
}
360373
// We check the return type the same way as we did the 5th parameter
361-
&& match &func.def.return_type {
374+
&& match &func_data.2.def.return_type {
362375
FunctionReturnType::Default(_) => false,
363376
FunctionReturnType::Ty(unresolved_type) => {
364377
match &unresolved_type.typ {
@@ -401,13 +414,6 @@ fn transform_module(
401414
generate_storage_implementation(module).map_err(|err| (err, crate_graph.root_file_id))?;
402415
}
403416

404-
if storage_defined && !check_for_compute_note_hash_and_nullifier_definition(module) {
405-
return Err((
406-
AztecMacroError::ComputeNoteHashAndNullifierNotFound { span: Span::default() },
407-
crate_graph.root_file_id,
408-
));
409-
}
410-
411417
for structure in module.types.iter() {
412418
if structure.attributes.iter().any(|attr| matches!(attr, SecondaryAttribute::Event)) {
413419
module.impls.push(generate_selector_impl(structure));
@@ -596,7 +602,7 @@ fn generate_storage_implementation(module: &mut SortedModule) -> Result<(), Azte
596602
/// If it does, it will insert the following things:
597603
/// - A new Input that is provided for a kernel app circuit, named: {Public/Private}ContextInputs
598604
/// - Hashes all of the function input variables
599-
/// - This instantiates a helper function
605+
/// - This instantiates a helper function
600606
fn transform_function(
601607
ty: &str,
602608
func: &mut NoirFunction,
@@ -1601,3 +1607,165 @@ fn event_signature(event: &StructType) -> String {
16011607
let fields = vecmap(event.get_fields(&[]), |(_, typ)| signature_of_type(&typ));
16021608
format!("{}({})", event.name.0.contents, fields.join(","))
16031609
}
1610+
1611+
fn inject_compute_note_hash_and_nullifier(
1612+
crate_id: &CrateId,
1613+
context: &mut HirContext,
1614+
unresolved_traits_impls: &Vec<UnresolvedTraitImpl>,
1615+
collected_functions: &mut Vec<UnresolvedFunctions>,
1616+
) -> Result<(), (MacroError, FileId)> {
1617+
// We first fetch modules in this crate which correspond to contracts, along with their file id.
1618+
let contract_module_file_ids: Vec<(LocalModuleId, FileId)> = context.def_map(crate_id).expect("ICE: Missing crate in def_map")
1619+
.modules().iter()
1620+
.filter(|(_, module)| module.is_contract)
1621+
.map(|(idx, module)| (LocalModuleId(idx), module.location.file))
1622+
.collect();
1623+
1624+
// If the current crate does not contain a contract module we simply skip it.
1625+
if contract_module_file_ids.len() == 0 {
1626+
return Ok(());
1627+
} else if contract_module_file_ids.len() != 1 {
1628+
panic!("Found multiple contracts in the same crate");
1629+
}
1630+
1631+
let (module_id, file_id) = contract_module_file_ids[0];
1632+
1633+
// If compute_note_hash_and_nullifier is already defined by the user, we skip auto-generation in order to provide an
1634+
// escape hatch for this mechanism.
1635+
// TODO(#4647): improve this diagnosis and error messaging.
1636+
if collected_functions.iter().any(|coll_funcs_data| check_for_compute_note_hash_and_nullifier_definition(&coll_funcs_data.functions, module_id)) {
1637+
return Ok(());
1638+
}
1639+
1640+
// In order to implement compute_note_hash_and_nullifier, we need to know all of the different note types the
1641+
// contract might use. These are the types that implement the NoteInterface trait, which provides the
1642+
// get_note_type_id function.
1643+
let note_types = fetch_struct_trait_impls(context, unresolved_traits_impls, "NoteInterface");
1644+
1645+
// We can now generate a version of compute_note_hash_and_nullifier tailored for the contract in this crate.
1646+
let func = generate_compute_note_hash_and_nullifier(&note_types);
1647+
1648+
// And inject the newly created function into the contract.
1649+
1650+
// TODO(#4373): We don't have a reasonable location for the source code of this autogenerated function, so we simply
1651+
// pass an empty span. This function should not produce errors anyway so this should not matter.
1652+
let location = Location::new(Span::empty(0), file_id);
1653+
1654+
// These are the same things the ModCollector does when collecting functions: we push the function to the
1655+
// NodeInterner, declare it in the module (which checks for duplicate definitions), and finally add it to the list
1656+
// on collected but unresolved functions.
1657+
1658+
let func_id = context.def_interner.push_empty_fn();
1659+
context.def_interner.push_function(func_id, &func.def, ModuleId { krate: *crate_id, local_id: module_id }, location);
1660+
1661+
context.def_map_mut(crate_id).unwrap()
1662+
.modules_mut()[module_id.0]
1663+
.declare_function(
1664+
func.name_ident().clone(), func_id
1665+
).expect(
1666+
"Failed to declare the autogenerated compute_note_hash_and_nullifier function, likely due to a duplicate definition. See https://github.com/AztecProtocol/aztec-packages/issues/4647."
1667+
);
1668+
1669+
collected_functions.iter_mut()
1670+
.find(|fns| fns.file_id == file_id).expect("ICE: no functions found in contract file")
1671+
.push_fn(module_id, func_id, func.clone());
1672+
1673+
Ok(())
1674+
}
1675+
1676+
// Fetches the name of all structs that implement trait_name, both in the current crate and all of its dependencies.
1677+
fn fetch_struct_trait_impls(
1678+
context: &mut HirContext,
1679+
unresolved_traits_impls: &Vec<UnresolvedTraitImpl>,
1680+
trait_name: &str
1681+
) -> Vec<String> {
1682+
let mut struct_typenames: Vec<String> = Vec::new();
1683+
1684+
// These structs can be declared in either external crates or the current one. External crates that contain
1685+
// dependencies have already been processed and resolved, but are available here via the NodeInterner. Note that
1686+
// crates on which the current crate does not depend on may not have been processed, and will be ignored.
1687+
for trait_impl_id in 0..(&context.def_interner.next_trait_impl_id()).0 {
1688+
let trait_impl = &context.def_interner.get_trait_implementation(TraitImplId(trait_impl_id));
1689+
1690+
if trait_impl.borrow().ident.0.contents == *trait_name {
1691+
if let Type::Struct(s, _) = &trait_impl.borrow().typ {
1692+
struct_typenames.push(s.borrow().name.0.contents.clone());
1693+
} else {
1694+
panic!("Found impl for {} on non-Struct", trait_name);
1695+
}
1696+
}
1697+
}
1698+
1699+
// This crate's traits and impls have not yet been resolved, so we look for impls in unresolved_trait_impls.
1700+
struct_typenames.extend(unresolved_traits_impls.iter()
1701+
.filter(|trait_impl|
1702+
trait_impl.trait_path.segments.last().expect("ICE: empty trait_impl path").0.contents == *trait_name)
1703+
.filter_map(|trait_impl| match &trait_impl.object_type.typ {
1704+
UnresolvedTypeData::Named(path, _, _) => Some(path.segments.last().unwrap().0.contents.clone()),
1705+
_ => None,
1706+
}));
1707+
1708+
struct_typenames
1709+
}
1710+
1711+
fn generate_compute_note_hash_and_nullifier(note_types: &Vec<String>) -> NoirFunction {
1712+
let function_source = generate_compute_note_hash_and_nullifier_source(note_types);
1713+
1714+
let (function_ast, errors) = parse_program(&function_source);
1715+
if !errors.is_empty() {
1716+
dbg!(errors.clone());
1717+
}
1718+
assert_eq!(errors.len(), 0, "Failed to parse Noir macro code. This is either a bug in the compiler or the Noir macro code");
1719+
1720+
let mut function_ast = function_ast.into_sorted();
1721+
function_ast.functions.remove(0)
1722+
}
1723+
1724+
fn generate_compute_note_hash_and_nullifier_source(note_types: &Vec<String>) -> String {
1725+
// TODO(#4649): The serialized_note parameter is a fixed-size array, but we don't know what length it should have.
1726+
// For now we hardcode it to 20, which is the same as MAX_NOTE_FIELDS_LENGTH.
1727+
1728+
if note_types.len() == 0 {
1729+
// TODO(#4520): Even if the contract does not include any notes, other parts of the stack expect for this
1730+
// function to exist, so we include a dummy version. We likely should error out here instead.
1731+
"
1732+
unconstrained fn compute_note_hash_and_nullifier(
1733+
contract_address: AztecAddress,
1734+
nonce: Field,
1735+
storage_slot: Field,
1736+
note_type_id: Field,
1737+
serialized_note: [Field; 20]
1738+
) -> pub [Field; 4] {
1739+
[0, 0, 0, 0]
1740+
}".to_string()
1741+
} else {
1742+
// For contracts that include notes we do a simple if-else chain comparing note_type_id with the different
1743+
// get_note_type_id of each of the note types.
1744+
1745+
let if_statements: Vec<String> = note_types.iter().map(|note_type| format!(
1746+
"if (note_type_id == {0}::get_note_type_id()) {{
1747+
note_utils::compute_note_hash_and_nullifier({0}::deserialize_content, note_header, serialized_note)
1748+
}}"
1749+
, note_type)).collect();
1750+
1751+
// TODO(#4520): error out on the else instead of returning a zero array
1752+
let full_if_statement = if_statements.join(" else ") + "
1753+
else {
1754+
[0, 0, 0, 0]
1755+
}";
1756+
1757+
format!("
1758+
unconstrained fn compute_note_hash_and_nullifier(
1759+
contract_address: AztecAddress,
1760+
nonce: Field,
1761+
storage_slot: Field,
1762+
note_type_id: Field,
1763+
serialized_note: [Field; 20]
1764+
) -> pub [Field; 4] {{
1765+
let note_header = NoteHeader::new(contract_address, nonce, storage_slot);
1766+
1767+
{}
1768+
}}", full_if_statement)
1769+
}
1770+
1771+
}

compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,15 @@ impl DefCollector {
256256
// Add the current crate to the collection of DefMaps
257257
context.def_maps.insert(crate_id, def_collector.def_map);
258258

259+
// TODO(#4653): generalize this function
260+
for macro_processor in &macro_processors {
261+
macro_processor.process_unresolved_traits_impls(&crate_id, context, &def_collector.collected_traits_impls, &mut def_collector.collected_functions).unwrap_or_else(
262+
|(macro_err, file_id)| {
263+
errors.push((macro_err.into(), file_id));
264+
},
265+
);
266+
}
267+
259268
inject_prelude(crate_id, context, crate_root, &mut def_collector.collected_imports);
260269
for submodule in submodules {
261270
inject_prelude(

compiler/noirc_frontend/src/hir/def_map/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ impl CrateDefMap {
135135
pub fn modules(&self) -> &Arena<ModuleData> {
136136
&self.modules
137137
}
138+
139+
pub fn modules_mut(&mut self) -> &mut Arena<ModuleData> {
140+
&mut self.modules
141+
}
142+
138143
pub fn krate(&self) -> CrateId {
139144
self.krate
140145
}

compiler/noirc_frontend/src/hir/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ impl Context<'_, '_> {
9191
self.def_maps.get(crate_id)
9292
}
9393

94+
pub fn def_map_mut(&mut self, crate_id: &CrateId) -> Option<&mut CrateDefMap> {
95+
self.def_maps.get_mut(crate_id)
96+
}
97+
9498
/// Return the CrateId for each crate that has been compiled
9599
/// successfully
96100
pub fn crates(&self) -> impl Iterator<Item = CrateId> + '_ {

0 commit comments

Comments
 (0)