diff --git a/acvm-repo/acvm/src/pwg/blackbox/embedded_curve_ops.rs b/acvm-repo/acvm/src/pwg/blackbox/embedded_curve_ops.rs index fb34a01faae..e408e988d13 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/embedded_curve_ops.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/embedded_curve_ops.rs @@ -12,6 +12,7 @@ pub(super) fn multi_scalar_mul( initial_witness: &mut WitnessMap, points: &[FunctionInput], scalars: &[FunctionInput], + predicate: FunctionInput, outputs: (Witness, Witness, Witness), ) -> Result<(), OpcodeResolutionError> { let points: Result, _> = @@ -20,6 +21,9 @@ pub(super) fn multi_scalar_mul( let scalars: Result, _> = scalars.iter().map(|input| input_to_value(initial_witness, *input)).collect(); + + let predicate = input_to_value(initial_witness, predicate)?.is_one(); + let mut scalars_lo = Vec::new(); let mut scalars_hi = Vec::new(); for (i, scalar) in scalars?.into_iter().enumerate() { @@ -31,7 +35,7 @@ pub(super) fn multi_scalar_mul( } // Call the backend's multi-scalar multiplication function let (res_x, res_y, is_infinite) = - backend.multi_scalar_mul(&points, &scalars_lo, &scalars_hi)?; + backend.multi_scalar_mul(&points, &scalars_lo, &scalars_hi, predicate)?; // Insert the resulting point into the witness map insert_value(&outputs.0, res_x, initial_witness)?; @@ -45,6 +49,7 @@ pub(super) fn embedded_curve_add( initial_witness: &mut WitnessMap, input1: [FunctionInput; 3], input2: [FunctionInput; 3], + predicate: FunctionInput, outputs: (Witness, Witness, Witness), ) -> Result<(), OpcodeResolutionError> { let input1_x = input_to_value(initial_witness, input1[0])?; @@ -53,6 +58,7 @@ pub(super) fn embedded_curve_add( let input2_x = input_to_value(initial_witness, input2[0])?; let input2_y = input_to_value(initial_witness, input2[1])?; let input2_infinite = input_to_value(initial_witness, input2[2])?; + let predicate = input_to_value(initial_witness, predicate)?.is_one(); let (res_x, res_y, res_infinite) = backend.ec_add( &input1_x, &input1_y, @@ -60,6 +66,7 @@ pub(super) fn embedded_curve_add( &input2_x, &input2_y, &input2_infinite, + predicate, )?; insert_value(&outputs.0, res_x, initial_witness)?; diff --git a/acvm-repo/acvm/src/pwg/blackbox/mod.rs b/acvm-repo/acvm/src/pwg/blackbox/mod.rs index 950ed1dc6a5..17858900b3c 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/mod.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/mod.rs @@ -116,13 +116,14 @@ pub(crate) fn solve( signature, hashed_message: message, output, - .. + predicate, } => secp256k1_prehashed( initial_witness, public_key_x, public_key_y, signature, message.as_ref(), + predicate, *output, ), BlackBoxFuncCall::EcdsaSecp256r1 { @@ -131,20 +132,21 @@ pub(crate) fn solve( signature, hashed_message: message, output, - .. + predicate, } => secp256r1_prehashed( initial_witness, public_key_x, public_key_y, signature, message.as_ref(), + predicate, *output, ), - BlackBoxFuncCall::MultiScalarMul { points, scalars, outputs, .. } => { - multi_scalar_mul(backend, initial_witness, points, scalars, *outputs) + BlackBoxFuncCall::MultiScalarMul { points, scalars, outputs, predicate } => { + multi_scalar_mul(backend, initial_witness, points, scalars, *predicate, *outputs) } - BlackBoxFuncCall::EmbeddedCurveAdd { input1, input2, outputs, .. } => { - embedded_curve_add(backend, initial_witness, **input1, **input2, *outputs) + BlackBoxFuncCall::EmbeddedCurveAdd { input1, input2, outputs, predicate } => { + embedded_curve_add(backend, initial_witness, **input1, **input2, *predicate, *outputs) } // Recursive aggregation will be entirely handled by the backend and is not solved by the ACVM BlackBoxFuncCall::RecursiveAggregation { .. } => Ok(()), diff --git a/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs b/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs index 228550e9fbb..286ba30e874 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs @@ -7,7 +7,7 @@ use acvm_blackbox_solver::{ecdsa_secp256k1_verify, ecdsa_secp256r1_verify}; use crate::{ OpcodeResolutionError, - pwg::{blackbox::utils::to_u8_array, insert_value}, + pwg::{blackbox::utils::to_u8_array, input_to_value, insert_value}, }; pub(crate) fn secp256k1_prehashed( @@ -16,14 +16,19 @@ pub(crate) fn secp256k1_prehashed( public_key_y_inputs: &[FunctionInput; 32], signature_inputs: &[FunctionInput; 64], hashed_message_inputs: &[FunctionInput; 32], + predicate: &FunctionInput, output: Witness, ) -> Result<(), OpcodeResolutionError> { let pub_key_x: [u8; 32] = to_u8_array(initial_witness, public_key_x_inputs)?; let pub_key_y: [u8; 32] = to_u8_array(initial_witness, public_key_y_inputs)?; let signature: [u8; 64] = to_u8_array(initial_witness, signature_inputs)?; let hashed_message: [u8; 32] = to_u8_array(initial_witness, hashed_message_inputs)?; - - let is_valid = ecdsa_secp256k1_verify(&hashed_message, &pub_key_x, &pub_key_y, &signature)?; + let predicate = input_to_value(initial_witness, *predicate)?.is_one(); + let is_valid = if predicate { + ecdsa_secp256k1_verify(&hashed_message, &pub_key_x, &pub_key_y, &signature)? + } else { + true + }; insert_value(&output, F::from(is_valid), initial_witness) } @@ -34,14 +39,19 @@ pub(crate) fn secp256r1_prehashed( public_key_y_inputs: &[FunctionInput; 32], signature_inputs: &[FunctionInput; 64], hashed_message_inputs: &[FunctionInput; 32], + predicate: &FunctionInput, output: Witness, ) -> Result<(), OpcodeResolutionError> { let pub_key_x: [u8; 32] = to_u8_array(initial_witness, public_key_x_inputs)?; let pub_key_y: [u8; 32] = to_u8_array(initial_witness, public_key_y_inputs)?; let signature: [u8; 64] = to_u8_array(initial_witness, signature_inputs)?; let hashed_message: [u8; 32] = to_u8_array(initial_witness, hashed_message_inputs)?; - - let is_valid = ecdsa_secp256r1_verify(&hashed_message, &pub_key_x, &pub_key_y, &signature)?; + let predicate = input_to_value(initial_witness, *predicate)?.is_one(); + let is_valid = if predicate { + ecdsa_secp256r1_verify(&hashed_message, &pub_key_x, &pub_key_y, &signature)? + } else { + true + }; insert_value(&output, F::from(is_valid), initial_witness) } diff --git a/acvm-repo/blackbox_solver/src/curve_specific_solver.rs b/acvm-repo/blackbox_solver/src/curve_specific_solver.rs index eaf31fbfce4..4511119e81f 100644 --- a/acvm-repo/blackbox_solver/src/curve_specific_solver.rs +++ b/acvm-repo/blackbox_solver/src/curve_specific_solver.rs @@ -13,7 +13,10 @@ pub trait BlackBoxFunctionSolver { points: &[F], scalars_lo: &[F], scalars_hi: &[F], + predicate: bool, ) -> Result<(F, F, F), BlackBoxResolutionError>; + + #[allow(clippy::too_many_arguments)] fn ec_add( &self, input1_x: &F, @@ -22,6 +25,7 @@ pub trait BlackBoxFunctionSolver { input2_x: &F, input2_y: &F, input2_infinite: &F, + predicate: bool, ) -> Result<(F, F, F), BlackBoxResolutionError>; fn poseidon2_permutation(&self, inputs: &[F]) -> Result, BlackBoxResolutionError>; } @@ -55,6 +59,7 @@ impl BlackBoxFunctionSolver for StubbedBlackBoxSolver { _points: &[F], _scalars_lo: &[F], _scalars_hi: &[F], + _predicate: bool, ) -> Result<(F, F, F), BlackBoxResolutionError> { Err(Self::fail(BlackBoxFunc::MultiScalarMul)) } @@ -66,6 +71,7 @@ impl BlackBoxFunctionSolver for StubbedBlackBoxSolver { _input2_x: &F, _input2_y: &F, _input2_infinite: &F, + _predicate: bool, ) -> Result<(F, F, F), BlackBoxResolutionError> { Err(Self::fail(BlackBoxFunc::EmbeddedCurveAdd)) } diff --git a/acvm-repo/bn254_blackbox_solver/src/lib.rs b/acvm-repo/bn254_blackbox_solver/src/lib.rs index d7cd6048b8a..8f520787753 100644 --- a/acvm-repo/bn254_blackbox_solver/src/lib.rs +++ b/acvm-repo/bn254_blackbox_solver/src/lib.rs @@ -1,5 +1,6 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))] +use acir::AcirField; use acvm_blackbox_solver::{BlackBoxFunctionSolver, BlackBoxResolutionError}; mod embedded_curve_ops; @@ -30,8 +31,13 @@ impl BlackBoxFunctionSolver for Bn254BlackBoxSolver { points: &[FieldElement], scalars_lo: &[FieldElement], scalars_hi: &[FieldElement], + predicate: bool, ) -> Result<(FieldElement, FieldElement, FieldElement), BlackBoxResolutionError> { - multi_scalar_mul(points, scalars_lo, scalars_hi, self.pedantic_solving()) + if predicate { + multi_scalar_mul(points, scalars_lo, scalars_hi, self.pedantic_solving()) + } else { + Ok((FieldElement::zero(), FieldElement::zero(), FieldElement::one())) + } } fn ec_add( @@ -42,12 +48,17 @@ impl BlackBoxFunctionSolver for Bn254BlackBoxSolver { input2_x: &FieldElement, input2_y: &FieldElement, input2_infinite: &FieldElement, + predicate: bool, ) -> Result<(FieldElement, FieldElement, FieldElement), BlackBoxResolutionError> { - embedded_curve_add( - [*input1_x, *input1_y, *input1_infinite], - [*input2_x, *input2_y, *input2_infinite], - self.pedantic_solving(), - ) + if predicate { + embedded_curve_add( + [*input1_x, *input1_y, *input1_infinite], + [*input2_x, *input2_y, *input2_infinite], + self.pedantic_solving(), + ) + } else { + Ok((FieldElement::zero(), FieldElement::zero(), FieldElement::one())) + } } fn poseidon2_permutation( diff --git a/acvm-repo/brillig_vm/src/black_box.rs b/acvm-repo/brillig_vm/src/black_box.rs index da8f0e0cafd..b0d82f0cc84 100644 --- a/acvm-repo/brillig_vm/src/black_box.rs +++ b/acvm-repo/brillig_vm/src/black_box.rs @@ -220,7 +220,12 @@ pub(crate) fn evaluate_black_box scalars_hi.push(*scalar); } } - let (x, y, is_infinite) = solver.multi_scalar_mul(&points, &scalars_lo, &scalars_hi)?; + let (x, y, is_infinite) = solver.multi_scalar_mul( + &points, + &scalars_lo, + &scalars_hi, + true, // Predicate is always true as brillig has control flow to handle false case + )?; write_heap_array( memory, result, @@ -254,6 +259,7 @@ pub(crate) fn evaluate_black_box &input2_x, &input2_y, &input2_infinite.into(), + true, // Predicate is always true as brillig has control flow to handle false case )?; write_heap_array( diff --git a/compiler/noirc_evaluator/src/acir/acir_context/black_box.rs b/compiler/noirc_evaluator/src/acir/acir_context/black_box.rs index 9e3dadd42cf..9d88e586424 100644 --- a/compiler/noirc_evaluator/src/acir/acir_context/black_box.rs +++ b/compiler/noirc_evaluator/src/acir/acir_context/black_box.rs @@ -87,29 +87,64 @@ impl AcirContext { pub(super) fn prepare_inputs_for_black_box_func( &mut self, - inputs: Vec, + mut inputs: Vec, name: BlackBoxFunc, ) -> Result>>, RuntimeError> { - // Allow constant inputs for most blackbox, but: - // - EmbeddedCurveAdd requires all-or-nothing constant inputs - // - Poseidon2Permutation requires witness input - let allow_constant_inputs = matches!( - name, + // Allow constant inputs for most blackbox + // Allow constant predicate for all blackbox having predicate + let inputs = match name { BlackBoxFunc::MultiScalarMul - | BlackBoxFunc::Keccakf1600 - | BlackBoxFunc::Blake2s - | BlackBoxFunc::Blake3 - | BlackBoxFunc::AND - | BlackBoxFunc::XOR - | BlackBoxFunc::AES128Encrypt - | BlackBoxFunc::EmbeddedCurveAdd - ); - // Convert `AcirVar` to `FunctionInput` - let mut inputs = - self.prepare_inputs_for_black_box_func_call(inputs, allow_constant_inputs)?; - if name == BlackBoxFunc::EmbeddedCurveAdd { - inputs = self.all_variables_or_constants_for_ec_add(inputs)?; - } + | BlackBoxFunc::Keccakf1600 + | BlackBoxFunc::Blake2s + | BlackBoxFunc::Blake3 + | BlackBoxFunc::AND + | BlackBoxFunc::XOR + | BlackBoxFunc::AES128Encrypt + | BlackBoxFunc::EmbeddedCurveAdd => { + self.prepare_inputs_for_black_box_func_call(inputs, true)? + } + BlackBoxFunc::EcdsaSecp256k1 | BlackBoxFunc::EcdsaSecp256r1 => { + // ECDSA blackbox functions have 6 inputs, the last ones are: [.., predicate, output] + let predicate = inputs.swap_remove(4); + // convert the inputs into witness, except for the predicate which has been removed + let mut inputs = self.prepare_inputs_for_black_box_func_call(inputs, false)?; + // convert the predicate into witness or constant + let predicate = self.value_to_function_input(predicate)?; + // Sanity check: proving system does not expect to receive 0 predicates + assert_ne!( + predicate, + FunctionInput::Constant(F::zero()), + "0 predicate should have been optimized away" + ); + + // add back the predicate into the FunctionInputs + inputs.insert(4, vec![predicate]); + inputs + } + BlackBoxFunc::RecursiveAggregation => { + let predicate = inputs.pop().ok_or_else(|| { + RuntimeError::InternalError(InternalError::MissingArg { + name: "recursive aggregation".to_string(), + arg: "predicate".to_string(), + call_stack: self.get_call_stack(), + }) + })?; + // convert the inputs into witness, except for the predicate which has been removed + let mut inputs = self.prepare_inputs_for_black_box_func_call(inputs, false)?; + // convert the predicate into witness or constant + let predicate = self.value_to_function_input(predicate)?; + // Sanity check: proving system does not expect to receive 0 predicates + assert_ne!( + predicate, + FunctionInput::Constant(F::zero()), + "0 predicate should have been optimized away" + ); + // add back the predicate into the FunctionInputs + inputs.push(vec![predicate]); + inputs + } + _ => self.prepare_inputs_for_black_box_func_call(inputs, false)?, + }; Ok(inputs) } @@ -150,42 +185,36 @@ impl AcirContext { Ok(witnesses) } - /// [`BlackBoxFunc::EmbeddedCurveAdd`] has 6 inputs representing the two points to add - /// Each point must be either all constants, or all witnesses, - /// where constants are converted to witnesses here if mixed constant and witnesses are - /// encountered - fn all_variables_or_constants_for_ec_add( + /// Converts an `AcirValue` into a `FunctionInput` for use in black box function calls. + /// + /// - If the value can be evaluated to a constant, it returns `FunctionInput::Constant` + /// - Otherwise, it creates or retrieves a witness variable and returns `FunctionInput::Witness` + fn value_to_function_input( &mut self, - inputs: Vec>>, - ) -> Result>>, RuntimeError> { - let mut has_constant = false; - let mut has_witness = false; - let mut result = inputs.clone(); - for (i, input) in inputs.iter().enumerate() { - assert_eq!(input.len(), 1); - if input[0].is_constant() { - has_constant = true; - } else { - has_witness = true; - } - - if i % 3 == 2 { - if has_constant && has_witness { - // Convert the constants to witnesses if mixed constants and witnesses are - // encountered - for j in i - 2..i + 1 { - if let FunctionInput::Constant(constant) = inputs[j][0] { - let constant = self.add_constant(constant); - let witness_var = self.get_or_create_witness_var(constant)?; - let witness = self.var_to_witness(witness_var)?; - result[j] = vec![FunctionInput::Witness(witness)]; - } - } + value: AcirValue, + ) -> Result, RuntimeError> { + if let AcirValue::Var(acir_var, acir_type) = value { + if let Some(constant) = self.var_to_expression(acir_var)?.to_const() { + let num_bits = acir_type.bit_size::(); + if num_bits < constant.num_bits() { + return Err(RuntimeError::InvalidBlackBoxInputBitSize { + value: constant.to_string(), + num_bits: constant.num_bits(), + max_num_bits: num_bits, + call_stack: self.get_call_stack(), + }); } - has_constant = false; - has_witness = false; + Ok(FunctionInput::Constant(*constant)) + } else { + let witness_var = self.get_or_create_witness_var(acir_var)?; + let witness = self.var_to_witness(witness_var)?; + Ok(FunctionInput::Witness(witness)) } + } else { + Err(RuntimeError::InternalError(InternalError::General { + message: "Expected AcirValue".to_string(), + call_stack: self.get_call_stack(), + })) } - Ok(result) } } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index c1ab14eaaf9..4c416a73cae 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -358,6 +358,7 @@ pub(crate) mod tests { _points: &[FieldElement], _scalars_lo: &[FieldElement], _scalars_hi: &[FieldElement], + _predicate: bool, ) -> Result<(FieldElement, FieldElement, FieldElement), BlackBoxResolutionError> { Ok((4_u128.into(), 5_u128.into(), 0_u128.into())) } @@ -370,6 +371,7 @@ pub(crate) mod tests { _input2_x: &FieldElement, _input2_y: &FieldElement, _input2_infinite: &FieldElement, + _predicate: bool, ) -> Result<(FieldElement, FieldElement, FieldElement), BlackBoxResolutionError> { panic!("Path not trodden by this test") } diff --git a/compiler/noirc_evaluator/src/ssa/interpreter/intrinsics.rs b/compiler/noirc_evaluator/src/ssa/interpreter/intrinsics.rs index 93c94155858..01295118422 100644 --- a/compiler/noirc_evaluator/src/ssa/interpreter/intrinsics.rs +++ b/compiler/noirc_evaluator/src/ssa/interpreter/intrinsics.rs @@ -309,8 +309,14 @@ impl Interpreter<'_, W> { )?); } } + let predicate = self.lookup_bool( + args[2], + "retrieving predicate in call to MultiScalarMul blackbox", + )?; + let solver = bn254_blackbox_solver::Bn254BlackBoxSolver(false); - let result = solver.multi_scalar_mul(&points, &scalars_lo, &scalars_hi); + let result = + solver.multi_scalar_mul(&points, &scalars_lo, &scalars_hi, predicate); let (x, y, is_infinite) = result.map_err(Self::convert_error)?; let result = new_embedded_curve_point(x, y, is_infinite)?; Ok(vec![result]) @@ -350,8 +356,16 @@ impl Interpreter<'_, W> { self.lookup_field(args[4], "call EmbeddedCurveAdd BlackBox")?, self.lookup_bool(args[5], "call EmbeddedCurveAdd BlackBox")?, ); - let result = - solver.ec_add(&lhs.0, &lhs.1, &lhs.2.into(), &rhs.0, &rhs.1, &rhs.2.into()); + let predicate = self.lookup_bool(args[6], "call EmbeddedCurveAdd BlackBox")?; + let result = solver.ec_add( + &lhs.0, + &lhs.1, + &lhs.2.into(), + &rhs.0, + &rhs.1, + &rhs.2.into(), + predicate, + ); let (x, y, is_infinite) = result.map_err(Self::convert_error)?; let result = new_embedded_curve_point(x, y, is_infinite)?; Ok(vec![result]) diff --git a/compiler/noirc_evaluator/src/ssa/ir/dfg/simplify/call/blackbox.rs b/compiler/noirc_evaluator/src/ssa/ir/dfg/simplify/call/blackbox.rs index 1b824702dba..bb46bac15f7 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/dfg/simplify/call/blackbox.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/dfg/simplify/call/blackbox.rs @@ -46,6 +46,7 @@ pub(super) fn simplify_ec_add( &point2_x, &point2_y, &point2_is_infinity, + true, ) else { return SimplifyResult::None; }; @@ -139,6 +140,7 @@ pub(super) fn simplify_msm( &constant_points, &constant_scalars_lo, &constant_scalars_hi, + true, ) else { return SimplifyResult::None; }; diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 6cadc1b11c4..fef89e72263 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -139,7 +139,6 @@ //! enable_side_effects u1 1 //! ... b3 instructions ... //! ``` -use std::sync::Arc; use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; @@ -1019,126 +1018,22 @@ impl<'f> Context<'f> { call_stack: CallStackId, ) -> Vec { match blackbox { - //Issue #5045: We set curve points to g1, g2=2g1 if condition is false, to ensure that they are on the curve, if not the addition may fail. - // If inputs are distinct curve points, then so is their predicate version. - // If inputs are identical (point doubling), then so is their predicate version - // Hence the assumptions for calling EmbeddedCurveAdd are kept by this transformation. BlackBoxFunc::EmbeddedCurveAdd => { - #[cfg(feature = "bn254")] - { - let generators = Self::grumpkin_generators(); - // Convert the generators to ValueId - let generators = generators - .iter() - .map(|v| { - self.inserter.function.dfg.make_constant(*v, NumericType::NativeField) - }) - .collect::>(); - let (point1_x, point2_x) = self.predicate_argument( - &arguments, - &generators, - true, - condition, - call_stack, - ); - let (point1_y, point2_y) = self.predicate_argument( - &arguments, - &generators, - false, - condition, - call_stack, - ); - arguments[0] = point1_x; - arguments[1] = point1_y; - arguments[3] = point2_x; - arguments[4] = point2_y; - } - // TODO: We now use a predicate in order to disable the blackbox on the backend side - // the predicates on the inputs above will be removed once the backend is updated - arguments[5] = self.mul_by_condition(arguments[5], condition, call_stack); + arguments[6] = self.mul_by_condition(arguments[6], condition, call_stack); arguments } - // For MSM, we also ensure the inputs are on the curve if the predicate is false. BlackBoxFunc::MultiScalarMul => { - let (elements, typ) = - self.apply_predicate_to_msm_argument(arguments[0], condition, call_stack); - let instruction = Instruction::MakeArray { elements, typ }; - let array = self.insert_instruction(instruction, call_stack); - arguments[0] = array; - // TODO: We now use a predicate in order to disable the blackbox on the backend side - // the predicates on the inputs above will be removed once the backend is updated arguments[2] = self.mul_by_condition(arguments[2], condition, call_stack); arguments } - // The ECDSA blackbox functions will fail to prove inside barretenberg in the situation where - // the public key doesn't not sit on the relevant curve. - // - // We then replace the public key with the generator point if the constraint is inactive to avoid - // invalid public keys from causing constraints to fail. - BlackBoxFunc::EcdsaSecp256k1 => { - // See: https://github.com/RustCrypto/elliptic-curves/blob/3381a99b6412ef9fa556e32a834e401d569007e3/k256/src/arithmetic/affine.rs#L57-L76 - const GENERATOR_X: [u8; 32] = [ - 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, - 0x87, 0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, - 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98, - ]; - const GENERATOR_Y: [u8; 32] = [ - 0x48, 0x3a, 0xda, 0x77, 0x26, 0xa3, 0xc4, 0x65, 0x5d, 0xa4, 0xfb, 0xfc, 0x0e, - 0x11, 0x08, 0xa8, 0xfd, 0x17, 0xb4, 0x48, 0xa6, 0x85, 0x54, 0x19, 0x9c, 0x47, - 0xd0, 0x8f, 0xfb, 0x10, 0xd4, 0xb8, - ]; - - arguments[0] = self.merge_with_array_constant( - arguments[0], - GENERATOR_X, - condition, - call_stack, - ); - arguments[1] = self.merge_with_array_constant( - arguments[1], - GENERATOR_Y, - condition, - call_stack, - ); - // TODO: We now use a predicate in order to disable the blackbox on the backend side - // the predicates on the inputs above will be removed once the backend is updated - arguments[4] = self.mul_by_condition(arguments[4], condition, call_stack); - arguments - } - BlackBoxFunc::EcdsaSecp256r1 => { - // See: https://github.com/RustCrypto/elliptic-curves/blob/3381a99b6412ef9fa556e32a834e401d569007e3/p256/src/arithmetic.rs#L46-L57 - const GENERATOR_X: [u8; 32] = [ - 0x6b, 0x17, 0xd1, 0xf2, 0xe1, 0x2c, 0x42, 0x47, 0xf8, 0xbc, 0xe6, 0xe5, 0x63, - 0xa4, 0x40, 0xf2, 0x77, 0x03, 0x7d, 0x81, 0x2d, 0xeb, 0x33, 0xa0, 0xf4, 0xa1, - 0x39, 0x45, 0xd8, 0x98, 0xc2, 0x96, - ]; - const GENERATOR_Y: [u8; 32] = [ - 0x4f, 0xe3, 0x42, 0xe2, 0xfe, 0x1a, 0x7f, 0x9b, 0x8e, 0xe7, 0xeb, 0x4a, 0x7c, - 0x0f, 0x9e, 0x16, 0x2b, 0xce, 0x33, 0x57, 0x6b, 0x31, 0x5e, 0xce, 0xcb, 0xb6, - 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5, - ]; - - arguments[0] = self.merge_with_array_constant( - arguments[0], - GENERATOR_X, - condition, - call_stack, - ); - arguments[1] = self.merge_with_array_constant( - arguments[1], - GENERATOR_Y, - condition, - call_stack, - ); - // TODO: We now use a predicate in order to disable the blackbox on the backend side - // the predicates on the inputs above will be removed once the backend is updated + BlackBoxFunc::EcdsaSecp256k1 | BlackBoxFunc::EcdsaSecp256r1 => { arguments[4] = self.mul_by_condition(arguments[4], condition, call_stack); arguments } - // TODO: https://github.com/noir-lang/noir/issues/8998 + // The predicate is injected in ACIRgen so no modification is needed here. BlackBoxFunc::RecursiveAggregation => arguments, // These functions will always be satisfiable no matter the input so no modification is needed. @@ -1157,91 +1052,6 @@ impl<'f> Context<'f> { } } - #[cfg(feature = "bn254")] - fn grumpkin_generators() -> Vec { - let g1_x = FieldElement::from_hex("0x01").unwrap(); - let g1_y = - FieldElement::from_hex("0x02cf135e7506a45d632d270d45f1181294833fc48d823f272c").unwrap(); - let g2_x = FieldElement::from_hex( - "0x06ce1b0827aafa85ddeb49cdaa36306d19a74caa311e13d46d8bc688cdbffffe", - ) - .unwrap(); - let g2_y = FieldElement::from_hex( - "0x1c122f81a3a14964909ede0ba2a6855fc93faf6fa1a788bf467be7e7a43f80ac", - ) - .unwrap(); - vec![g1_x, g1_y, g2_x, g2_y] - } - - /// Merges the given array with a constant array of 32 elements of type `u8`. - /// - /// This is expected to be used for the ECDSA secp256k1 and secp256r1 generators, - /// where the x and y coordinates of the generators are constant values. - fn merge_with_array_constant( - &mut self, - array: ValueId, - constant: [u8; 32], - condition: ValueId, - call_stack: CallStackId, - ) -> ValueId { - let expected_array_type = Type::Array(Arc::new(vec![Type::unsigned(8)]), 32); - let array_type = self.inserter.function.dfg.type_of_value(array); - assert_eq!(array_type, expected_array_type); - - let elements = constant - .iter() - .map(|elem| { - self.inserter - .function - .dfg - .make_constant(FieldElement::from(u32::from(*elem)), NumericType::unsigned(8)) - }) - .collect(); - let constant_array = Instruction::MakeArray { elements, typ: expected_array_type }; - let constant_array_value = self.insert_instruction(constant_array, call_stack); - let not_condition = self.not_instruction(condition, call_stack); - - self.insert_instruction( - Instruction::IfElse { - then_condition: condition, - then_value: array, - else_condition: not_condition, - else_value: constant_array_value, - }, - call_stack, - ) - } - - /// Returns the values corresponding to the given inputs by doing - /// 'if condition {inputs} else {generators}' - /// It is done for the abscissas or the ordinates, depending on 'abscissa'. - /// Inputs are supposed to be of the form: - /// - inputs: (point1_x, point1_y, point1_infinite, point2_x, point2_y, point2_infinite) - /// - generators: [g1_x, g1_y, g2_x, g2_y] - /// - index: true for abscissa, false for ordinate - #[cfg(feature = "bn254")] - fn predicate_argument( - &mut self, - inputs: &[ValueId], - generators: &[ValueId], - abscissa: bool, - condition: ValueId, - call_stack: CallStackId, - ) -> (ValueId, ValueId) { - let index = usize::from(!abscissa); - if inputs[3] == inputs[0] && inputs[4] == inputs[1] { - // Point doubling - let predicated_value = - self.var_or(inputs[index], condition, generators[index], call_stack); - (predicated_value, predicated_value) - } else { - ( - self.var_or(inputs[index], condition, generators[index], call_stack), - self.var_or(inputs[3 + index], condition, generators[2 + index], call_stack), - ) - } - } - /// 'Cast' the 'condition' to 'value' type /// /// This is needed because we need to multiply the condition with several values @@ -1273,66 +1083,6 @@ impl<'f> Context<'f> { call_stack, ) } - - /// When a MSM is done under a predicate, we need to apply the predicate - /// to the is_infinity property of the input points in order to ensure - /// that the points will be on the curve no matter what. - fn apply_predicate_to_msm_argument( - &mut self, - argument: ValueId, - predicate: ValueId, - call_stack: CallStackId, - ) -> (im::Vector, Type) { - let array_typ; - let mut array_with_predicate = im::Vector::new(); - if let Some((array, typ)) = &self.inserter.function.dfg.get_array_constant(argument) { - array_typ = typ.clone(); - for (i, value) in array.clone().iter().enumerate() { - if i % 3 == 2 { - array_with_predicate.push_back(self.var_or_one(*value, predicate, call_stack)); - } else { - array_with_predicate.push_back(*value); - } - } - } else { - unreachable!( - "Expected an array, got {}", - &self.inserter.function.dfg.type_of_value(argument) - ); - }; - - (array_with_predicate, array_typ) - } - - /// Computes: `if condition { var } else { 1 }` - fn var_or_one(&mut self, var: ValueId, condition: ValueId, call_stack: CallStackId) -> ValueId { - let field = self.mul_by_condition(var, condition, call_stack); - let not_condition = self.not_instruction(condition, call_stack); - // Unchecked add because of the values is guaranteed to be 0 - self.insert_instruction( - Instruction::binary(BinaryOp::Add { unchecked: true }, field, not_condition), - call_stack, - ) - } - - /// Computes: `if condition { var } else { other }` - #[cfg(feature = "bn254")] - fn var_or( - &mut self, - var: ValueId, - condition: ValueId, - other: ValueId, - call_stack: CallStackId, - ) -> ValueId { - let field = self.mul_by_condition(var, condition, call_stack); - let not_condition = self.not_instruction(condition, call_stack); - let else_field = self.mul_by_condition(other, not_condition, call_stack); - // Unchecked add because one of the values is guaranteed to be 0 - self.insert_instruction( - Instruction::binary(BinaryOp::Add { unchecked: true }, field, else_field), - call_stack, - ) - } } #[cfg(test)] @@ -2230,24 +1980,6 @@ mod test { "); } - #[test] - #[cfg(feature = "bn254")] - fn test_grumpkin_points() { - use crate::ssa::opt::flatten_cfg::Context; - use acvm::acir::FieldElement; - - let generators = Context::grumpkin_generators(); - let len = generators.len(); - for i in (0..len).step_by(2) { - let gen_x = generators[i]; - let gen_y = generators[i + 1]; - assert!( - gen_y * gen_y - gen_x * gen_x * gen_x + FieldElement::from(17_u128) - == FieldElement::zero() - ); - } - } - #[test] fn use_predicated_value() { let src = " diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs index 7b1061c1621..7570212e9f1 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs @@ -211,7 +211,15 @@ fn embedded_curve_add( let (p2x, p2y, p2inf) = get_embedded_curve_point(point2)?; let (x, y, inf) = Bn254BlackBoxSolver(pedantic_solving) - .ec_add(&p1x, &p1y, &p1inf.into(), &p2x, &p2y, &p2inf.into()) + .ec_add( + &p1x, + &p1y, + &p1inf.into(), + &p2x, + &p2y, + &p2inf.into(), + true, // Predicate is always true as interpreter has control flow to handle false case + ) .map_err(|e| InterpreterError::BlackBoxError(e, location))?; Ok(Value::Array( @@ -248,7 +256,12 @@ fn multi_scalar_mul( } let (x, y, inf) = Bn254BlackBoxSolver(pedantic_solving) - .multi_scalar_mul(&points, &scalars_lo, &scalars_hi) + .multi_scalar_mul( + &points, + &scalars_lo, + &scalars_hi, + true, // Predicate is always true as interpreter has control flow to handle false case + ) .map_err(|e| InterpreterError::BlackBoxError(e, location))?; let embedded_curve_point_typ = match &return_type { diff --git a/tooling/lsp/src/solver.rs b/tooling/lsp/src/solver.rs index 819d3f558d1..b33c3c929ab 100644 --- a/tooling/lsp/src/solver.rs +++ b/tooling/lsp/src/solver.rs @@ -15,11 +15,12 @@ impl BlackBoxFunctionSolver for WrapperSolver { points: &[acvm::FieldElement], scalars_lo: &[acvm::FieldElement], scalars_hi: &[acvm::FieldElement], + predicate: bool, ) -> Result< (acvm::FieldElement, acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError, > { - self.0.multi_scalar_mul(points, scalars_lo, scalars_hi) + self.0.multi_scalar_mul(points, scalars_lo, scalars_hi, predicate) } fn ec_add( @@ -30,11 +31,20 @@ impl BlackBoxFunctionSolver for WrapperSolver { input2_x: &acvm::FieldElement, input2_y: &acvm::FieldElement, input2_infinite: &acvm::FieldElement, + predicate: bool, ) -> Result< (acvm::FieldElement, acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError, > { - self.0.ec_add(input1_x, input1_y, input1_infinite, input2_x, input2_y, input2_infinite) + self.0.ec_add( + input1_x, + input1_y, + input1_infinite, + input2_x, + input2_y, + input2_infinite, + predicate, + ) } fn poseidon2_permutation(