diff --git a/.noir-sync-commit b/.noir-sync-commit index 90fcf3f8242..5690600dd74 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -89846cfbc4961c5258d91b5973f027be80885a20 +23a64779c7a7384b852bf1c51295f3eb77573130 diff --git a/noir/noir-repo/.github/workflows/gates_report.yml b/noir/noir-repo/.github/workflows/gates_report.yml index 3d4bef1940e..0cc94a1a04d 100644 --- a/noir/noir-repo/.github/workflows/gates_report.yml +++ b/noir/noir-repo/.github/workflows/gates_report.yml @@ -1,88 +1,94 @@ -# name: Report gates diff - -# on: -# push: -# branches: -# - master -# pull_request: - -# jobs: -# build-nargo: -# runs-on: ubuntu-latest -# strategy: -# matrix: -# target: [x86_64-unknown-linux-gnu] - -# steps: -# - name: Checkout Noir repo -# uses: actions/checkout@v4 - -# - name: Setup toolchain -# uses: dtolnay/rust-toolchain@1.74.1 - -# - uses: Swatinem/rust-cache@v2 -# with: -# key: ${{ matrix.target }} -# cache-on-failure: true -# save-if: ${{ github.event_name != 'merge_group' }} - -# - name: Build Nargo -# run: cargo build --package nargo_cli --release - -# - name: Package artifacts -# run: | -# mkdir dist -# cp ./target/release/nargo ./dist/nargo -# 7z a -ttar -so -an ./dist/* | 7z a -si ./nargo-x86_64-unknown-linux-gnu.tar.gz - -# - name: Upload artifact -# uses: actions/upload-artifact@v4 -# with: -# name: nargo -# path: ./dist/* -# retention-days: 3 - - -# compare_gas_reports: -# needs: [build-nargo] -# runs-on: ubuntu-latest -# permissions: -# pull-requests: write - -# steps: -# - uses: actions/checkout@v4 - -# - name: Download nargo binary -# uses: actions/download-artifact@v4 -# with: -# name: nargo -# path: ./nargo - -# - name: Set nargo on PATH -# run: | -# nargo_binary="${{ github.workspace }}/nargo/nargo" -# chmod +x $nargo_binary -# echo "$(dirname $nargo_binary)" >> $GITHUB_PATH -# export PATH="$PATH:$(dirname $nargo_binary)" -# nargo -V - -# - name: Generate gates report -# working-directory: ./test_programs -# run: | -# ./gates_report.sh -# mv gates_report.json ../gates_report.json +name: Report gates diff + +on: + push: + branches: + - master + pull_request: + +jobs: + build-nargo: + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64-unknown-linux-gnu] + + steps: + - name: Checkout Noir repo + uses: actions/checkout@v4 + + - name: Setup toolchain + uses: dtolnay/rust-toolchain@1.74.1 + + - uses: Swatinem/rust-cache@v2 + with: + key: ${{ matrix.target }} + cache-on-failure: true + save-if: ${{ github.event_name != 'merge_group' }} + + - name: Build Nargo + run: cargo build --package nargo_cli --release + + - name: Package artifacts + run: | + mkdir dist + cp ./target/release/nargo ./dist/nargo + 7z a -ttar -so -an ./dist/* | 7z a -si ./nargo-x86_64-unknown-linux-gnu.tar.gz + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: nargo + path: ./dist/* + retention-days: 3 + + + compare_gates_reports: + needs: [build-nargo] + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - uses: actions/checkout@v4 + + - name: Install `bb` + run: | + ./scripts/install_bb.sh + echo "$HOME/.bb/" >> $GITHUB_PATH + + - name: Download nargo binary + uses: actions/download-artifact@v4 + with: + name: nargo + path: ./nargo + + - name: Set nargo on PATH + run: | + nargo_binary="${{ github.workspace }}/nargo/nargo" + chmod +x $nargo_binary + echo "$(dirname $nargo_binary)" >> $GITHUB_PATH + export PATH="$PATH:$(dirname $nargo_binary)" + nargo -V + + - name: Generate gates report + working-directory: ./test_programs + run: | + ./rebuild.sh + ./gates_report.sh + mv gates_report.json ../gates_report.json -# - name: Compare gates reports -# id: gates_diff -# uses: vezenovm/noir-gates-diff@acf12797860f237117e15c0d6e08d64253af52b6 -# with: -# report: gates_report.json -# summaryQuantile: 0.9 # only display the 10% most significant circuit size diffs in the summary (defaults to 20%) - -# - name: Add gates diff to sticky comment -# if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' -# uses: marocchino/sticky-pull-request-comment@v2 -# with: -# # delete the comment in case changes no longer impact circuit sizes -# delete: ${{ !steps.gates_diff.outputs.markdown }} -# message: ${{ steps.gates_diff.outputs.markdown }} + - name: Compare gates reports + id: gates_diff + uses: vezenovm/noir-gates-diff@acf12797860f237117e15c0d6e08d64253af52b6 + with: + report: gates_report.json + summaryQuantile: 0.9 # only display the 10% most significant circuit size diffs in the summary (defaults to 20%) + + - name: Add gates diff to sticky comment + if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' + uses: marocchino/sticky-pull-request-comment@v2 + with: + # delete the comment in case changes no longer impact circuit sizes + delete: ${{ !steps.gates_diff.outputs.markdown }} + message: ${{ steps.gates_diff.outputs.markdown }} diff --git a/noir/noir-repo/.github/workflows/test-js-packages.yml b/noir/noir-repo/.github/workflows/test-js-packages.yml index c8a8be998e6..db162e21269 100644 --- a/noir/noir-repo/.github/workflows/test-js-packages.yml +++ b/noir/noir-repo/.github/workflows/test-js-packages.yml @@ -399,10 +399,10 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Download bb binary + - name: Install `bb` run: | - # Adds `bb` to PATH ./scripts/install_bb.sh + echo "$HOME/.bb/" >> $GITHUB_PATH - name: Download nargo binary uses: actions/download-artifact@v4 @@ -453,7 +453,7 @@ jobs: test-integration-browser: name: Integration Tests (Browser) runs-on: ubuntu-latest - needs: [build-acvm-js, build-noir-wasm, build-nargo, build-noirc-abi] + needs: [build-acvm-js, build-noir-wasm, build-noirc-abi] timeout-minutes: 30 steps: @@ -495,6 +495,46 @@ jobs: run: | yarn test:browser + test-examples: + name: Example scripts + runs-on: ubuntu-latest + needs: [build-nargo] + timeout-minutes: 30 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1.2.0 + + - name: Install `bb` + run: | + ./scripts/install_bb.sh + echo "$HOME/.bb/" >> $GITHUB_PATH + + - name: Download nargo binary + uses: actions/download-artifact@v4 + with: + name: nargo + path: ./nargo + + - name: Set nargo on PATH + run: | + nargo_binary="${{ github.workspace }}/nargo/nargo" + chmod +x $nargo_binary + echo "$(dirname $nargo_binary)" >> $GITHUB_PATH + export PATH="$PATH:$(dirname $nargo_binary)" + nargo -V + + - name: Run `prove_and_verify` + working-directory: ./examples/prove_and_verify + run: ./test.sh + + - name: Run `codegen_verifier` + working-directory: ./examples/codegen_verifier + run: ./test.sh + # This is a job which depends on all test jobs and reports the overall status. # This allows us to add/remove test jobs without having to update the required workflows. tests-end: @@ -512,6 +552,7 @@ jobs: - test-noir-codegen - test-integration-node - test-integration-browser + - test-examples steps: - name: Report overall success diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs index e6dc11dac78..6043196dfff 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs @@ -18,6 +18,12 @@ pub enum BlockType { ReturnData, } +impl BlockType { + pub fn is_databus(&self) -> bool { + matches!(self, BlockType::CallData | BlockType::ReturnData) + } +} + #[allow(clippy::large_enum_variant)] #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Opcode { diff --git a/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs b/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs index 18eefa79ac2..5fdcf54a492 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs @@ -44,8 +44,9 @@ impl UnusedMemoryOptimizer { let mut optimized_opcodes = Vec::with_capacity(self.circuit.opcodes.len()); for (idx, opcode) in self.circuit.opcodes.into_iter().enumerate() { match opcode { - Opcode::MemoryInit { block_id, .. } - if self.unused_memory_initializations.contains(&block_id) => + Opcode::MemoryInit { block_id, block_type, .. } + if !block_type.is_databus() + && self.unused_memory_initializations.contains(&block_id) => { // Drop opcode } diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs index 338511874c9..9f2b07b31fc 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs @@ -20,11 +20,11 @@ use crate::{ }; #[wasm_bindgen] -pub struct WasmBlackBoxFunctionSolver(Bn254BlackBoxSolver); +pub struct WasmBlackBoxFunctionSolver; impl WasmBlackBoxFunctionSolver { async fn initialize() -> WasmBlackBoxFunctionSolver { - WasmBlackBoxFunctionSolver(Bn254BlackBoxSolver::initialize().await) + WasmBlackBoxFunctionSolver } } @@ -47,15 +47,9 @@ pub async fn execute_circuit( ) -> Result { console_error_panic_hook::set_once(); - let solver = WasmBlackBoxFunctionSolver::initialize().await; - - let mut witness_stack = execute_program_with_native_type_return( - &solver, - program, - initial_witness, - &foreign_call_handler, - ) - .await?; + let mut witness_stack = + execute_program_with_native_type_return(program, initial_witness, &foreign_call_handler) + .await?; let witness_map = witness_stack.pop().expect("Should have at least one witness on the stack").witness; Ok(witness_map.into()) @@ -71,7 +65,7 @@ pub async fn execute_circuit( /// @returns {SolvedAndReturnWitness} The solved witness calculated by executing the circuit on the provided inputs, as well as the return witness indices as specified by the circuit. #[wasm_bindgen(js_name = executeCircuitWithReturnWitness, skip_jsdoc)] pub async fn execute_circuit_with_return_witness( - solver: &WasmBlackBoxFunctionSolver, + _solver: &WasmBlackBoxFunctionSolver, program: Vec, initial_witness: JsWitnessMap, foreign_call_handler: ForeignCallHandler, @@ -82,7 +76,6 @@ pub async fn execute_circuit_with_return_witness( .map_err(|_| JsExecutionError::new("Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), None, None))?; let mut witness_stack = execute_program_with_native_program_and_return( - solver, &program, initial_witness, &foreign_call_handler, @@ -108,20 +101,16 @@ pub async fn execute_circuit_with_return_witness( /// @returns {WitnessMap} The solved witness calculated by executing the circuit on the provided inputs. #[wasm_bindgen(js_name = executeCircuitWithBlackBoxSolver, skip_jsdoc)] pub async fn execute_circuit_with_black_box_solver( - solver: &WasmBlackBoxFunctionSolver, + _solver: &WasmBlackBoxFunctionSolver, program: Vec, initial_witness: JsWitnessMap, foreign_call_handler: ForeignCallHandler, ) -> Result { console_error_panic_hook::set_once(); - let mut witness_stack = execute_program_with_native_type_return( - solver, - program, - initial_witness, - &foreign_call_handler, - ) - .await?; + let mut witness_stack = + execute_program_with_native_type_return(program, initial_witness, &foreign_call_handler) + .await?; let witness_map = witness_stack.pop().expect("Should have at least one witness on the stack").witness; Ok(witness_map.into()) @@ -143,24 +132,19 @@ pub async fn execute_program( #[wasm_bindgen(js_name = executeProgramWithBlackBoxSolver, skip_jsdoc)] pub async fn execute_program_with_black_box_solver( - solver: &WasmBlackBoxFunctionSolver, + _solver: &WasmBlackBoxFunctionSolver, program: Vec, initial_witness: JsWitnessMap, foreign_call_executor: &ForeignCallHandler, ) -> Result { - let witness_stack = execute_program_with_native_type_return( - solver, - program, - initial_witness, - foreign_call_executor, - ) - .await?; + let witness_stack = + execute_program_with_native_type_return(program, initial_witness, foreign_call_executor) + .await?; Ok(witness_stack.into()) } async fn execute_program_with_native_type_return( - solver: &WasmBlackBoxFunctionSolver, program: Vec, initial_witness: JsWitnessMap, foreign_call_executor: &ForeignCallHandler, @@ -171,25 +155,20 @@ async fn execute_program_with_native_type_return( None, None))?; - execute_program_with_native_program_and_return( - solver, - &program, - initial_witness, - foreign_call_executor, - ) - .await + execute_program_with_native_program_and_return(&program, initial_witness, foreign_call_executor) + .await } async fn execute_program_with_native_program_and_return( - solver: &WasmBlackBoxFunctionSolver, program: &Program, initial_witness: JsWitnessMap, foreign_call_executor: &ForeignCallHandler, ) -> Result { + let blackbox_solver = Bn254BlackBoxSolver; let executor = ProgramExecutor::new( &program.functions, &program.unconstrained_functions, - &solver.0, + &blackbox_solver, foreign_call_executor, ); let witness_stack = executor.execute(initial_witness.into()).await?; diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/benches/criterion.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/benches/criterion.rs index a8fa7d8aae4..b86414423cf 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/benches/criterion.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/benches/criterion.rs @@ -15,23 +15,21 @@ fn bench_poseidon2(c: &mut Criterion) { fn bench_pedersen_commitment(c: &mut Criterion) { let inputs = [FieldElement::one(); 2]; - let solver = Bn254BlackBoxSolver::new(); c.bench_function("pedersen_commitment", |b| { - b.iter(|| solver.pedersen_commitment(black_box(&inputs), 0)) + b.iter(|| Bn254BlackBoxSolver.pedersen_commitment(black_box(&inputs), 0)) }); } fn bench_pedersen_hash(c: &mut Criterion) { let inputs = [FieldElement::one(); 2]; - let solver = Bn254BlackBoxSolver::new(); - c.bench_function("pedersen_hash", |b| b.iter(|| solver.pedersen_hash(black_box(&inputs), 0))); + c.bench_function("pedersen_hash", |b| { + b.iter(|| Bn254BlackBoxSolver.pedersen_hash(black_box(&inputs), 0)) + }); } fn bench_schnorr_verify(c: &mut Criterion) { - let solver = Bn254BlackBoxSolver::new(); - let pub_key_x = FieldElement::from_hex( "0x04b260954662e97f00cab9adb773a259097f7a274b83b113532bce27fa3fb96a", ) @@ -51,7 +49,7 @@ fn bench_schnorr_verify(c: &mut Criterion) { c.bench_function("schnorr_verify", |b| { b.iter(|| { - solver.schnorr_verify( + Bn254BlackBoxSolver.schnorr_verify( black_box(&pub_key_x), black_box(&pub_key_y), black_box(&sig_bytes), diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs index 43b86e083d5..ae5a1c3db6d 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs @@ -15,26 +15,9 @@ use ark_ec::AffineRepr; pub use embedded_curve_ops::{embedded_curve_add, multi_scalar_mul}; pub use poseidon2::poseidon2_permutation; +#[derive(Default)] pub struct Bn254BlackBoxSolver; -impl Bn254BlackBoxSolver { - pub async fn initialize() -> Bn254BlackBoxSolver { - Bn254BlackBoxSolver - } - - #[cfg(not(target_arch = "wasm32"))] - pub fn new() -> Bn254BlackBoxSolver { - Bn254BlackBoxSolver - } -} - -#[cfg(not(target_arch = "wasm32"))] -impl Default for Bn254BlackBoxSolver { - fn default() -> Self { - Self::new() - } -} - impl BlackBoxFunctionSolver for Bn254BlackBoxSolver { fn schnorr_verify( &self, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index fefe5f6f8e6..30fd83d7704 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -1728,7 +1728,7 @@ impl<'a> Context<'a> { &mut self, terminator: &TerminatorInstruction, dfg: &DataFlowGraph, - ) -> Result, InternalError> { + ) -> Result, RuntimeError> { let (return_values, call_stack) = match terminator { TerminatorInstruction::Return { return_values, call_stack } => { (return_values, call_stack) @@ -1750,6 +1750,8 @@ impl<'a> Context<'a> { if !is_databus { // We do not return value for the data bus. self.acir_context.return_var(acir_var)?; + } else { + self.check_array_is_initialized(self.data_bus.return_data.unwrap(), dfg)?; } } Ok(warnings) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs index 83b607ca976..75c95c06d09 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -324,6 +324,10 @@ impl<'context> Elaborator<'context> { }); let method_call = HirMethodCallExpression { method, object, arguments, location, generics }; + + // Desugar the method call into a normal, resolved function call + // so that the backend doesn't need to worry about methods + // TODO: update object_type here? let ((function_id, function_name), function_call) = method_call.into_function_call( &method_ref, object_type, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs index 0f9d22ca9b5..0581e7900f8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -10,7 +10,13 @@ use crate::{ UnresolvedTraitConstraint, UnresolvedTypeExpression, }, hir::{ - def_collector::{dc_crate::CompilationError, errors::DuplicateType}, + def_collector::{ + dc_crate::{ + filter_literal_globals, CompilationError, UnresolvedGlobal, UnresolvedStruct, + UnresolvedTrait, UnresolvedTypeAlias, + }, + errors::DuplicateType, + }, resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext}, scope::ScopeForest as GenericScopeForest, type_check::TypeCheckError, @@ -22,15 +28,16 @@ use crate::{ HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, HirMethodReference, HirPrefixExpression, }, + stmt::HirLetStatement, traits::TraitConstraint, }, macros_api::{ BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirExpression, HirLiteral, HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression, - MethodCallExpression, NodeInterner, NoirFunction, PrefixExpression, Statement, - StatementKind, StructId, + MethodCallExpression, NodeInterner, NoirFunction, NoirStruct, Pattern, PrefixExpression, + SecondaryAttribute, Statement, StatementKind, StructId, }, - node_interner::{DefinitionKind, DependencyId, ExprId, FuncId, StmtId, TraitId}, + node_interner::{DefinitionKind, DependencyId, ExprId, FuncId, StmtId, TraitId, TypeAliasId}, Shared, StructType, Type, TypeVariable, }; use crate::{ @@ -68,6 +75,7 @@ mod expressions; mod patterns; mod scope; mod statements; +mod traits; mod types; use fm::FileId; @@ -203,24 +211,48 @@ impl<'context> Elaborator<'context> { ) -> Vec<(CompilationError, FileId)> { let mut this = Self::new(context, crate_id); - // the resolver filters literal globals first - for global in items.globals {} + // We must first resolve and intern the globals before we can resolve any stmts inside each function. + // Each function uses its own resolver with a newly created ScopeForest, and must be resolved again to be within a function's scope + // + // Additionally, we must resolve integer globals before structs since structs may refer to + // the values of integer globals as numeric generics. + let (literal_globals, non_literal_globals) = filter_literal_globals(items.globals); - for alias in items.type_aliases {} + for global in literal_globals { + this.elaborate_global(global); + } + + for (alias_id, alias) in items.type_aliases { + this.define_type_alias(alias_id, alias); + } - for trait_ in items.traits {} + this.collect_traits(items.traits); - for struct_ in items.types {} + // Must resolve structs before we resolve globals. + this.collect_struct_definitions(items.types); + // Bind trait impls to their trait. Collect trait functions, that have a + // default implementation, which hasn't been overridden. for trait_impl in &mut items.trait_impls { this.collect_trait_impl(trait_impl); } + // Before we resolve any function symbols we must go through our impls and + // re-collect the methods within into their proper module. This cannot be + // done during def collection since we need to be able to resolve the type of + // the impl since that determines the module we should collect into. + // + // These are resolved after trait impls so that struct methods are chosen + // over trait methods if there are name conflicts. for ((typ, module), impls) in &items.impls { this.collect_impls(typ, *module, impls); } - // resolver resolves non-literal globals here + // We must wait to resolve non-literal globals until after we resolve structs since struct + // globals will need to reference the struct type they're initialized to to ensure they are valid. + for global in non_literal_globals { + this.elaborate_global(global); + } for functions in items.functions { this.elaborate_functions(functions); @@ -1100,4 +1132,101 @@ impl<'context> Elaborator<'context> { }); } } + + fn define_type_alias(&mut self, alias_id: TypeAliasId, alias: UnresolvedTypeAlias) { + self.file = alias.file_id; + self.local_module = alias.module_id; + + let generics = self.add_generics(&alias.type_alias_def.generics); + self.resolve_local_globals(); + self.current_item = Some(DependencyId::Alias(alias_id)); + let typ = self.resolve_type(alias.type_alias_def.typ); + self.interner.set_type_alias(alias_id, typ, generics); + } + + fn collect_struct_definitions(&mut self, structs: BTreeMap) { + // This is necessary to avoid cloning the entire struct map + // when adding checks after each struct field is resolved. + let struct_ids = structs.keys().copied().collect::>(); + + // Resolve each field in each struct. + // Each struct should already be present in the NodeInterner after def collection. + for (type_id, typ) in structs { + self.file = typ.file_id; + self.local_module = typ.module_id; + let (generics, fields) = self.resolve_struct_fields(typ.struct_def, type_id); + + self.interner.update_struct(type_id, |struct_def| { + struct_def.set_fields(fields); + struct_def.generics = generics; + }); + } + + // Check whether the struct fields have nested slices + // We need to check after all structs are resolved to + // make sure every struct's fields is accurately set. + for id in struct_ids { + let struct_type = self.interner.get_struct(id); + // Only handle structs without generics as any generics args will be checked + // after monomorphization when performing SSA codegen + if struct_type.borrow().generics.is_empty() { + let fields = struct_type.borrow().get_fields(&[]); + for (_, field_type) in fields.iter() { + if field_type.is_nested_slice() { + let location = struct_type.borrow().location; + self.file = location.file; + self.push_err(ResolverError::NestedSlices { span: location.span }); + } + } + } + } + } + + pub fn resolve_struct_fields( + &mut self, + unresolved: NoirStruct, + struct_id: StructId, + ) -> (Generics, Vec<(Ident, Type)>) { + let generics = self.add_generics(&unresolved.generics); + + // Check whether the struct definition has globals in the local module and add them to the scope + self.resolve_local_globals(); + + self.current_item = Some(DependencyId::Struct(struct_id)); + + self.resolving_ids.insert(struct_id); + let fields = vecmap(unresolved.fields, |(ident, typ)| (ident, self.resolve_type(typ))); + self.resolving_ids.remove(&struct_id); + + (generics, fields) + } + + fn elaborate_global(&mut self, global: UnresolvedGlobal) { + self.local_module = global.module_id; + self.file = global.file_id; + + let global_id = global.global_id; + self.current_item = Some(DependencyId::Global(global_id)); + + let definition_kind = DefinitionKind::Global(global_id); + let let_stmt = global.stmt_def; + + if !self.in_contract + && let_stmt.attributes.iter().any(|attr| matches!(attr, SecondaryAttribute::Abi(_))) + { + let span = let_stmt.pattern.span(); + self.push_err(ResolverError::AbiAttributeOutsideContract { span }); + } + + if !let_stmt.comptime && matches!(let_stmt.pattern, Pattern::Mutable(..)) { + let span = let_stmt.pattern.span(); + self.push_err(ResolverError::MutableGlobal { span }); + } + + let (let_statement, _typ) = self.elaborate_let(let_stmt); + + let statement_id = self.interner.get_global(global_id).let_statement; + self.interner.get_global_definition_mut(global_id).kind = definition_kind; + self.interner.replace_statement(statement_id, let_statement); + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs new file mode 100644 index 00000000000..e7018d900d8 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs @@ -0,0 +1,196 @@ +use std::collections::BTreeMap; + +use iter_extended::vecmap; +use noirc_errors::Location; + +use crate::{ + ast::{FunctionKind, TraitItem, UnresolvedGenerics, UnresolvedTraitConstraint}, + hir::{ + def_collector::dc_crate::UnresolvedTrait, def_map::ModuleId, + resolution::path_resolver::StandardPathResolver, + }, + hir_def::{ + function::{FuncMeta, HirFunction}, + traits::{TraitConstant, TraitFunction, TraitType}, + }, + macros_api::{ + BlockExpression, FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, + NoirFunction, Param, Pattern, UnresolvedType, Visibility, + }, + node_interner::{FuncId, TraitId}, + token::Attributes, + Generics, Type, TypeVariable, TypeVariableKind, +}; + +use super::Elaborator; + +impl<'context> Elaborator<'context> { + pub fn collect_traits(&mut self, traits: BTreeMap) { + for (trait_id, unresolved_trait) in &traits { + self.interner.push_empty_trait(*trait_id, unresolved_trait); + } + + for (trait_id, unresolved_trait) in traits { + let generics = vecmap(&unresolved_trait.trait_def.generics, |_| { + TypeVariable::unbound(self.interner.next_type_variable_id()) + }); + + // Resolve order + // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) + let _ = self.resolve_trait_types(&unresolved_trait); + // 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after) + let _ = self.resolve_trait_constants(&unresolved_trait); + // 3. Trait Methods + let methods = self.resolve_trait_methods(trait_id, &unresolved_trait, &generics); + + self.interner.update_trait(trait_id, |trait_def| { + trait_def.set_methods(methods); + trait_def.generics = generics; + }); + + // This check needs to be after the trait's methods are set since + // the interner may set `interner.ordering_type` based on the result type + // of the Cmp trait, if this is it. + if self.crate_id.is_stdlib() { + self.interner.try_add_operator_trait(trait_id); + } + } + } + + fn resolve_trait_types(&mut self, _unresolved_trait: &UnresolvedTrait) -> Vec { + // TODO + vec![] + } + + fn resolve_trait_constants( + &mut self, + _unresolved_trait: &UnresolvedTrait, + ) -> Vec { + // TODO + vec![] + } + + fn resolve_trait_methods( + &mut self, + trait_id: TraitId, + unresolved_trait: &UnresolvedTrait, + trait_generics: &Generics, + ) -> Vec { + self.local_module = unresolved_trait.module_id; + self.file = self.def_maps[&self.crate_id].file_id(unresolved_trait.module_id); + + let mut functions = vec![]; + + for item in &unresolved_trait.trait_def.items { + if let TraitItem::Function { + name, + generics, + parameters, + return_type, + where_clause, + body: _, + } = item + { + let old_generic_count = self.generics.len(); + + let the_trait = self.interner.get_trait(trait_id); + let self_typevar = the_trait.self_type_typevar.clone(); + let self_type = Type::TypeVariable(self_typevar.clone(), TypeVariableKind::Normal); + let name_span = the_trait.name.span(); + + self.add_generics(generics); + self.add_existing_generics(&unresolved_trait.trait_def.generics, trait_generics); + self.add_existing_generic("Self", name_span, self_typevar); + self.self_type = Some(self_type.clone()); + + let func_id = unresolved_trait.method_ids[&name.0.contents]; + self.resolve_trait_function( + name, + generics, + parameters, + return_type, + where_clause, + func_id, + ); + + let arguments = vecmap(parameters, |param| self.resolve_type(param.1.clone())); + let return_type = self.resolve_type(return_type.get_type().into_owned()); + + let generics = vecmap(&self.generics, |(_, type_var, _)| type_var.clone()); + + let default_impl_list: Vec<_> = unresolved_trait + .fns_with_default_impl + .functions + .iter() + .filter(|(_, _, q)| q.name() == name.0.contents) + .collect(); + + let default_impl = if default_impl_list.len() == 1 { + Some(Box::new(default_impl_list[0].2.clone())) + } else { + None + }; + + let no_environment = Box::new(Type::Unit); + let function_type = + Type::Function(arguments, Box::new(return_type), no_environment); + + functions.push(TraitFunction { + name: name.clone(), + typ: Type::Forall(generics, Box::new(function_type)), + location: Location::new(name.span(), unresolved_trait.file_id), + default_impl, + default_impl_module_id: unresolved_trait.module_id, + }); + + self.generics.truncate(old_generic_count); + } + } + functions + } + + pub fn resolve_trait_function( + &mut self, + name: &Ident, + generics: &UnresolvedGenerics, + parameters: &[(Ident, UnresolvedType)], + return_type: &FunctionReturnType, + where_clause: &[UnresolvedTraitConstraint], + func_id: FuncId, + ) { + let old_generic_count = self.generics.len(); + self.scopes.start_function(); + + // Check whether the function has globals in the local module and add them to the scope + self.resolve_local_globals(); + + self.trait_bounds = where_clause.to_vec(); + + let kind = FunctionKind::Normal; + let def = FunctionDefinition { + name: name.clone(), + attributes: Attributes::empty(), + is_unconstrained: false, + is_comptime: false, + visibility: ItemVisibility::Public, // Trait functions are always public + generics: generics.clone(), + parameters: vecmap(parameters, |(name, typ)| Param { + visibility: Visibility::Private, + pattern: Pattern::Identifier(name.clone()), + typ: typ.clone(), + span: name.span(), + }), + body: BlockExpression { statements: Vec::new() }, + span: name.span(), + where_clause: where_clause.to_vec(), + return_type: return_type.clone(), + return_visibility: Visibility::Private, + }; + + self.elaborate_function(NoirFunction { kind, def }, func_id); + let _ = self.scopes.end_function(); + // Don't check the scope tree for unused variables, they can't be used in a declaration anyway. + self.trait_bounds.clear(); + self.generics.truncate(old_generic_count); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs index 54920d5738b..3c8d805d802 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -4,7 +4,10 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; use crate::{ - ast::{BinaryOpKind, IntegerBitSize, UnresolvedTraitConstraint, UnresolvedTypeExpression}, + ast::{ + BinaryOpKind, IntegerBitSize, NoirTypeAlias, UnresolvedGenerics, UnresolvedTraitConstraint, + UnresolvedTypeExpression, + }, hir::{ def_map::ModuleDefId, resolution::{ @@ -26,7 +29,10 @@ use crate::{ HirExpression, HirLiteral, HirStatement, Path, PathKind, SecondaryAttribute, Signedness, UnaryOp, UnresolvedType, UnresolvedTypeData, }, - node_interner::{DefinitionKind, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId}, + node_interner::{ + DefinitionKind, DependencyId, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId, + TypeAliasId, + }, Generics, Shared, StructType, Type, TypeAlias, TypeBinding, TypeVariable, TypeVariableKind, }; @@ -1435,4 +1441,27 @@ impl<'context> Elaborator<'context> { } } } + + pub fn add_existing_generics(&mut self, names: &UnresolvedGenerics, generics: &Generics) { + assert_eq!(names.len(), generics.len()); + + for (name, typevar) in names.iter().zip(generics) { + self.add_existing_generic(&name.0.contents, name.0.span(), typevar.clone()); + } + } + + pub fn add_existing_generic(&mut self, name: &str, span: Span, typevar: TypeVariable) { + // Check for name collisions of this generic + let rc_name = Rc::new(name.to_owned()); + + if let Some((_, _, first_span)) = self.find_generic(&rc_name) { + self.push_err(ResolverError::DuplicateDefinition { + name: name.to_owned(), + first_span: *first_span, + second_span: span, + }); + } else { + self.generics.push((rc_name, typevar, span)); + } + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index d2eaf79b0f0..afec3839599 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -486,7 +486,7 @@ fn inject_prelude( /// Separate the globals Vec into two. The first element in the tuple will be the /// literal globals, except for arrays, and the second will be all other globals. /// We exclude array literals as they can contain complex types -fn filter_literal_globals( +pub fn filter_literal_globals( globals: Vec, ) -> (Vec, Vec) { globals.into_iter().partition(|global| match &global.stmt_def.expression.kind { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs index 9a20d0dd537..54a6af97744 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -55,9 +55,12 @@ struct LambdaContext { struct Monomorphizer<'interner> { /// Functions are keyed by their unique ID and expected type so that we can monomorphize /// a new version of the function for each type. + /// We also key by any turbofish generics that are specified. + /// This is necessary for a case where we may have a trait generic that can be instantiated + /// outside of a function parameter or return value. /// /// Using nested HashMaps here lets us avoid cloning HirTypes when calling .get() - functions: HashMap>, + functions: HashMap), FuncId>>, /// Unlike functions, locals are only keyed by their unique ID because they are never /// duplicated during monomorphization. Doing so would allow them to be used polymorphically @@ -198,10 +201,15 @@ impl<'interner> Monomorphizer<'interner> { id: node_interner::FuncId, expr_id: node_interner::ExprId, typ: &HirType, + turbofish_generics: Vec, trait_method: Option, ) -> Definition { let typ = typ.follow_bindings(); - match self.functions.get(&id).and_then(|inner_map| inner_map.get(&typ)) { + match self + .functions + .get(&id) + .and_then(|inner_map| inner_map.get(&(typ.clone(), turbofish_generics.clone()))) + { Some(id) => Definition::Function(*id), None => { // Function has not been monomorphized yet @@ -222,7 +230,8 @@ impl<'interner> Monomorphizer<'interner> { Definition::Builtin(opcode) } FunctionKind::Normal => { - let id = self.queue_function(id, expr_id, typ, trait_method); + let id = + self.queue_function(id, expr_id, typ, turbofish_generics, trait_method); Definition::Function(id) } FunctionKind::Oracle => { @@ -249,8 +258,14 @@ impl<'interner> Monomorphizer<'interner> { } /// Prerequisite: typ = typ.follow_bindings() - fn define_function(&mut self, id: node_interner::FuncId, typ: HirType, new_id: FuncId) { - self.functions.entry(id).or_default().insert(typ, new_id); + fn define_function( + &mut self, + id: node_interner::FuncId, + typ: HirType, + turbofish_generics: Vec, + new_id: FuncId, + ) { + self.functions.entry(id).or_default().insert((typ, turbofish_generics), new_id); } fn compile_main( @@ -393,7 +408,7 @@ impl<'interner> Monomorphizer<'interner> { use ast::Literal::*; let expr = match self.interner.expression(&expr) { - HirExpression::Ident(ident, _) => self.ident(ident, expr)?, + HirExpression::Ident(ident, generics) => self.ident(ident, expr, generics)?, HirExpression::Literal(HirLiteral::Str(contents)) => Literal(Str(contents)), HirExpression::Literal(HirLiteral::FmtStr(contents, idents)) => { let fields = try_vecmap(idents, |ident| self.expr(ident))?; @@ -825,6 +840,7 @@ impl<'interner> Monomorphizer<'interner> { &mut self, ident: HirIdent, expr_id: node_interner::ExprId, + generics: Option>, ) -> Result { let typ = self.interner.id_type(expr_id); @@ -838,7 +854,13 @@ impl<'interner> Monomorphizer<'interner> { let mutable = definition.mutable; let location = Some(ident.location); let name = definition.name.clone(); - let definition = self.lookup_function(*func_id, expr_id, &typ, None); + let definition = self.lookup_function( + *func_id, + expr_id, + &typ, + generics.unwrap_or_default(), + None, + ); let typ = Self::convert_type(&typ, ident.location)?; let ident = ast::Ident { location, mutable, definition, name, typ: typ.clone() }; let ident_expression = ast::Expression::Ident(ident); @@ -1063,10 +1085,11 @@ impl<'interner> Monomorphizer<'interner> { } }; - let func_id = match self.lookup_function(func_id, expr_id, &function_type, Some(method)) { - Definition::Function(func_id) => func_id, - _ => unreachable!(), - }; + let func_id = + match self.lookup_function(func_id, expr_id, &function_type, vec![], Some(method)) { + Definition::Function(func_id) => func_id, + _ => unreachable!(), + }; let the_trait = self.interner.get_trait(method.trait_id); let location = self.interner.expr_location(&expr_id); @@ -1292,10 +1315,11 @@ impl<'interner> Monomorphizer<'interner> { id: node_interner::FuncId, expr_id: node_interner::ExprId, function_type: HirType, + turbofish_generics: Vec, trait_method: Option, ) -> FuncId { let new_id = self.next_function_id(); - self.define_function(id, function_type.clone(), new_id); + self.define_function(id, function_type.clone(), turbofish_generics, new_id); let bindings = self.interner.get_instantiation_bindings(expr_id); let bindings = self.follow_bindings(bindings); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs index 7f1b67abfbd..d4145ef6a1d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -916,6 +916,13 @@ impl NodeInterner { &self.definitions[id.0] } + /// Retrieves the definition where the given id was defined. + /// This will panic if given DefinitionId::dummy_id. Use try_definition for + /// any call with a possibly undefined variable. + pub fn definition_mut(&mut self, id: DefinitionId) -> &mut DefinitionInfo { + &mut self.definitions[id.0] + } + /// Tries to retrieve the given id's definition. /// This function should be used during name resolution or type checking when we cannot be sure /// all variables have corresponding definitions (in case of an error in the user's code). @@ -997,6 +1004,11 @@ impl NodeInterner { self.definition(global.definition_id) } + pub fn get_global_definition_mut(&mut self, global_id: GlobalId) -> &mut DefinitionInfo { + let global = self.get_global(global_id); + self.definition_mut(global.definition_id) + } + pub fn get_all_globals(&self) -> &[GlobalInfo] { &self.globals } diff --git a/noir/noir-repo/cspell.json b/noir/noir-repo/cspell.json index b4f214c2f27..4497d0bf9da 100644 --- a/noir/noir-repo/cspell.json +++ b/noir/noir-repo/cspell.json @@ -19,6 +19,7 @@ "barebones", "barretenberg", "barustenberg", + "bbup", "bincode", "bindgen", "bitand", diff --git a/noir/noir-repo/docs/docs/getting_started/barretenberg/index.md b/noir/noir-repo/docs/docs/getting_started/barretenberg/index.md index f435ae151fe..0102c86770b 100644 --- a/noir/noir-repo/docs/docs/getting_started/barretenberg/index.md +++ b/noir/noir-repo/docs/docs/getting_started/barretenberg/index.md @@ -1,7 +1,6 @@ --- title: Barretenberg Installation -description: - `bb` is a command line tool for interacting with Aztec's proving backend Barretenberg. This page is a quick guide on how to install `bb` +description: bb is a command line tool for interacting with Aztec's proving backend Barretenberg. This page is a quick guide on how to install `bb` keywords: [ Barretenberg bb @@ -24,31 +23,25 @@ Open a terminal on your machine, and write: ##### macOS (Apple Silicon) ```bash -mkdir -p $HOME/.barretenberg && \ -curl -o ./barretenberg-aarch64-apple-darwin.tar.gz -L https://github.com/AztecProtocol/aztec-packages/releases/download/aztec-packages-v0.38.0/barretenberg-aarch64-apple-darwin.tar.gz && \ -tar -xvf ./barretenberg-aarch64-apple-darwin.tar.gz -C $HOME/.barretenberg/ && \ -echo 'export PATH=$PATH:$HOME/.barretenberg/' >> ~/.zshrc && \ +curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash source ~/.zshrc +bbup -v 0.41.0 ``` ##### macOS (Intel) ```bash -mkdir -p $HOME/.barretenberg && \ -curl -o ./barretenberg-x86_64-apple-darwin.tar.gz -L https://github.com/AztecProtocol/aztec-packages/releases/download/aztec-packages-v0.38.0/barretenberg-x86_64-apple-darwin.tar.gz && \ -tar -xvf ./barretenberg-x86_64-apple-darwin.tar.gz -C $HOME/.barretenberg/ && \ -echo 'export PATH=$PATH:$HOME/.barretenberg/' >> ~/.zshrc && \ +curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash source ~/.zshrc +bbup -v 0.41.0 ``` ##### Linux (Bash) ```bash -mkdir -p $HOME/.barretenberg && \ -curl -o ./barretenberg-x86_64-linux-gnu.tar.gz -L https://github.com/AztecProtocol/aztec-packages/releases/download/aztec-packages-v0.38.0/barretenberg-x86_64-linux-gnu.tar.gz && \ -tar -xvf ./barretenberg-x86_64-linux-gnu.tar.gz -C $HOME/.barretenberg/ && \ -echo -e 'export PATH=$PATH:$HOME/.barretenberg/' >> ~/.bashrc && \ +curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash source ~/.bashrc +bbup -v 0.41.0 ``` Now we're ready to start working on [our first Noir program!](../hello_noir/index.md) diff --git a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx index c2c0624dfad..789d26ce426 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx +++ b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -15,12 +15,12 @@ Verifier for EdDSA signatures fn eddsa_poseidon_verify(public_key_x : Field, public_key_y : Field, signature_s: Field, signature_r8_x: Field, signature_r8_y: Field, message: Field) -> bool ``` -It is also possible to specify the hash algorithm used for the signature by using the `eddsa_verify_with_hasher` function with a parameter implementing the Hasher trait. For instance, if you want to use Poseidon2 instead, you can do the following: +It is also possible to specify the hash algorithm used for the signature by using the `eddsa_verify` function by passing a type implementing the Hasher trait with the turbofish operator. +For instance, if you want to use Poseidon2 instead, you can do the following: ```rust use dep::std::hash::poseidon2::Poseidon2Hasher; -let mut hasher = Poseidon2Hasher::default(); -eddsa_verify_with_hasher(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg, &mut hasher); +eddsa_verify::(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg); ``` diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.29.0/how_to/how-to-solidity-verifier.md b/noir/noir-repo/docs/versioned_docs/version-v0.29.0/how_to/how-to-solidity-verifier.md index e3c7c1065da..e5ce3b7bc22 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.29.0/how_to/how-to-solidity-verifier.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.29.0/how_to/how-to-solidity-verifier.md @@ -212,13 +212,14 @@ It would be incorrect to say that a Noir proof verification costs any gas at all ZK-SNARK verification depends on some precompiled cryptographic primitives such as Elliptic Curve Pairings (if you like complex math, you can read about EC Pairings [here](https://medium.com/@VitalikButerin/exploring-elliptic-curve-pairings-c73c1864e627)). Not all EVM chains support EC Pairings, notably some of the ZK-EVMs. This means that you won't be able to use the verifier contract in all of them. -For example, chains like `zkSync ERA` and `Polygon zkEVM` do not currently support these precompiles, so proof verification via Solidity verifier contracts won't work. Here's a quick list of EVM chains that have been tested and are known to work: +For example, `Polygon zkEVM` does not currently support these precompiles, so proof verification via Solidity verifier contracts won't work. Here's a quick list of EVM chains that have been tested and are known to work: - Optimism - Arbitrum - Polygon PoS - Scroll - Celo +- zkSync If you test any other chains, please open a PR on this page to update the list. See [this doc](https://github.com/noir-lang/noir-starter/tree/main/with-foundry#testing-on-chain) for more info about testing verifier contracts on different EVM chains. diff --git a/noir/noir-repo/examples/codegen-verifier/.gitignore b/noir/noir-repo/examples/codegen_verifier/.gitignore similarity index 100% rename from noir/noir-repo/examples/codegen-verifier/.gitignore rename to noir/noir-repo/examples/codegen_verifier/.gitignore diff --git a/noir/noir-repo/examples/codegen-verifier/Nargo.toml b/noir/noir-repo/examples/codegen_verifier/Nargo.toml similarity index 100% rename from noir/noir-repo/examples/codegen-verifier/Nargo.toml rename to noir/noir-repo/examples/codegen_verifier/Nargo.toml diff --git a/noir/noir-repo/examples/codegen-verifier/Prover.toml b/noir/noir-repo/examples/codegen_verifier/Prover.toml similarity index 100% rename from noir/noir-repo/examples/codegen-verifier/Prover.toml rename to noir/noir-repo/examples/codegen_verifier/Prover.toml diff --git a/noir/noir-repo/examples/codegen-verifier/codegen_verifier.sh b/noir/noir-repo/examples/codegen_verifier/codegen_verifier.sh similarity index 100% rename from noir/noir-repo/examples/codegen-verifier/codegen_verifier.sh rename to noir/noir-repo/examples/codegen_verifier/codegen_verifier.sh diff --git a/noir/noir-repo/examples/codegen-verifier/foundry.toml b/noir/noir-repo/examples/codegen_verifier/foundry.toml similarity index 100% rename from noir/noir-repo/examples/codegen-verifier/foundry.toml rename to noir/noir-repo/examples/codegen_verifier/foundry.toml diff --git a/noir/noir-repo/examples/codegen-verifier/src/main.nr b/noir/noir-repo/examples/codegen_verifier/src/main.nr similarity index 100% rename from noir/noir-repo/examples/codegen-verifier/src/main.nr rename to noir/noir-repo/examples/codegen_verifier/src/main.nr diff --git a/noir/noir-repo/examples/codegen-verifier/test.sh b/noir/noir-repo/examples/codegen_verifier/test.sh similarity index 100% rename from noir/noir-repo/examples/codegen-verifier/test.sh rename to noir/noir-repo/examples/codegen_verifier/test.sh diff --git a/noir/noir-repo/noir_stdlib/src/eddsa.nr b/noir/noir-repo/noir_stdlib/src/eddsa.nr index 3aff6043ffd..1ab0158af8f 100644 --- a/noir/noir-repo/noir_stdlib/src/eddsa.nr +++ b/noir/noir-repo/noir_stdlib/src/eddsa.nr @@ -3,6 +3,7 @@ use crate::ec::consts::te::baby_jubjub; use crate::ec::tecurve::affine::Point as TEPoint; use crate::hash::{Hash, Hasher, BuildHasher, BuildHasherDefault}; use crate::hash::poseidon::PoseidonHasher; +use crate::default::Default; // Returns true if signature is valid pub fn eddsa_poseidon_verify( @@ -13,28 +14,25 @@ pub fn eddsa_poseidon_verify( signature_r8_y: Field, message: Field ) -> bool { - let mut hasher = PoseidonHasher::default(); - eddsa_verify_with_hasher( + eddsa_verify::( pub_key_x, pub_key_y, signature_s, signature_r8_x, signature_r8_y, - message, - &mut hasher + message ) } -pub fn eddsa_verify_with_hasher( +pub fn eddsa_verify( pub_key_x: Field, pub_key_y: Field, signature_s: Field, signature_r8_x: Field, signature_r8_y: Field, - message: Field, - hasher: &mut H + message: Field ) -> bool -where H: Hasher { +where H: Hasher + Default { // Verifies by testing: // S * B8 = R8 + H(R8, A, m) * A8 let bjj = baby_jubjub(); @@ -47,12 +45,13 @@ where H: Hasher { // Ensure S < Subgroup Order assert(signature_s.lt(bjj.suborder)); // Calculate the h = H(R, A, msg) - signature_r8_x.hash(hasher); - signature_r8_y.hash(hasher); - pub_key_x.hash(hasher); - pub_key_y.hash(hasher); - message.hash(hasher); - let hash: Field = (*hasher).finish(); + let mut hasher: H = H::default(); + hasher.write(signature_r8_x); + hasher.write(signature_r8_y); + hasher.write(pub_key_x); + hasher.write(pub_key_y); + hasher.write(message); + let hash: Field = hasher.finish(); // Calculate second part of the right side: right2 = h*8*A // Multiply by 8 by doubling 3 times. This also ensures that the result is in the subgroup. let pub_key_mul_2 = bjj.curve.add(pub_key, pub_key); diff --git a/noir/noir-repo/scripts/install_bb.sh b/noir/noir-repo/scripts/install_bb.sh index 4ee5bbbbe47..c3ed476200a 100755 --- a/noir/noir-repo/scripts/install_bb.sh +++ b/noir/noir-repo/scripts/install_bb.sh @@ -1,9 +1,11 @@ #!/bin/bash -# We use this script just for CI so we assume we're running on x86 linux +VERSION="0.41.0" -mkdir -p $HOME/.barretenberg -curl -o ./barretenberg-aarch64-apple-darwin.tar.gz -L https://github.com/AztecProtocol/aztec-packages/releases/download/aztec-packages-v0.38.0/barretenberg-aarch64-apple-darwin.tar.gz -tar -xvf ./barretenberg-aarch64-apple-darwin.tar.gz -C $HOME/.barretenberg/ -echo 'export PATH=$PATH:$HOME/.barretenberg/' >> ~/.bashrc -source ~/.bashrc +BBUP_PATH=~/.bb/bbup + +if ! [ -f $BBUP_PATH ]; then + curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash +fi + +$BBUP_PATH -v $VERSION diff --git a/noir/noir-repo/test_programs/execution_success/brillig_slice_input/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/brillig_slice_input/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/brillig_slice_input/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/brillig_slice_input/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/brillig_slice_input/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/brillig_slice_input/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/brillig_slice_input/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/brillig_slice_input/src/main.nr diff --git a/noir/noir-repo/test_programs/execution_success/regression_4383/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/regression_4383/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/regression_4383/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/regression_4383/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/regression_4383/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/regression_4383/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/regression_4383/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/regression_4383/src/main.nr diff --git a/noir/noir-repo/test_programs/execution_success/regression_4436/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/regression_4436/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/regression_4436/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/regression_4436/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/regression_4436/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/regression_4436/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/regression_4436/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/regression_4436/src/main.nr diff --git a/noir/noir-repo/test_programs/execution_success/slice_init_with_complex_type/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/slice_init_with_complex_type/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/slice_init_with_complex_type/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/slice_init_with_complex_type/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/slice_init_with_complex_type/Prover.toml b/noir/noir-repo/test_programs/compile_success_empty/slice_init_with_complex_type/Prover.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/slice_init_with_complex_type/Prover.toml rename to noir/noir-repo/test_programs/compile_success_empty/slice_init_with_complex_type/Prover.toml diff --git a/noir/noir-repo/test_programs/execution_success/slice_init_with_complex_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/slice_init_with_complex_type/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/slice_init_with_complex_type/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/slice_init_with_complex_type/src/main.nr diff --git a/noir/noir-repo/test_programs/execution_success/eddsa/src/main.nr b/noir/noir-repo/test_programs/execution_success/eddsa/src/main.nr index 012c8466f2f..ada15d5405c 100644 --- a/noir/noir-repo/test_programs/execution_success/eddsa/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/eddsa/src/main.nr @@ -2,7 +2,7 @@ use dep::std::compat; use dep::std::ec::consts::te::baby_jubjub; use dep::std::ec::tecurve::affine::Point as TEPoint; use dep::std::hash; -use dep::std::eddsa::{eddsa_to_pub, eddsa_poseidon_verify, eddsa_verify_with_hasher}; +use dep::std::eddsa::{eddsa_to_pub, eddsa_poseidon_verify, eddsa_verify}; use dep::std::hash::poseidon2::Poseidon2Hasher; fn main(msg: pub Field, _priv_key_a: Field, _priv_key_b: Field) { @@ -50,7 +50,6 @@ fn main(msg: pub Field, _priv_key_a: Field, _priv_key_b: Field) { // User A's signature over the message can't be used with another message assert(!eddsa_poseidon_verify(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg + 1)); // Using a different hash should fail - let mut hasher = Poseidon2Hasher::default(); - assert(!eddsa_verify_with_hasher(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg, &mut hasher)); + assert(!eddsa_verify::(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg)); } } diff --git a/noir/noir-repo/test_programs/execution_success/turbofish_call_func_diff_types/Nargo.toml b/noir/noir-repo/test_programs/execution_success/turbofish_call_func_diff_types/Nargo.toml new file mode 100644 index 00000000000..8624cda646b --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/turbofish_call_func_diff_types/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "turbofish_call_func_diff_types" +type = "bin" +authors = [""] +compiler_version = ">=0.29.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/turbofish_call_func_diff_types/Prover.toml b/noir/noir-repo/test_programs/execution_success/turbofish_call_func_diff_types/Prover.toml new file mode 100644 index 00000000000..f28f2f8cc48 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/turbofish_call_func_diff_types/Prover.toml @@ -0,0 +1,2 @@ +x = "5" +y = "10" diff --git a/noir/noir-repo/test_programs/execution_success/turbofish_call_func_diff_types/src/main.nr b/noir/noir-repo/test_programs/execution_success/turbofish_call_func_diff_types/src/main.nr new file mode 100644 index 00000000000..709a694e77b --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/turbofish_call_func_diff_types/src/main.nr @@ -0,0 +1,36 @@ +use dep::std::hash::Hasher; +use dep::std::hash::poseidon2::Poseidon2Hasher; +use dep::std::hash::poseidon::PoseidonHasher; + +fn main(x: Field, y: pub Field) { + let mut hasher = PoseidonHasher::default(); + hasher.write(x); + hasher.write(y); + let poseidon_expected_hash = hasher.finish(); + // Check that we get the same result when using the hasher in a + // method that purely uses trait methods without a supplied implementation. + assert(hash_simple_array::([x, y]) == poseidon_expected_hash); + + // Now let's do the same logic but with a different `Hasher` supplied to the turbofish operator + // We want to make sure that we have correctly monomorphized a function with a trait generic + // where the generic is not used on any function parameters or the return value. + let mut hasher = Poseidon2Hasher::default(); + hasher.write(x); + hasher.write(y); + let poseidon2_expected_hash = hasher.finish(); + assert(hash_simple_array::([x, y]) == poseidon2_expected_hash); +} + +fn hash_simple_array(input: [Field; 2]) -> Field where H: Hasher + Default { + // Check that we can call a trait method instead of a trait implementation + // TODO(https://github.com/noir-lang/noir/issues/5063): Need to remove the need for this type annotation + // Curently, without the annotation we will get `Expression type is ambiguous` when trying to use the `hasher` + let mut hasher: H = H::default(); + // Regression that the object is converted to a mutable reference type `&mut _`. + // Otherwise will see `Expected type &mut _, found type H`. + // Then we need to make sure to also auto dereference later in the type checking process + // when searching for a matching impl or else we will get `No matching impl found for `&mut H: Hasher` + hasher.write(input[0]); + hasher.write(input[1]); + hasher.finish() +} diff --git a/noir/noir-repo/test_programs/gates_report.sh b/noir/noir-repo/test_programs/gates_report.sh index 3b0b4d9e148..b33e81b037c 100755 --- a/noir/noir-repo/test_programs/gates_report.sh +++ b/noir/noir-repo/test_programs/gates_report.sh @@ -1,36 +1,37 @@ #!/usr/bin/env bash set -e +BACKEND=${BACKEND:-bb} + # These tests are incompatible with gas reporting excluded_dirs=("workspace" "workspace_default_member" "double_verify_nested_proof") -# These tests cause failures in CI with a stack overflow for some reason. -ci_excluded_dirs=("eddsa") - current_dir=$(pwd) -base_path="$current_dir/execution_success" -test_dirs=$(ls $base_path) +artifacts_path="$current_dir/acir_artifacts" +test_dirs=$(ls $artifacts_path) -# We generate a Noir workspace which contains all of the test cases -# This allows us to generate a gates report using `nargo info` for all of them at once. +echo "{\"programs\": [" > gates_report.json -echo "[workspace]" > Nargo.toml -echo "members = [" >> Nargo.toml +# Bound for checking where to place last parentheses +NUM_ARTIFACTS=$(ls -1q "$artifacts_path" | wc -l) -for dir in $test_dirs; do - if [[ " ${excluded_dirs[@]} " =~ " ${dir} " ]]; then - continue - fi +ITER="1" +for pathname in $test_dirs; do + ARTIFACT_NAME=$(basename "$pathname") + + GATES_INFO=$($BACKEND gates -b "$artifacts_path/$ARTIFACT_NAME/target/program.json") + MAIN_FUNCTION_INFO=$(echo $GATES_INFO | jq -r '.functions[0] | .name = "main"') + echo "{\"package_name\": \"$ARTIFACT_NAME\", \"functions\": [$MAIN_FUNCTION_INFO]" >> gates_report.json - if [[ ${CI-false} = "true" ]] && [[ " ${ci_excluded_dirs[@]} " =~ " ${dir} " ]]; then - continue + if (($ITER == $NUM_ARTIFACTS)); then + echo "}" >> gates_report.json + else + echo "}, " >> gates_report.json fi - echo " \"execution_success/$dir\"," >> Nargo.toml + ITER=$(( $ITER + 1 )) done -echo "]" >> Nargo.toml +echo "]}" >> gates_report.json -nargo info --json > gates_report.json -rm Nargo.toml diff --git a/noir/noir-repo/test_programs/rebuild.sh b/noir/noir-repo/test_programs/rebuild.sh index 4733bad10c3..cfc6479febf 100755 --- a/noir/noir-repo/test_programs/rebuild.sh +++ b/noir/noir-repo/test_programs/rebuild.sh @@ -8,6 +8,14 @@ process_dir() { local current_dir=$2 local dir_name=$(basename "$dir") + if [[ ! -f "$dir/Nargo.toml" ]]; then + # This directory isn't a proper test but just hold some stale build artifacts + # We then delete it and carry on. + rm -rf $dir + return 0 + fi + + if [[ ! -d "$current_dir/acir_artifacts/$dir_name" ]]; then mkdir -p $current_dir/acir_artifacts/$dir_name fi diff --git a/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs b/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs index 4e36dbd1f22..5f9651c9138 100644 --- a/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs +++ b/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs @@ -67,13 +67,12 @@ pub(crate) fn execute_program_from_witness( bytecode: &[u8], foreign_call_resolver_url: Option<&str>, ) -> Result { - let blackbox_solver = Bn254BlackBoxSolver::new(); let program: Program = Program::deserialize_program(bytecode) .map_err(|_| CliError::CircuitDeserializationError())?; execute_program( &program, inputs_map, - &blackbox_solver, + &Bn254BlackBoxSolver, &mut DefaultForeignCallExecutor::new(true, foreign_call_resolver_url), ) .map_err(CliError::CircuitExecutionError) diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index 646beaf0096..a031d127d82 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -34,6 +34,10 @@ pub(super) struct DebugContext<'a, B: BlackBoxFunctionSolver> { breakpoints: HashSet, source_to_opcodes: BTreeMap>, unconstrained_functions: &'a [BrilligBytecode], + + // Absolute (in terms of all the opcodes ACIR+Brillig) addresses of the ACIR + // opcodes with one additional entry for to indicate the last valid address. + acir_opcode_addresses: Vec, } impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { @@ -46,6 +50,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { unconstrained_functions: &'a [BrilligBytecode], ) -> Self { let source_to_opcodes = build_source_to_opcode_debug_mappings(debug_artifact); + let acir_opcode_addresses = build_acir_opcode_offsets(circuit, unconstrained_functions); Self { // TODO: need to handle brillig pointer in the debugger acvm: ACVM::new( @@ -61,6 +66,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { breakpoints: HashSet::new(), source_to_opcodes, unconstrained_functions, + acir_opcode_addresses, } } @@ -213,111 +219,55 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { .collect() } - fn get_opcodes_sizes(&self) -> Vec { - self.get_opcodes() - .iter() - .map(|opcode| match opcode { - Opcode::BrilligCall { id, .. } => { - self.unconstrained_functions[*id as usize].bytecode.len() - } - _ => 1, - }) - .collect() + /// Returns the absolute address of the opcode at the given location. + /// Absolute here means accounting for nested Brillig opcodes in BrilligCall + /// opcodes. + pub fn opcode_location_to_address(&self, location: &OpcodeLocation) -> usize { + match location { + OpcodeLocation::Acir(acir_index) => self.acir_opcode_addresses[*acir_index], + OpcodeLocation::Brillig { acir_index, brillig_index } => { + self.acir_opcode_addresses[*acir_index] + *brillig_index + } + } } - /// Offsets the given location by the given number of opcodes (including - /// Brillig opcodes). If the offset would move the location outside of a - /// valid circuit location, returns None and the number of remaining - /// opcodes/instructions left which span outside the valid range in the - /// second element of the returned tuple. - pub(super) fn offset_opcode_location( - &self, - location: &Option, - mut offset: i64, - ) -> (Option, i64) { - if offset == 0 { - return (*location, 0); + pub fn address_to_opcode_location(&self, address: usize) -> Option { + if address >= *self.acir_opcode_addresses.last().unwrap_or(&0) { + return None; } - let Some(location) = location else { - return (None, offset); - }; - - let (mut acir_index, mut brillig_index) = match location { - OpcodeLocation::Acir(acir_index) => (*acir_index, 0), - OpcodeLocation::Brillig { acir_index, brillig_index } => (*acir_index, *brillig_index), - }; - let opcode_sizes = self.get_opcodes_sizes(); - if offset > 0 { - while offset > 0 { - let opcode_size = opcode_sizes[acir_index] as i64 - brillig_index as i64; - if offset >= opcode_size { - acir_index += 1; - offset -= opcode_size; - brillig_index = 0; - } else { - brillig_index += offset as usize; - offset = 0; - } - if acir_index >= opcode_sizes.len() { - return (None, offset); - } + let location = match self.acir_opcode_addresses.binary_search(&address) { + Ok(found_index) => OpcodeLocation::Acir(found_index), + Err(insert_index) => { + let acir_index = insert_index - 1; + let base_offset = self.acir_opcode_addresses[acir_index]; + let brillig_index = address - base_offset; + OpcodeLocation::Brillig { acir_index, brillig_index } } - } else { - while offset < 0 { - if brillig_index > 0 { - if brillig_index > (-offset) as usize { - brillig_index -= (-offset) as usize; - offset = 0; - } else { - offset += brillig_index as i64; - brillig_index = 0; - } - } else { - if acir_index == 0 { - return (None, offset); - } - acir_index -= 1; - let opcode_size = opcode_sizes[acir_index] as i64; - if opcode_size <= -offset { - offset += opcode_size; - } else { - brillig_index = (opcode_size + offset) as usize; - offset = 0; - } - } - } - } - if brillig_index > 0 { - (Some(OpcodeLocation::Brillig { acir_index, brillig_index }), 0) - } else { - (Some(OpcodeLocation::Acir(acir_index)), 0) - } + }; + Some(location) } - pub(super) fn render_opcode_at_location(&self, location: &Option) -> String { + pub(super) fn render_opcode_at_location(&self, location: &OpcodeLocation) -> String { let opcodes = self.get_opcodes(); match location { - None => String::from("invalid"), - Some(OpcodeLocation::Acir(acir_index)) => { + OpcodeLocation::Acir(acir_index) => { let opcode = &opcodes[*acir_index]; match opcode { Opcode::BrilligCall { id, .. } => { let first_opcode = &self.unconstrained_functions[*id as usize].bytecode[0]; - format!("BRILLIG CALL {first_opcode:?}") + format!("BRILLIG {first_opcode:?}") } _ => format!("{opcode:?}"), } } - Some(OpcodeLocation::Brillig { acir_index, brillig_index }) => { - match &opcodes[*acir_index] { - Opcode::BrilligCall { id, .. } => { - let bytecode = &self.unconstrained_functions[*id as usize].bytecode; - let opcode = &bytecode[*brillig_index]; - format!(" | {opcode:?}") - } - _ => String::from(" | invalid"), + OpcodeLocation::Brillig { acir_index, brillig_index } => match &opcodes[*acir_index] { + Opcode::BrilligCall { id, .. } => { + let bytecode = &self.unconstrained_functions[*id as usize].bytecode; + let opcode = &bytecode[*brillig_index]; + format!(" | {opcode:?}") } - } + _ => String::from(" | invalid"), + }, } } @@ -640,6 +590,28 @@ fn build_source_to_opcode_debug_mappings( result } +fn build_acir_opcode_offsets( + circuit: &Circuit, + unconstrained_functions: &[BrilligBytecode], +) -> Vec { + let mut result = Vec::with_capacity(circuit.opcodes.len() + 1); + // address of the first opcode is always 0 + result.push(0); + circuit.opcodes.iter().fold(0, |acc, opcode| { + let acc = acc + + match opcode { + Opcode::BrilligCall { id, .. } => { + unconstrained_functions[*id as usize].bytecode.len() + } + _ => 1, + }; + // push the starting address of the next opcode + result.push(acc); + acc + }); + result +} + // TODO: update all debugger tests to use unconstrained brillig pointers #[cfg(test)] mod tests { @@ -851,7 +823,7 @@ mod tests { } #[test] - fn test_offset_opcode_location() { + fn test_address_opcode_location_mapping() { let brillig_bytecode = BrilligBytecode { bytecode: vec![ BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, @@ -883,85 +855,48 @@ mod tests { brillig_funcs, ); - assert_eq!(context.offset_opcode_location(&None, 0), (None, 0)); - assert_eq!(context.offset_opcode_location(&None, 2), (None, 2)); - assert_eq!(context.offset_opcode_location(&None, -2), (None, -2)); - assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 0), - (Some(OpcodeLocation::Acir(0)), 0) - ); - assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 1), - (Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }), 0) - ); - assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 2), - (Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }), 0) - ); - assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 3), - (Some(OpcodeLocation::Acir(1)), 0) - ); - assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 4), - (Some(OpcodeLocation::Acir(2)), 0) - ); - assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 5), - (Some(OpcodeLocation::Brillig { acir_index: 2, brillig_index: 1 }), 0) - ); - assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 7), - (Some(OpcodeLocation::Acir(3)), 0) - ); - assert_eq!(context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 8), (None, 0)); - assert_eq!(context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 20), (None, 12)); - assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(1)), 2), - (Some(OpcodeLocation::Brillig { acir_index: 2, brillig_index: 1 }), 0) - ); - assert_eq!(context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), -1), (None, -1)); - assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), -10), - (None, -10) - ); + let locations = + (0..=7).map(|address| context.address_to_opcode_location(address)).collect::>(); + // mapping from addresses to opcode locations assert_eq!( - context.offset_opcode_location( - &Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }), - -1 - ), - (Some(OpcodeLocation::Acir(0)), 0) - ); - assert_eq!( - context.offset_opcode_location( - &Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }), - -2 - ), - (Some(OpcodeLocation::Acir(0)), 0) - ); - assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(1)), -3), - (Some(OpcodeLocation::Acir(0)), 0) - ); - assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(2)), -4), - (Some(OpcodeLocation::Acir(0)), 0) - ); - assert_eq!( - context.offset_opcode_location( - &Some(OpcodeLocation::Brillig { acir_index: 2, brillig_index: 1 }), - -5 - ), - (Some(OpcodeLocation::Acir(0)), 0) + locations, + vec![ + Some(OpcodeLocation::Acir(0)), + Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }), + Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }), + Some(OpcodeLocation::Acir(1)), + Some(OpcodeLocation::Acir(2)), + Some(OpcodeLocation::Brillig { acir_index: 2, brillig_index: 1 }), + Some(OpcodeLocation::Brillig { acir_index: 2, brillig_index: 2 }), + Some(OpcodeLocation::Acir(3)), + ] ); + + let addresses = locations + .iter() + .flatten() + .map(|location| context.opcode_location_to_address(location)) + .collect::>(); + + // and vice-versa + assert_eq!(addresses, (0..=7).collect::>()); + + // check edge cases + assert_eq!(None, context.address_to_opcode_location(8)); assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(3)), -7), - (Some(OpcodeLocation::Acir(0)), 0) + 0, + context.opcode_location_to_address(&OpcodeLocation::Brillig { + acir_index: 0, + brillig_index: 0 + }) ); assert_eq!( - context.offset_opcode_location(&Some(OpcodeLocation::Acir(2)), -2), - (Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }), 0) + 4, + context.opcode_location_to_address(&OpcodeLocation::Brillig { + acir_index: 2, + brillig_index: 0 + }) ); } } diff --git a/noir/noir-repo/tooling/debugger/src/dap.rs b/noir/noir-repo/tooling/debugger/src/dap.rs index 060945132f5..c9b6b816a7e 100644 --- a/noir/noir-repo/tooling/debugger/src/dap.rs +++ b/noir/noir-repo/tooling/debugger/src/dap.rs @@ -1,6 +1,5 @@ use std::collections::BTreeMap; use std::io::{Read, Write}; -use std::str::FromStr; use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::acir::circuit::{Circuit, OpcodeLocation}; @@ -205,6 +204,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { Some(frame) => format!("{} {}", frame.function_name, index), None => format!("frame #{index}"), }; + let address = self.context.opcode_location_to_address(opcode_location); StackFrame { id: index as i64, @@ -218,7 +218,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { }), line: line_number as i64, column: column_number as i64, - instruction_pointer_reference: Some(opcode_location.to_string()), + instruction_pointer_reference: Some(address.to_string()), ..StackFrame::default() } }) @@ -240,50 +240,41 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { let Command::Disassemble(ref args) = req.command else { unreachable!("handle_disassemble called on a non disassemble request"); }; - let starting_ip = OpcodeLocation::from_str(args.memory_reference.as_str()).ok(); + + // we assume memory references are unsigned integers + let starting_address = args.memory_reference.parse::().unwrap_or(0); let instruction_offset = args.instruction_offset.unwrap_or(0); - let (mut opcode_location, mut invalid_count) = - self.context.offset_opcode_location(&starting_ip, instruction_offset); + + let mut address = starting_address + instruction_offset; let mut count = args.instruction_count; let mut instructions: Vec = vec![]; - // leading invalid locations (when the request goes back - // beyond the start of the program) - if invalid_count < 0 { - while invalid_count < 0 { + while count > 0 { + let opcode_location = if address >= 0 { + self.context.address_to_opcode_location(address as usize) + } else { + None + }; + + if let Some(opcode_location) = opcode_location { instructions.push(DisassembledInstruction { - address: String::from("---"), - instruction: String::from("---"), + address: address.to_string(), + // we'll use the instruction_bytes field to render the OpcodeLocation + instruction_bytes: Some(opcode_location.to_string()), + instruction: self.context.render_opcode_at_location(&opcode_location), + ..DisassembledInstruction::default() + }); + } else { + // entry for invalid location to fill up the request + instructions.push(DisassembledInstruction { + address: "---".to_owned(), + instruction: "---".to_owned(), ..DisassembledInstruction::default() }); - invalid_count += 1; - count -= 1; - } - if count > 0 { - opcode_location = Some(OpcodeLocation::Acir(0)); } - } - // the actual opcodes - while count > 0 && opcode_location.is_some() { - instructions.push(DisassembledInstruction { - address: format!("{}", opcode_location.unwrap()), - instruction: self.context.render_opcode_at_location(&opcode_location), - ..DisassembledInstruction::default() - }); - (opcode_location, _) = self.context.offset_opcode_location(&opcode_location, 1); - count -= 1; - } - // any remaining instruction count is beyond the valid opcode - // vector so return invalid placeholders - while count > 0 { - instructions.push(DisassembledInstruction { - address: String::from("---"), - instruction: String::from("---"), - ..DisassembledInstruction::default() - }); - invalid_count -= 1; count -= 1; + address += 1; } self.server.respond( @@ -418,25 +409,35 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { .breakpoints .iter() .map(|breakpoint| { - let Ok(location) = - OpcodeLocation::from_str(breakpoint.instruction_reference.as_str()) - else { + let offset = breakpoint.offset.unwrap_or(0); + let address = breakpoint.instruction_reference.parse::().unwrap_or(0) + offset; + let Ok(address): Result = address.try_into() else { return Breakpoint { verified: false, - message: Some(String::from("Missing instruction reference")), + message: Some(String::from("Invalid instruction reference/offset")), ..Breakpoint::default() }; }; - if !self.context.is_valid_opcode_location(&location) { + let Some(location) = self + .context + .address_to_opcode_location(address) + .filter(|location| self.context.is_valid_opcode_location(location)) + else { return Breakpoint { verified: false, message: Some(String::from("Invalid opcode location")), ..Breakpoint::default() }; - } + }; let id = self.get_next_breakpoint_id(); breakpoints_to_set.push((location, id)); - Breakpoint { id: Some(id), verified: true, ..Breakpoint::default() } + Breakpoint { + id: Some(id), + verified: true, + offset: Some(0), + instruction_reference: Some(address.to_string()), + ..Breakpoint::default() + } }) .collect(); @@ -496,15 +497,17 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { ..Breakpoint::default() }; } - let instruction_reference = format!("{}", location); + let breakpoint_address = self.context.opcode_location_to_address(&location); + let instruction_reference = format!("{}", breakpoint_address); let breakpoint_id = self.get_next_breakpoint_id(); breakpoints_to_set.push((location, breakpoint_id)); Breakpoint { id: Some(breakpoint_id), verified: true, source: Some(args.source.clone()), - instruction_reference: Some(instruction_reference), line: Some(line), + instruction_reference: Some(instruction_reference), + offset: Some(0), ..Breakpoint::default() } }) diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/dap_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/dap_cmd.rs index 124e30069ae..eded2bfd8d2 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/dap_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -1,5 +1,6 @@ use acvm::acir::circuit::ExpressionWidth; use acvm::acir::native_types::WitnessMap; +use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; use nargo::constants::PROVER_INPUT_FILE; use nargo::workspace::Workspace; @@ -193,11 +194,9 @@ fn loop_uninitialized_dap( Ok((compiled_program, initial_witness)) => { server.respond(req.ack()?)?; - let blackbox_solver = bn254_blackbox_solver::Bn254BlackBoxSolver::new(); - noir_debugger::run_dap_loop( server, - &blackbox_solver, + &Bn254BlackBoxSolver, compiled_program, initial_witness, )?; diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs index f950cd0405c..7865b608261 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs @@ -219,8 +219,6 @@ pub(crate) fn debug_program( compiled_program: &CompiledProgram, inputs_map: &InputMap, ) -> Result, CliError> { - let blackbox_solver = Bn254BlackBoxSolver::new(); - let initial_witness = compiled_program.abi.encode(inputs_map, None)?; let debug_artifact = DebugArtifact { @@ -230,7 +228,7 @@ pub(crate) fn debug_program( }; noir_debugger::debug_circuit( - &blackbox_solver, + &Bn254BlackBoxSolver, &compiled_program.program.functions[0], debug_artifact, initial_witness, diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs index 862a46884ef..3fcedbb8f54 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs @@ -110,14 +110,12 @@ pub(crate) fn execute_program( inputs_map: &InputMap, foreign_call_resolver_url: Option<&str>, ) -> Result { - let blackbox_solver = Bn254BlackBoxSolver::new(); - let initial_witness = compiled_program.abi.encode(inputs_map, None)?; let solved_witness_stack_err = nargo::ops::execute_program( &compiled_program.program, initial_witness, - &blackbox_solver, + &Bn254BlackBoxSolver, &mut DefaultForeignCallExecutor::new(true, foreign_call_resolver_url), ); match solved_witness_stack_err { diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs index 11cf6e22ab5..7c50e907dc9 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs @@ -94,7 +94,7 @@ pub(crate) fn run(args: InfoCommand, config: NargoConfig) -> Result<(), CliError }) .collect(); - let info_report = InfoReport { programs: program_info, contracts: Vec::new() }; + let info_report = InfoReport { programs: program_info }; if args.json { // Expose machine-readable JSON data. @@ -102,7 +102,8 @@ pub(crate) fn run(args: InfoCommand, config: NargoConfig) -> Result<(), CliError } else { // Otherwise print human-readable table. if !info_report.programs.is_empty() { - let mut program_table = table!([Fm->"Package", Fm->"Function", Fm->"Expression Width", Fm->"ACIR Opcodes", Fm->"Backend Circuit Size"]); + let mut program_table = + table!([Fm->"Package", Fm->"Function", Fm->"Expression Width", Fm->"ACIR Opcodes"]); for program_info in info_report.programs { let program_rows: Vec = program_info.into(); @@ -169,7 +170,6 @@ fn byte_index(string: &str, index: u32) -> usize { #[derive(Debug, Default, Serialize)] struct InfoReport { programs: Vec, - contracts: Vec, } #[derive(Debug, Serialize)] diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/lsp_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/lsp_cmd.rs index 45ac02ea552..9ff7a42e5f5 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/lsp_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/lsp_cmd.rs @@ -25,8 +25,7 @@ pub(crate) fn run(_args: LspCommand, _config: NargoConfig) -> Result<(), CliErro runtime.block_on(async { let (server, _) = async_lsp::MainLoop::new_server(|client| { - let blackbox_solver = Bn254BlackBoxSolver::new(); - let router = NargoLspService::new(&client, blackbox_solver); + let router = NargoLspService::new(&client, Bn254BlackBoxSolver); ServiceBuilder::new() .layer(TracingLayer::default()) diff --git a/noir/noir-repo/tooling/nargo_cli/tests/hello_world.rs b/noir/noir-repo/tooling/nargo_cli/tests/hello_world.rs index 9fcb0c873e1..6b6931542b5 100644 --- a/noir/noir-repo/tooling/nargo_cli/tests/hello_world.rs +++ b/noir/noir-repo/tooling/nargo_cli/tests/hello_world.rs @@ -34,22 +34,11 @@ fn hello_world_example() { .stdout(predicate::str::contains("Constraint system successfully built!")); project_dir.child("Prover.toml").assert(predicate::path::is_file()); - project_dir.child("Verifier.toml").assert(predicate::path::is_file()); - // `nargo prove` + // `nargo execute` project_dir.child("Prover.toml").write_str("x = 1\ny = 2").unwrap(); let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("prove"); - cmd.assert().success(); - - project_dir - .child("proofs") - .child(format!("{project_name}.proof")) - .assert(predicate::path::is_file()); - - // `nargo verify p` - let mut cmd = Command::cargo_bin("nargo").unwrap(); - cmd.arg("verify"); + cmd.arg("execute"); cmd.assert().success(); } diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json index 1a26367e3c0..10fd14a0090 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json @@ -42,7 +42,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "portal:../../../../barretenberg/ts", + "@aztec/bb.js": "0.41.0", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/noir/noir-repo/yarn.lock b/noir/noir-repo/yarn.lock index b45678f5d8b..8fb574afa30 100644 --- a/noir/noir-repo/yarn.lock +++ b/noir/noir-repo/yarn.lock @@ -221,18 +221,19 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg": - version: 0.0.0-use.local - resolution: "@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg" +"@aztec/bb.js@npm:0.41.0": + version: 0.41.0 + resolution: "@aztec/bb.js@npm:0.41.0" dependencies: comlink: ^4.4.1 commander: ^10.0.1 debug: ^4.3.4 tslib: ^2.4.0 bin: - bb.js: ./dest/node/main.js + bb.js: dest/node/main.js + checksum: e5e0095eaff3de45726366726337b131bb6ff7cf2cb53be705572c7d6715dae4c948bf86a03cfad68bc98c0c2d83e64cbe3723cc72260c8dbfa262af8cb81f9b languageName: node - linkType: soft + linkType: hard "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.11, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.8.3": version: 7.23.5 @@ -4395,7 +4396,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": "portal:../../../../barretenberg/ts" + "@aztec/bb.js": 0.41.0 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3