diff --git a/acvm-repo/acir/acir_docs.md b/acvm-repo/acir/README.md similarity index 100% rename from acvm-repo/acir/acir_docs.md rename to acvm-repo/acir/README.md diff --git a/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs b/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs index 5d19f9629ba..ecabd98b3b1 100644 --- a/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs +++ b/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs @@ -3,8 +3,7 @@ use acir::{ opcodes::{BlackBoxFuncCall, FunctionInput}, Circuit, Opcode, }, - native_types::{Expression, Witness}, - FieldElement, + native_types::Witness, }; use std::collections::{BTreeMap, HashSet}; @@ -105,9 +104,11 @@ impl RangeOptimizer { let mut new_order_list = Vec::with_capacity(order_list.len()); let mut optimized_opcodes = Vec::with_capacity(self.circuit.opcodes.len()); for (idx, opcode) in self.circuit.opcodes.into_iter().enumerate() { - let (witness, num_bits) = match extract_range_opcode(&opcode) { - Some(range_opcode) => range_opcode, - None => { + let (witness, num_bits) = match &opcode { + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { input }) => { + (input.witness, input.num_bits) + } + _ => { // If its not the range opcode, add it to the opcode // list and continue; optimized_opcodes.push(opcode); @@ -131,7 +132,7 @@ impl RangeOptimizer { if is_lowest_bit_size { already_seen_witness.insert(witness); new_order_list.push(order_list[idx]); - optimized_opcodes.push(optimized_range_opcode(witness, num_bits)); + optimized_opcodes.push(opcode); } } @@ -139,36 +140,11 @@ impl RangeOptimizer { } } -/// Extract the range opcode from the `Opcode` enum -/// Returns None, if `Opcode` is not the range opcode. -fn extract_range_opcode(opcode: &Opcode) -> Option<(Witness, u32)> { - match opcode { - Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { input }) => { - Some((input.witness, input.num_bits)) - } - _ => None, - } -} - -fn optimized_range_opcode(witness: Witness, num_bits: u32) -> Opcode { - if num_bits == 1 { - Opcode::AssertZero(Expression { - mul_terms: vec![(FieldElement::one(), witness, witness)], - linear_combinations: vec![(-FieldElement::one(), witness)], - q_c: FieldElement::zero(), - }) - } else { - Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput { witness, num_bits }, - }) - } -} - #[cfg(test)] mod tests { use std::collections::BTreeSet; - use crate::compiler::optimizers::redundant_range::{extract_range_opcode, RangeOptimizer}; + use crate::compiler::optimizers::redundant_range::RangeOptimizer; use acir::{ circuit::{ opcodes::{BlackBoxFuncCall, FunctionInput}, @@ -218,11 +194,12 @@ mod tests { let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions); assert_eq!(optimized_circuit.opcodes.len(), 1); - let (witness, num_bits) = - extract_range_opcode(&optimized_circuit.opcodes[0]).expect("expected one range opcode"); - - assert_eq!(witness, Witness(1)); - assert_eq!(num_bits, 16); + assert_eq!( + optimized_circuit.opcodes[0], + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: Witness(1), num_bits: 16 } + }) + ); } #[test] @@ -240,15 +217,18 @@ mod tests { let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions); assert_eq!(optimized_circuit.opcodes.len(), 2); - let (witness_a, num_bits_a) = - extract_range_opcode(&optimized_circuit.opcodes[0]).expect("expected two range opcode"); - let (witness_b, num_bits_b) = - extract_range_opcode(&optimized_circuit.opcodes[1]).expect("expected two range opcode"); - - assert_eq!(witness_a, Witness(1)); - assert_eq!(witness_b, Witness(2)); - assert_eq!(num_bits_a, 16); - assert_eq!(num_bits_b, 23); + assert_eq!( + optimized_circuit.opcodes[0], + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: Witness(1), num_bits: 16 } + }) + ); + assert_eq!( + optimized_circuit.opcodes[1], + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: Witness(2), num_bits: 23 } + }) + ); } #[test] diff --git a/aztec_macros/src/lib.rs b/aztec_macros/src/lib.rs index b1d401b4e53..1dbe7631388 100644 --- a/aztec_macros/src/lib.rs +++ b/aztec_macros/src/lib.rs @@ -26,8 +26,12 @@ impl MacroProcessor for AztecMacro { transform(ast, crate_id, context) } - fn process_typed_ast(&self, crate_id: &CrateId, context: &mut HirContext) { - transform_hir(crate_id, context) + fn process_typed_ast( + &self, + crate_id: &CrateId, + context: &mut HirContext, + ) -> Result<(), (MacroError, FileId)> { + transform_hir(crate_id, context).map_err(|(err, file_id)| (err.into(), file_id)) } } @@ -41,6 +45,7 @@ pub enum AztecMacroError { ContractHasTooManyFunctions { span: Span }, ContractConstructorMissing { span: Span }, UnsupportedFunctionArgumentType { span: Span, typ: UnresolvedTypeData }, + EventError { span: Span, message: String }, } impl From for MacroError { @@ -71,6 +76,11 @@ impl From for MacroError { secondary_message: None, span: Some(span), }, + AztecMacroError::EventError { span, message } => MacroError { + primary_message: message, + secondary_message: None, + span: Some(span), + }, } } } @@ -237,8 +247,11 @@ fn transform( // /// Completes the Hir with data gathered from type resolution -fn transform_hir(crate_id: &CrateId, context: &mut HirContext) { - transform_events(crate_id, context); +fn transform_hir( + crate_id: &CrateId, + context: &mut HirContext, +) -> Result<(), (AztecMacroError, FileId)> { + transform_events(crate_id, context) } /// Includes an import to the aztec library if it has not been included yet @@ -472,19 +485,30 @@ fn collect_crate_structs(crate_id: &CrateId, context: &HirContext) -> Vec Result<(), (AztecMacroError, FileId)> { let struct_type = interner.get_struct(struct_id); let selector_id = interner - .lookup_method(&Type::Struct(struct_type, vec![]), struct_id, "selector", false) - .expect("Selector method not found"); + .lookup_method(&Type::Struct(struct_type.clone(), vec![]), struct_id, "selector", false) + .ok_or_else(|| { + let error = AztecMacroError::EventError { + span: struct_type.borrow().location.span, + message: "Selector method not found".to_owned(), + }; + (error, struct_type.borrow().location.file) + })?; let selector_function = interner.function(&selector_id); let compute_selector_statement = interner.statement( - selector_function - .block(interner) - .statements() - .first() - .expect("Compute selector statement not found"), + selector_function.block(interner).statements().first().ok_or_else(|| { + let error = AztecMacroError::EventError { + span: struct_type.borrow().location.span, + message: "Compute selector statement not found".to_owned(), + }; + (error, struct_type.borrow().location.file) + })?, ); let compute_selector_expression = match compute_selector_statement { @@ -494,12 +518,21 @@ fn transform_event(struct_id: StructId, interner: &mut NodeInterner) { }, _ => None, } - .expect("Compute selector statement is not a call expression"); - - let first_arg_id = compute_selector_expression - .arguments - .first() - .expect("Missing argument for compute selector"); + .ok_or_else(|| { + let error = AztecMacroError::EventError { + span: struct_type.borrow().location.span, + message: "Compute selector statement is not a call expression".to_owned(), + }; + (error, struct_type.borrow().location.file) + })?; + + let first_arg_id = compute_selector_expression.arguments.first().ok_or_else(|| { + let error = AztecMacroError::EventError { + span: struct_type.borrow().location.span, + message: "Compute selector statement is not a call expression".to_owned(), + }; + (error, struct_type.borrow().location.file) + })?; match interner.expression(first_arg_id) { HirExpression::Literal(HirLiteral::Str(signature)) @@ -518,18 +551,29 @@ fn transform_event(struct_id: StructId, interner: &mut NodeInterner) { selector_literal_id, Type::String(Box::new(Type::Constant(signature.len() as u64))), ); + Ok(()) } - _ => unreachable!("Signature placeholder literal does not match"), + _ => Err(( + AztecMacroError::EventError { + span: struct_type.borrow().location.span, + message: "Signature placeholder literal does not match".to_owned(), + }, + struct_type.borrow().location.file, + )), } } -fn transform_events(crate_id: &CrateId, context: &mut HirContext) { +fn transform_events( + crate_id: &CrateId, + context: &mut HirContext, +) -> Result<(), (AztecMacroError, FileId)> { for struct_id in collect_crate_structs(crate_id, context) { let attributes = context.def_interner.struct_attributes(&struct_id); if attributes.iter().any(|attr| matches!(attr, SecondaryAttribute::Event)) { - transform_event(struct_id, &mut context.def_interner); + transform_event(struct_id, &mut context.def_interner)?; } } + Ok(()) } const SIGNATURE_PLACEHOLDER: &str = "SIGNATURE_PLACEHOLDER"; diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index a6ab6b1d825..f7441750fc8 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -14,7 +14,7 @@ use crate::hir::resolution::{ use crate::hir::type_check::{type_check_func, TypeCheckError, TypeChecker}; use crate::hir::Context; -use crate::macros_api::MacroProcessor; +use crate::macros_api::{MacroError, MacroProcessor}; use crate::node_interner::{FuncId, NodeInterner, StmtId, StructId, TraitId, TypeAliasId}; use crate::parser::{ParserError, SortedModule}; @@ -155,6 +155,12 @@ impl From for CustomDiagnostic { } } +impl From for CompilationError { + fn from(value: MacroError) -> Self { + CompilationError::DefinitionError(DefCollectorErrorKind::MacroError(value)) + } +} + impl From for CompilationError { fn from(value: ParserError) -> Self { CompilationError::ParseError(value) @@ -359,7 +365,11 @@ impl DefCollector { errors.extend(resolved_globals.errors); for macro_processor in macro_processors { - macro_processor.process_typed_ast(&crate_id, context); + macro_processor.process_typed_ast(&crate_id, context).unwrap_or_else( + |(macro_err, file_id)| { + errors.push((macro_err.into(), file_id)); + }, + ); } errors.extend(type_check_globals(&mut context.def_interner, resolved_globals.globals)); diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index 390807afd17..7bd4de77e84 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -84,6 +84,8 @@ pub enum ResolverError { InvalidTypeForEntryPoint { span: Span }, #[error("Nested slices are not supported")] NestedSlices { span: Span }, + #[error("Usage of the `#[foreign]` or `#[builtin]` function attributes are not allowed outside of the Noir standard library")] + LowLevelFunctionOutsideOfStdlib { ident: Ident }, } impl ResolverError { @@ -311,6 +313,11 @@ impl From for Diagnostic { "Try to use a constant sized array instead".into(), span, ), + ResolverError::LowLevelFunctionOutsideOfStdlib { ident } => Diagnostic::simple_error( + "Definition of low-level function outside of standard library".into(), + "Usage of the `#[foreign]` or `#[builtin]` function attributes are not allowed outside of the Noir standard library".into(), + ident.span(), + ), } } } diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index df533f6a4ae..8243b684c8a 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -191,10 +191,18 @@ impl<'a> Resolver<'a> { self.add_generics(&func.def.generics); self.trait_bounds = func.def.where_clause.clone(); + let is_low_level_or_oracle = func + .attributes() + .function + .as_ref() + .map_or(false, |func| func.is_low_level() || func.is_oracle()); let (hir_func, func_meta) = self.intern_function(func, func_id); let func_scope_tree = self.scopes.end_function(); - self.check_for_unused_variables_in_scope_tree(func_scope_tree); + // The arguments to low-level and oracle functions are always unused so we do not produce warnings for them. + if !is_low_level_or_oracle { + self.check_for_unused_variables_in_scope_tree(func_scope_tree); + } self.trait_bounds.clear(); (hir_func, func_meta, self.errors) @@ -900,6 +908,13 @@ impl<'a> Resolver<'a> { position: PubPosition::ReturnType, }); } + let is_low_level_function = + func.attributes().function.as_ref().map_or(false, |func| func.is_low_level()); + if !self.path_resolver.module_id().krate.is_stdlib() && is_low_level_function { + let error = + ResolverError::LowLevelFunctionOutsideOfStdlib { ident: func.name_ident().clone() }; + self.push_err(error); + } // 'pub' is required on return types for entry point functions if self.is_entry_point_function(func) diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index ab131ccd880..835a0baae3f 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -562,6 +562,10 @@ impl FunctionAttribute { matches!(self, FunctionAttribute::Foreign(_)) } + pub fn is_oracle(&self) -> bool { + matches!(self, FunctionAttribute::Oracle(_)) + } + pub fn is_low_level(&self) -> bool { matches!(self, FunctionAttribute::Foreign(_) | FunctionAttribute::Builtin(_)) } diff --git a/compiler/noirc_frontend/src/lib.rs b/compiler/noirc_frontend/src/lib.rs index 9582b80dcba..b6d4c568334 100644 --- a/compiler/noirc_frontend/src/lib.rs +++ b/compiler/noirc_frontend/src/lib.rs @@ -75,6 +75,10 @@ pub mod macros_api { ) -> Result; /// Function to manipulate the AST after type checking has been completed. /// The AST after type checking has been done is called the HIR. - fn process_typed_ast(&self, crate_id: &CrateId, context: &mut HirContext); + fn process_typed_ast( + &self, + crate_id: &CrateId, + context: &mut HirContext, + ) -> Result<(), (MacroError, FileId)>; } } diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 9ccbddab9ec..a4246a9fe7d 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -52,10 +52,12 @@ mod test { ) -> (ParsedModule, Context, Vec<(CompilationError, FileId)>) { let root = std::path::Path::new("/"); let fm = FileManager::new(root); + let mut context = Context::new(fm, Default::default()); context.def_interner.populate_dummy_operator_traits(); let root_file_id = FileId::dummy(); let root_crate_id = context.crate_graph.add_crate_root(root_file_id); + let (program, parser_errors) = parse_program(src); let mut errors = vecmap(parser_errors, |e| (e.into(), root_file_id)); remove_experimental_warnings(&mut errors); diff --git a/docs/docs/explainers/explainer-recursion.md b/docs/docs/explainers/explainer-recursion.md index 8f992ec29fd..18846176ca7 100644 --- a/docs/docs/explainers/explainer-recursion.md +++ b/docs/docs/explainers/explainer-recursion.md @@ -16,12 +16,13 @@ keywords: "Optimizing Computational Resources", "Improving Efficiency", "Verification Key", - "Aggregation Objects", + "Aggregation", "Recursive zkSNARK schemes", "PLONK", "Proving and Verification Keys" ] sidebar_position: 1 +pagination_next: how_to/how-to-recursion --- In programming, we tend to think of recursion as something calling itself. A classic example would be the calculation of the factorial of a number: @@ -64,7 +65,7 @@ So, they use zero-knowledge proofs. Alice tries to guess Bob's number, and Bob w This ZK proof can go on a smart contract, revealing the winner and even giving prizes. However, this means every turn needs to be verified on-chain. This incurs some cost and waiting time that may simply make the game too expensive or time-consuming to be worth it. -As a solution, Alice proposes the following: "what if Bob generates his proof, and instead of sending it on-chain, I verify it *within* my own proof before playing my own turn?". +As a solution, Alice proposes the following: "what if Bob generates his proof, and instead of sending it on-chain, I verify it *within* my own proof before playing my own turn?". She can then generate a proof that she verified his proof, and so on. @@ -116,25 +117,19 @@ As you can see in the [recursion reference](noir/standard_library/recursion.md), - The Verification Key of the circuit that generated the proof - A hash of this verification key, as it's needed for some backends - The public inputs for the proof -- The input aggregation object -It also returns the `output aggregation object`. These aggregation objects can be confusing at times, so let's dive in a little bit. - -### Aggregation objects +:::info Recursive zkSNARK schemes do not necessarily "verify a proof" in the sense that you expect a true or false to be spit out by the verifier. Rather an aggregation object is built over the public inputs. -In the case of PLONK the recursive aggregation object is two G1 points (expressed as 16 witness values). The final verifier (in our case this is most often the smart contract verifier) has to be aware of this aggregation object to execute a pairing and check the validity of these points. - So, taking the example of Alice and Bob and their guessing game: - Alice makes her guess. Her proof is *not* recursive: it doesn't verify any proof within it! It's just a standard `assert(x != y)` circuit -- Bob verifies Alice's proof and makes his own guess. In this circuit, he is verifying a proof, so it needs to output an `aggregation object`: he is generating a recursive proof! -- Alice verifies Bob's *recursive proof*, and uses Bob's `output aggregation object` as the `input aggregation object` in her proof... Which in turn, generates another `output aggregation object`. +- Bob verifies Alice's proof and makes his own guess. In this circuit, he doesn't exactly *prove* the verification of Alice's proof. Instead, he *aggregates* his proof to Alice's proof. The actual verification is done when the full proof is verified, for example when using `nargo verify` or through the verifier smart contract. -One should notice that when Bob generates his first proof, he has no input aggregation object. Because he is not verifying an recursive proof, he has no `input aggregation object`. In this case, he may use zeros instead. +We can imagine recursive proofs a [relay race](https://en.wikipedia.org/wiki/Relay_race). The first runner doesn't have to receive the baton from anyone else, as he/she already starts with it. But when his/her turn is over, the next runner needs to receive it, run a bit more, and pass it along. Even though every runner could theoretically verify the baton mid-run (why not? 🏃🔍), only at the end of the race does the referee verify that the whole race is valid. -We can imagine the `aggregation object` as the baton in a [relay race](https://en.wikipedia.org/wiki/Relay_race). The first runner doesn't have to receive the baton from anyone else, as he/she already starts with it. But when his/her turn is over, the next runner needs to receive it, run a bit more, and pass it along. Even though every runner could theoretically verify the baton mid-run (why not? 🏃🔍), only at the end of the race does the referee verify that the whole race is valid. +::: ## Some architecture @@ -175,3 +170,7 @@ In this example, a regulator could verify that taxes were paid for a specific pu At the time of writing, verifying recursive proofs is surprisingly fast. This is because most of the time is spent on generating the verification key that will be used to generate the next proof. So you are able to cache the verification key and reuse it later. Currently, Noir JS packages don't expose the functionality of loading proving and verification keys, but that feature exists in the underlying `bb.js` package. + +## How can I try it + +Learn more about using recursion in Nargo and NoirJS in the [how-to guide](../how_to/how-to-recursion.md) and see a full example in [noir-examples](https://github.com/noir-lang/noir-examples). diff --git a/docs/docs/how_to/how-to-recursion.md b/docs/docs/how_to/how-to-recursion.md index 39db23f1f3a..f34647a99d5 100644 --- a/docs/docs/how_to/how-to-recursion.md +++ b/docs/docs/how_to/how-to-recursion.md @@ -108,11 +108,7 @@ This call takes the public inputs and the proof, but also the public inputs coun :::info -The `proofAsFields` has a constant size `[Field; 93]`. However, currently the backend doesn't remove the public inputs from the proof when converting it. - -This means that if your `main` circuit has two public inputs, then you should also modify the recursive circuit to accept a proof with the public inputs appended. This means that in our example, since `y` is a public input, our `proofAsFields` is of type `[Field; 94]`. - -Verification keys in Barretenberg are always of size 114. +The `proofAsFields` has a constant size `[Field; 93]` and verification keys in Barretenberg are always `[Field; 114]`. ::: @@ -136,7 +132,6 @@ const recursiveInputs = { proof: proofAsFields, // array of length 93 + size of public inputs publicInputs: [mainInput.y], // using the example above, where `y` is the only public input key_hash: vkHash, - input_aggregation_object: Array(16).fill(0) // this circuit is verifying a non-recursive proof, so there's no input aggregation object: just use zero } const { witness, returnValue } = noir.execute(recursiveInputs) // we're executing the recursive circuit now! @@ -144,7 +139,7 @@ const { proof, publicInputs } = backend.generateFinalProof(witness) const verified = backend.verifyFinalProof({ proof, publicInputs }) ``` -You can obviously chain this proof into another proof. In fact, if you're using recursive proofs, you're probably interested of using them this way! In that case, you should keep in mind the `returnValue`, as it will contain the `input_aggregation_object` for the next proof. +You can obviously chain this proof into another proof. In fact, if you're using recursive proofs, you're probably interested of using them this way! :::tip @@ -152,16 +147,16 @@ Managing circuits and "who does what" can be confusing. To make sure your naming ```js const circuits = { -main: mainJSON, -recursive: recursiveJSON + main: mainJSON, + recursive: recursiveJSON } const backends = { -main: new BarretenbergBackend(circuits.main), -recursive: new BarretenbergBackend(circuits.recursive) + main: new BarretenbergBackend(circuits.main), + recursive: new BarretenbergBackend(circuits.recursive) } const noir_programs = { -main: new Noir(circuits.main, backends.main), -recursive: new Noir(circuits.recursive, backends.recursive) + main: new Noir(circuits.main, backends.main), + recursive: new Noir(circuits.recursive, backends.recursive) } ``` diff --git a/docs/docs/noir/standard_library/recursion.md b/docs/docs/noir/standard_library/recursion.md index 67962082a8f..f252150c8b5 100644 --- a/docs/docs/noir/standard_library/recursion.md +++ b/docs/docs/noir/standard_library/recursion.md @@ -1,16 +1,16 @@ --- title: Recursive Proofs description: Learn about how to write recursive proofs in Noir. -keywords: [recursion, recursive proofs, verification_key, aggregation object, verify_proof] +keywords: [recursion, recursive proofs, verification_key, verify_proof] --- Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. -The `verify_proof` function takes a verification key, proof and public inputs for a zk program, as well as a key hash and an input aggregation object. The key hash is used to check the validity of the verification key and the input aggregation object is required by some proving systems. The `verify_proof` function returns an output aggregation object that can then be fed into future iterations of the proof verification if required. +Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) ```rust #[foreign(verify_proof)] -fn verify_proof(_verification_key : [Field], _proof : [Field], _public_input : Field, _key_hash : Field, _input_aggregation_object : [Field]) -> [Field] {} +fn verify_proof(_verification_key : [Field], _proof : [Field], _public_input : Field, _key_hash : Field) {} ``` :::info @@ -26,36 +26,29 @@ use dep::std; fn main( verification_key : [Field; 114], - proof : [Field; 94], + proof : [Field; 93], public_inputs : [Field; 1], key_hash : Field, - input_aggregation_object : [Field; 16], - proof_b : [Field; 94], -) -> pub [Field; 16] { - let output_aggregation_object_a = std::verify_proof( + proof_b : [Field; 93], +) { + std::verify_proof( verification_key.as_slice(), proof.as_slice(), public_inputs.as_slice(), - key_hash, - input_aggregation_object + key_hash ); - let output_aggregation_object = std::verify_proof( + std::verify_proof( verification_key.as_slice(), proof_b.as_slice(), public_inputs.as_slice(), - key_hash, - output_aggregation_object_a + key_hash ); - - let mut output = [0; 16]; - for i in 0..16 { - output[i] = output_aggregation_object[i]; - } - output } ``` +You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). + ## Parameters ### `verification_key` @@ -68,23 +61,8 @@ The proof for the zk program that is being verified. ### `public_inputs` -These represent the public inputs of the proof we are verifying. They should be checked against in the circuit after construction of a new aggregation state. +These represent the public inputs of the proof we are verifying. ### `key_hash` A key hash is used to check the validity of the verification key. The circuit implementing this opcode can use this hash to ensure that the key provided to the circuit matches the key produced by the circuit creator. - -### `input_aggregation_object` - -An aggregation object is blob of data that the top-level verifier must run some proof system specific algorithm on to complete verification. The size is proof system specific and will be set by the backend integrating this opcode. The input aggregation object is only not `None` when we are verifying a previous recursive aggregation in the current circuit. If this is the first recursive aggregation there is no input aggregation object. It is left to the backend to determine how to handle when there is no input aggregation object. - -## Return value - -### `output_aggregation_object` - -This is the result of a recursive aggregation and is what will be fed into the next verifier. -The next verifier can either perform a final verification (returning true or false) or perform another recursive aggregation where this output aggregation object will be the input aggregation object of the next recursive aggregation. - -## Example - -You can see an example of how to do recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). diff --git a/docs/versioned_docs/version-v0.23.0/explainers/explainer-recursion.md b/docs/versioned_docs/version-v0.23.0/explainers/explainer-recursion.md index 8f992ec29fd..18846176ca7 100644 --- a/docs/versioned_docs/version-v0.23.0/explainers/explainer-recursion.md +++ b/docs/versioned_docs/version-v0.23.0/explainers/explainer-recursion.md @@ -16,12 +16,13 @@ keywords: "Optimizing Computational Resources", "Improving Efficiency", "Verification Key", - "Aggregation Objects", + "Aggregation", "Recursive zkSNARK schemes", "PLONK", "Proving and Verification Keys" ] sidebar_position: 1 +pagination_next: how_to/how-to-recursion --- In programming, we tend to think of recursion as something calling itself. A classic example would be the calculation of the factorial of a number: @@ -64,7 +65,7 @@ So, they use zero-knowledge proofs. Alice tries to guess Bob's number, and Bob w This ZK proof can go on a smart contract, revealing the winner and even giving prizes. However, this means every turn needs to be verified on-chain. This incurs some cost and waiting time that may simply make the game too expensive or time-consuming to be worth it. -As a solution, Alice proposes the following: "what if Bob generates his proof, and instead of sending it on-chain, I verify it *within* my own proof before playing my own turn?". +As a solution, Alice proposes the following: "what if Bob generates his proof, and instead of sending it on-chain, I verify it *within* my own proof before playing my own turn?". She can then generate a proof that she verified his proof, and so on. @@ -116,25 +117,19 @@ As you can see in the [recursion reference](noir/standard_library/recursion.md), - The Verification Key of the circuit that generated the proof - A hash of this verification key, as it's needed for some backends - The public inputs for the proof -- The input aggregation object -It also returns the `output aggregation object`. These aggregation objects can be confusing at times, so let's dive in a little bit. - -### Aggregation objects +:::info Recursive zkSNARK schemes do not necessarily "verify a proof" in the sense that you expect a true or false to be spit out by the verifier. Rather an aggregation object is built over the public inputs. -In the case of PLONK the recursive aggregation object is two G1 points (expressed as 16 witness values). The final verifier (in our case this is most often the smart contract verifier) has to be aware of this aggregation object to execute a pairing and check the validity of these points. - So, taking the example of Alice and Bob and their guessing game: - Alice makes her guess. Her proof is *not* recursive: it doesn't verify any proof within it! It's just a standard `assert(x != y)` circuit -- Bob verifies Alice's proof and makes his own guess. In this circuit, he is verifying a proof, so it needs to output an `aggregation object`: he is generating a recursive proof! -- Alice verifies Bob's *recursive proof*, and uses Bob's `output aggregation object` as the `input aggregation object` in her proof... Which in turn, generates another `output aggregation object`. +- Bob verifies Alice's proof and makes his own guess. In this circuit, he doesn't exactly *prove* the verification of Alice's proof. Instead, he *aggregates* his proof to Alice's proof. The actual verification is done when the full proof is verified, for example when using `nargo verify` or through the verifier smart contract. -One should notice that when Bob generates his first proof, he has no input aggregation object. Because he is not verifying an recursive proof, he has no `input aggregation object`. In this case, he may use zeros instead. +We can imagine recursive proofs a [relay race](https://en.wikipedia.org/wiki/Relay_race). The first runner doesn't have to receive the baton from anyone else, as he/she already starts with it. But when his/her turn is over, the next runner needs to receive it, run a bit more, and pass it along. Even though every runner could theoretically verify the baton mid-run (why not? 🏃🔍), only at the end of the race does the referee verify that the whole race is valid. -We can imagine the `aggregation object` as the baton in a [relay race](https://en.wikipedia.org/wiki/Relay_race). The first runner doesn't have to receive the baton from anyone else, as he/she already starts with it. But when his/her turn is over, the next runner needs to receive it, run a bit more, and pass it along. Even though every runner could theoretically verify the baton mid-run (why not? 🏃🔍), only at the end of the race does the referee verify that the whole race is valid. +::: ## Some architecture @@ -175,3 +170,7 @@ In this example, a regulator could verify that taxes were paid for a specific pu At the time of writing, verifying recursive proofs is surprisingly fast. This is because most of the time is spent on generating the verification key that will be used to generate the next proof. So you are able to cache the verification key and reuse it later. Currently, Noir JS packages don't expose the functionality of loading proving and verification keys, but that feature exists in the underlying `bb.js` package. + +## How can I try it + +Learn more about using recursion in Nargo and NoirJS in the [how-to guide](../how_to/how-to-recursion.md) and see a full example in [noir-examples](https://github.com/noir-lang/noir-examples). diff --git a/docs/versioned_docs/version-v0.23.0/how_to/how-to-recursion.md b/docs/versioned_docs/version-v0.23.0/how_to/how-to-recursion.md index 39db23f1f3a..f34647a99d5 100644 --- a/docs/versioned_docs/version-v0.23.0/how_to/how-to-recursion.md +++ b/docs/versioned_docs/version-v0.23.0/how_to/how-to-recursion.md @@ -108,11 +108,7 @@ This call takes the public inputs and the proof, but also the public inputs coun :::info -The `proofAsFields` has a constant size `[Field; 93]`. However, currently the backend doesn't remove the public inputs from the proof when converting it. - -This means that if your `main` circuit has two public inputs, then you should also modify the recursive circuit to accept a proof with the public inputs appended. This means that in our example, since `y` is a public input, our `proofAsFields` is of type `[Field; 94]`. - -Verification keys in Barretenberg are always of size 114. +The `proofAsFields` has a constant size `[Field; 93]` and verification keys in Barretenberg are always `[Field; 114]`. ::: @@ -136,7 +132,6 @@ const recursiveInputs = { proof: proofAsFields, // array of length 93 + size of public inputs publicInputs: [mainInput.y], // using the example above, where `y` is the only public input key_hash: vkHash, - input_aggregation_object: Array(16).fill(0) // this circuit is verifying a non-recursive proof, so there's no input aggregation object: just use zero } const { witness, returnValue } = noir.execute(recursiveInputs) // we're executing the recursive circuit now! @@ -144,7 +139,7 @@ const { proof, publicInputs } = backend.generateFinalProof(witness) const verified = backend.verifyFinalProof({ proof, publicInputs }) ``` -You can obviously chain this proof into another proof. In fact, if you're using recursive proofs, you're probably interested of using them this way! In that case, you should keep in mind the `returnValue`, as it will contain the `input_aggregation_object` for the next proof. +You can obviously chain this proof into another proof. In fact, if you're using recursive proofs, you're probably interested of using them this way! :::tip @@ -152,16 +147,16 @@ Managing circuits and "who does what" can be confusing. To make sure your naming ```js const circuits = { -main: mainJSON, -recursive: recursiveJSON + main: mainJSON, + recursive: recursiveJSON } const backends = { -main: new BarretenbergBackend(circuits.main), -recursive: new BarretenbergBackend(circuits.recursive) + main: new BarretenbergBackend(circuits.main), + recursive: new BarretenbergBackend(circuits.recursive) } const noir_programs = { -main: new Noir(circuits.main, backends.main), -recursive: new Noir(circuits.recursive, backends.recursive) + main: new Noir(circuits.main, backends.main), + recursive: new Noir(circuits.recursive, backends.recursive) } ``` diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/recursion.md b/docs/versioned_docs/version-v0.23.0/noir/standard_library/recursion.md index 67962082a8f..f252150c8b5 100644 --- a/docs/versioned_docs/version-v0.23.0/noir/standard_library/recursion.md +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/recursion.md @@ -1,16 +1,16 @@ --- title: Recursive Proofs description: Learn about how to write recursive proofs in Noir. -keywords: [recursion, recursive proofs, verification_key, aggregation object, verify_proof] +keywords: [recursion, recursive proofs, verification_key, verify_proof] --- Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. -The `verify_proof` function takes a verification key, proof and public inputs for a zk program, as well as a key hash and an input aggregation object. The key hash is used to check the validity of the verification key and the input aggregation object is required by some proving systems. The `verify_proof` function returns an output aggregation object that can then be fed into future iterations of the proof verification if required. +Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) ```rust #[foreign(verify_proof)] -fn verify_proof(_verification_key : [Field], _proof : [Field], _public_input : Field, _key_hash : Field, _input_aggregation_object : [Field]) -> [Field] {} +fn verify_proof(_verification_key : [Field], _proof : [Field], _public_input : Field, _key_hash : Field) {} ``` :::info @@ -26,36 +26,29 @@ use dep::std; fn main( verification_key : [Field; 114], - proof : [Field; 94], + proof : [Field; 93], public_inputs : [Field; 1], key_hash : Field, - input_aggregation_object : [Field; 16], - proof_b : [Field; 94], -) -> pub [Field; 16] { - let output_aggregation_object_a = std::verify_proof( + proof_b : [Field; 93], +) { + std::verify_proof( verification_key.as_slice(), proof.as_slice(), public_inputs.as_slice(), - key_hash, - input_aggregation_object + key_hash ); - let output_aggregation_object = std::verify_proof( + std::verify_proof( verification_key.as_slice(), proof_b.as_slice(), public_inputs.as_slice(), - key_hash, - output_aggregation_object_a + key_hash ); - - let mut output = [0; 16]; - for i in 0..16 { - output[i] = output_aggregation_object[i]; - } - output } ``` +You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). + ## Parameters ### `verification_key` @@ -68,23 +61,8 @@ The proof for the zk program that is being verified. ### `public_inputs` -These represent the public inputs of the proof we are verifying. They should be checked against in the circuit after construction of a new aggregation state. +These represent the public inputs of the proof we are verifying. ### `key_hash` A key hash is used to check the validity of the verification key. The circuit implementing this opcode can use this hash to ensure that the key provided to the circuit matches the key produced by the circuit creator. - -### `input_aggregation_object` - -An aggregation object is blob of data that the top-level verifier must run some proof system specific algorithm on to complete verification. The size is proof system specific and will be set by the backend integrating this opcode. The input aggregation object is only not `None` when we are verifying a previous recursive aggregation in the current circuit. If this is the first recursive aggregation there is no input aggregation object. It is left to the backend to determine how to handle when there is no input aggregation object. - -## Return value - -### `output_aggregation_object` - -This is the result of a recursive aggregation and is what will be fed into the next verifier. -The next verifier can either perform a final verification (returning true or false) or perform another recursive aggregation where this output aggregation object will be the input aggregation object of the next recursive aggregation. - -## Example - -You can see an example of how to do recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). diff --git a/noir_stdlib/src/array.nr b/noir_stdlib/src/array.nr index bcdf56dd7aa..87cf4167dac 100644 --- a/noir_stdlib/src/array.nr +++ b/noir_stdlib/src/array.nr @@ -3,10 +3,10 @@ // by the methods in the `slice` module impl [T; N] { #[builtin(array_len)] - pub fn len(_self: Self) -> Field {} + pub fn len(self) -> Field {} #[builtin(arraysort)] - pub fn sort(_self: Self) -> Self {} + pub fn sort(self) -> Self {} // Sort with a custom sorting function. pub fn sort_via(mut a: Self, ordering: fn[Env](T, T) -> bool) -> Self { diff --git a/noir_stdlib/src/ecdsa_secp256k1.nr b/noir_stdlib/src/ecdsa_secp256k1.nr index 290ccba27e5..e8d9af2230f 100644 --- a/noir_stdlib/src/ecdsa_secp256k1.nr +++ b/noir_stdlib/src/ecdsa_secp256k1.nr @@ -1,10 +1,10 @@ #[foreign(ecdsa_secp256k1)] // docs:start:ecdsa_secp256k1 pub fn verify_signature( - _public_key_x: [u8; 32], - _public_key_y: [u8; 32], - _signature: [u8; 64], - _message_hash: [u8; N] + public_key_x: [u8; 32], + public_key_y: [u8; 32], + signature: [u8; 64], + message_hash: [u8; N] ) -> bool // docs:end:ecdsa_secp256k1 -{} +{} \ No newline at end of file diff --git a/noir_stdlib/src/ecdsa_secp256r1.nr b/noir_stdlib/src/ecdsa_secp256r1.nr index 390f8ed39d2..9fe932a2f3d 100644 --- a/noir_stdlib/src/ecdsa_secp256r1.nr +++ b/noir_stdlib/src/ecdsa_secp256r1.nr @@ -1,10 +1,10 @@ #[foreign(ecdsa_secp256r1)] // docs:start:ecdsa_secp256r1 pub fn verify_signature( - _public_key_x: [u8; 32], - _public_key_y: [u8; 32], - _signature: [u8; 64], - _message_hash: [u8; N] + public_key_x: [u8; 32], + public_key_y: [u8; 32], + signature: [u8; 64], + message_hash: [u8; N] ) -> bool // docs:end:ecdsa_secp256r1 -{} +{} \ No newline at end of file diff --git a/noir_stdlib/src/field.nr b/noir_stdlib/src/field.nr index fbd76a1e8a2..66fb50119f9 100644 --- a/noir_stdlib/src/field.nr +++ b/noir_stdlib/src/field.nr @@ -13,13 +13,13 @@ impl Field { } #[builtin(to_le_bits)] - fn __to_le_bits(_self: Self, _bit_size: u32) -> [u1] {} + fn __to_le_bits(self, _bit_size: u32) -> [u1] {} #[builtin(to_be_bits)] - fn __to_be_bits(_self: Self, _bit_size: u32) -> [u1] {} + fn __to_be_bits(self, bit_size: u32) -> [u1] {} #[builtin(apply_range_constraint)] - fn __assert_max_bit_size(_self: Self, _bit_size: u32) {} + fn __assert_max_bit_size(self, bit_size: u32) {} pub fn assert_max_bit_size(self: Self, bit_size: u32) { crate::assert_constant(bit_size); @@ -53,10 +53,10 @@ impl Field { // decompose `_self` into a `_result_len` vector over the `_radix` basis // `_radix` must be less than 256 #[builtin(to_le_radix)] - fn __to_le_radix(_self: Self, _radix: u32, _result_len: u32) -> [u8] {} + fn __to_le_radix(self, radix: u32, result_len: u32) -> [u8] {} #[builtin(to_be_radix)] - fn __to_be_radix(_self: Self, _radix: u32, _result_len: u32) -> [u8] {} + fn __to_be_radix(self, radix: u32, result_len: u32) -> [u8] {} // Returns self to the power of the given exponent value. diff --git a/noir_stdlib/src/hash.nr b/noir_stdlib/src/hash.nr index 4033e2a5365..cc864039a90 100644 --- a/noir_stdlib/src/hash.nr +++ b/noir_stdlib/src/hash.nr @@ -3,19 +3,19 @@ mod mimc; #[foreign(sha256)] // docs:start:sha256 -pub fn sha256(_input: [u8; N]) -> [u8; 32] +pub fn sha256(input: [u8; N]) -> [u8; 32] // docs:end:sha256 {} #[foreign(blake2s)] // docs:start:blake2s -pub fn blake2s(_input: [u8; N]) -> [u8; 32] +pub fn blake2s(input: [u8; N]) -> [u8; 32] // docs:end:blake2s {} #[foreign(blake3)] // docs:start:blake3 -pub fn blake3(_input: [u8; N]) -> [u8; 32] +pub fn blake3(input: [u8; N]) -> [u8; 32] // docs:end:blake3 {} @@ -32,7 +32,7 @@ pub fn pedersen_commitment(input: [Field; N]) -> PedersenPoint } #[foreign(pedersen_commitment)] -pub fn __pedersen_commitment_with_separator(_input: [Field; N], _separator: u32) -> [Field; 2] {} +pub fn __pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> [Field; 2] {} pub fn pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> PedersenPoint { let values = __pedersen_commitment_with_separator(input, separator); @@ -47,13 +47,13 @@ pub fn pedersen_hash(input: [Field; N]) -> Field } #[foreign(pedersen_hash)] -pub fn pedersen_hash_with_separator(_input: [Field; N], _separator: u32) -> Field {} +pub fn pedersen_hash_with_separator(input: [Field; N], separator: u32) -> Field {} -pub fn hash_to_field(_input: [Field; N]) -> Field { +pub fn hash_to_field(input: [Field; N]) -> Field { let mut inputs_as_bytes = []; for i in 0..N { - let input_bytes = _input[i].to_le_bytes(32); + let input_bytes = input[i].to_le_bytes(32); for i in 0..32 { inputs_as_bytes = inputs_as_bytes.push_back(input_bytes[i]); } @@ -65,7 +65,7 @@ pub fn hash_to_field(_input: [Field; N]) -> Field { #[foreign(keccak256)] // docs:start:keccak256 -pub fn keccak256(_input: [u8; N], _message_size: u32) -> [u8; 32] +pub fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] // docs:end:keccak256 {} diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 90aff3c312b..5165d1ee07b 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -30,7 +30,7 @@ mod uint128; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident #[oracle(print)] -unconstrained fn print_oracle(_with_newline: bool, _input: T) {} +unconstrained fn print_oracle(with_newline: bool, input: T) {} unconstrained pub fn print(input: T) { print_oracle(false, input); @@ -41,20 +41,20 @@ unconstrained pub fn println(input: T) { } #[foreign(recursive_aggregation)] -pub fn verify_proof(_verification_key: [Field], _proof: [Field], _public_inputs: [Field], _key_hash: Field) {} +pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} // Asserts that the given value is known at compile-time. // Useful for debugging for-loop bounds. #[builtin(assert_constant)] -pub fn assert_constant(_x: T) {} +pub fn assert_constant(x: T) {} // from_field and as_field are private since they are not valid for every type. // `as` should be the default for users to cast between primitive types, and in the future // traits can be used to work with generic types. #[builtin(from_field)] -fn from_field(_x: Field) -> T {} +fn from_field(x: Field) -> T {} #[builtin(as_field)] -fn as_field(_x: T) -> Field {} +fn as_field(x: T) -> Field {} pub fn wrapping_add(x: T, y: T) -> T { crate::from_field(crate::as_field(x) + crate::as_field(y)) diff --git a/noir_stdlib/src/scalar_mul.nr b/noir_stdlib/src/scalar_mul.nr index 0e84b4f66fc..26378e4839a 100644 --- a/noir_stdlib/src/scalar_mul.nr +++ b/noir_stdlib/src/scalar_mul.nr @@ -26,8 +26,8 @@ impl Add for EmbeddedCurvePoint { #[foreign(fixed_base_scalar_mul)] // docs:start:fixed_base_embedded_curve pub fn fixed_base_embedded_curve( - _low: Field, - _high: Field + low: Field, + high: Field ) -> [Field; 2] // docs:end:fixed_base_embedded_curve {} diff --git a/noir_stdlib/src/schnorr.nr b/noir_stdlib/src/schnorr.nr index 025c3a0f921..33656254550 100644 --- a/noir_stdlib/src/schnorr.nr +++ b/noir_stdlib/src/schnorr.nr @@ -1,10 +1,10 @@ #[foreign(schnorr_verify)] // docs:start:schnorr_verify pub fn verify_signature( - _public_key_x: Field, - _public_key_y: Field, - _signature: [u8; 64], - _message: [u8; N] + public_key_x: Field, + public_key_y: Field, + signature: [u8; 64], + message: [u8; N] ) -> bool // docs:end:schnorr_verify -{} +{} \ No newline at end of file diff --git a/noir_stdlib/src/slice.nr b/noir_stdlib/src/slice.nr index a5a9a38ed53..aa4b73edc1a 100644 --- a/noir_stdlib/src/slice.nr +++ b/noir_stdlib/src/slice.nr @@ -3,34 +3,34 @@ impl [T] { /// new slice with a length one greater than the /// original unmodified slice. #[builtin(slice_push_back)] - pub fn push_back(_self: Self, _elem: T) -> Self { } + pub fn push_back(self, elem: T) -> Self { } /// Push a new element to the front of the slice, returning a /// new slice with a length one greater than the /// original unmodified slice. #[builtin(slice_push_front)] - pub fn push_front(_self: Self, _elem: T) -> Self { } + pub fn push_front(self, elem: T) -> Self { } /// Remove the last element of the slice, returning the /// popped slice and the element in a tuple #[builtin(slice_pop_back)] - pub fn pop_back(_self: Self) -> (Self, T) { } + pub fn pop_back(self) -> (Self, T) { } /// Remove the first element of the slice, returning the /// element and the popped slice in a tuple #[builtin(slice_pop_front)] - pub fn pop_front(_self: Self) -> (T, Self) { } + pub fn pop_front(self) -> (T, Self) { } /// Insert an element at a specified index, shifting all elements /// after it to the right #[builtin(slice_insert)] - pub fn insert(_self: Self, _index: Field, _elem: T) -> Self { } + pub fn insert(self, index: Field, elem: T) -> Self { } /// Remove an element at a specified index, shifting all elements /// after it to the left, returning the altered slice and /// the removed element #[builtin(slice_remove)] - pub fn remove(_self: Self, _index: Field) -> (Self, T) { } + pub fn remove(self, index: Field) -> (Self, T) { } // Append each element of the `other` slice to the end of `self`. // This returns a new slice and leaves both input slices unchanged. diff --git a/noir_stdlib/src/string.nr b/noir_stdlib/src/string.nr index e402abf9ab6..ad6fd19e2de 100644 --- a/noir_stdlib/src/string.nr +++ b/noir_stdlib/src/string.nr @@ -2,7 +2,7 @@ use crate::collections::vec::Vec; impl str { /// Converts the given string into a byte array #[builtin(str_as_bytes)] - pub fn as_bytes(_self: Self) -> [u8; N] { } + pub fn as_bytes(self) -> [u8; N] { } /// return a byte vector of the str content pub fn as_bytes_vec(self: Self) -> Vec { diff --git a/noir_stdlib/src/test.nr b/noir_stdlib/src/test.nr index 47b31f4acea..560cfde741c 100644 --- a/noir_stdlib/src/test.nr +++ b/noir_stdlib/src/test.nr @@ -1,17 +1,17 @@ #[oracle(create_mock)] -unconstrained fn create_mock_oracle(_name: str) -> Field {} +unconstrained fn create_mock_oracle(name: str) -> Field {} #[oracle(set_mock_params)] -unconstrained fn set_mock_params_oracle

(_id: Field, _params: P) {} +unconstrained fn set_mock_params_oracle

(id: Field, params: P) {} #[oracle(set_mock_returns)] -unconstrained fn set_mock_returns_oracle(_id: Field, _returns: R) {} +unconstrained fn set_mock_returns_oracle(id: Field, returns: R) {} #[oracle(set_mock_times)] -unconstrained fn set_mock_times_oracle(_id: Field, _times: u64) {} +unconstrained fn set_mock_times_oracle(id: Field, times: u64) {} #[oracle(clear_mock)] -unconstrained fn clear_mock_oracle(_id: Field) {} +unconstrained fn clear_mock_oracle(id: Field) {} struct OracleMock { id: Field, diff --git a/scripts/bootstrap_native.sh b/scripts/bootstrap_native.sh index 3e0e2ed853a..974f0edcfec 100755 --- a/scripts/bootstrap_native.sh +++ b/scripts/bootstrap_native.sh @@ -12,6 +12,12 @@ else export GIT_COMMIT=$(git rev-parse --verify HEAD) fi +# Check if the 'cargo' command is available in the system +if ! command -v cargo > /dev/null; then + echo "Cargo is not installed. Please install Cargo and the Rust toolchain." + exit 1 +fi + # Build native. if [ -n "${DEBUG:-}" ]; then cargo build diff --git a/test_programs/compile_failure/builtin_function_declaration/Nargo.toml b/test_programs/compile_failure/builtin_function_declaration/Nargo.toml new file mode 100644 index 00000000000..3835292a6ba --- /dev/null +++ b/test_programs/compile_failure/builtin_function_declaration/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "builtin_function_declaration" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] diff --git a/test_programs/compile_failure/builtin_function_declaration/src/main.nr b/test_programs/compile_failure/builtin_function_declaration/src/main.nr new file mode 100644 index 00000000000..ed376557371 --- /dev/null +++ b/test_programs/compile_failure/builtin_function_declaration/src/main.nr @@ -0,0 +1,10 @@ +// This test prevents users from trying to create their own builtin functions as these should only exist in the stdlib. + +// This would otherwise be a perfectly valid declaration of the `to_le_bits` builtin function +#[builtin(to_le_bits)] +fn to_le_bits(_x: Field, _bit_size: u32) -> [u1] {} + +fn main(x: Field) -> pub u1 { + let bits = to_le_bits(x, 100); + bits[0] +} diff --git a/test_programs/compile_failure/foreign_function_declaration/Nargo.toml b/test_programs/compile_failure/foreign_function_declaration/Nargo.toml new file mode 100644 index 00000000000..951658d7fb8 --- /dev/null +++ b/test_programs/compile_failure/foreign_function_declaration/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "foreign_function_declaration" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] diff --git a/test_programs/compile_failure/foreign_function_declaration/src/main.nr b/test_programs/compile_failure/foreign_function_declaration/src/main.nr new file mode 100644 index 00000000000..6273067f6a7 --- /dev/null +++ b/test_programs/compile_failure/foreign_function_declaration/src/main.nr @@ -0,0 +1,10 @@ +// This test prevents users from trying to create their own blackbox functions as these should only exist in the stdlib. + +// This would otherwise be a perfectly valid definition of the `pedersen_hash` black box function, +// however executing the circuit results in an unhelpful ICE. +#[foreign(pedersen_hash)] +fn my_pedersen_hash(_input: [Field; N]) -> Field {} + +fn main() -> pub Field { + my_pedersen_hash([1]) +}