diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index ddafc0bb57..e7c5523725 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -135,6 +135,13 @@ impl AcirContext { self.add_data(constant_data) } + /// Returns the constant represented by the given variable. + /// + /// Panics: if the variable does not represent a constant. + pub(crate) fn constant(&self, var: AcirVar) -> FieldElement { + self.vars[&var].as_constant().expect("ICE - expected the variable to be a constant value") + } + /// Adds a Variable to the context, whose exact value is resolved at /// runtime. pub(crate) fn add_variable(&mut self) -> AcirVar { diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index a1c96a3cd2..03fb4db73c 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -1712,6 +1712,9 @@ impl Context { Ok(Self::convert_vars_to_values(vars, dfg, result_ids)) } + Intrinsic::ApplyRangeConstraint => { + unreachable!("ICE: `Intrinsic::ApplyRangeConstraint` calls should be transformed into an `Instruction::RangeCheck`"); + } Intrinsic::ToRadix(endian) => { let field = self.convert_value(arguments[0], dfg).into_var()?; let radix = self.convert_value(arguments[1], dfg).into_var()?; diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index f7875a73f6..457fe41de9 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -40,6 +40,7 @@ pub(crate) enum Intrinsic { SlicePopFront, SliceInsert, SliceRemove, + ApplyRangeConstraint, StrAsBytes, ToBits(Endian), ToRadix(Endian), @@ -61,6 +62,7 @@ impl std::fmt::Display for Intrinsic { Intrinsic::SliceInsert => write!(f, "slice_insert"), Intrinsic::SliceRemove => write!(f, "slice_remove"), Intrinsic::StrAsBytes => write!(f, "str_as_bytes"), + Intrinsic::ApplyRangeConstraint => write!(f, "apply_range_constraint"), Intrinsic::ToBits(Endian::Big) => write!(f, "to_be_bits"), Intrinsic::ToBits(Endian::Little) => write!(f, "to_le_bits"), Intrinsic::ToRadix(Endian::Big) => write!(f, "to_be_radix"), @@ -78,7 +80,7 @@ impl Intrinsic { /// If there are no side effects then the `Intrinsic` can be removed if the result is unused. pub(crate) fn has_side_effects(&self) -> bool { match self { - Intrinsic::AssertConstant => true, + Intrinsic::AssertConstant | Intrinsic::ApplyRangeConstraint => true, Intrinsic::Sort | Intrinsic::ArrayLen @@ -106,6 +108,7 @@ impl Intrinsic { "arraysort" => Some(Intrinsic::Sort), "array_len" => Some(Intrinsic::ArrayLen), "assert_constant" => Some(Intrinsic::AssertConstant), + "apply_range_constraint" => Some(Intrinsic::ApplyRangeConstraint), "slice_push_back" => Some(Intrinsic::SlicePushBack), "slice_push_front" => Some(Intrinsic::SlicePushFront), "slice_pop_back" => Some(Intrinsic::SlicePopBack), diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 146a4a8f12..68c7177f06 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -233,6 +233,20 @@ pub(super) fn simplify_call( SimplifyResult::None } } + Intrinsic::ApplyRangeConstraint => { + let value = arguments[0]; + let max_bit_size = dfg.get_numeric_constant(arguments[1]); + if let Some(max_bit_size) = max_bit_size { + let max_bit_size = max_bit_size.to_u128() as u32; + SimplifyResult::SimplifiedToInstruction(Instruction::RangeCheck { + value, + max_bit_size, + assert_message: Some("call to assert_max_bit_size".to_owned()), + }) + } else { + SimplifyResult::None + } + } Intrinsic::BlackBox(bb_func) => simplify_black_box_func(bb_func, arguments, dfg), Intrinsic::Sort => simplify_sort(dfg, arguments), Intrinsic::AsField => { diff --git a/noir_stdlib/src/field.nr b/noir_stdlib/src/field.nr index b4cb9b64e3..df00b3eb65 100644 --- a/noir_stdlib/src/field.nr +++ b/noir_stdlib/src/field.nr @@ -15,6 +15,15 @@ impl Field { #[builtin(to_be_bits)] fn __to_be_bits(_self: Self, _bit_size: u32) -> [u1] {} + #[builtin(apply_range_constraint)] + fn __assert_max_bit_size(_self: Self, _bit_size: u32) {} + + pub fn assert_max_bit_size(self: Self, bit_size: u32) { + crate::assert_constant(bit_size); + assert(bit_size < modulus_num_bits() as u32); + self.__assert_max_bit_size(bit_size); + } + pub fn to_le_bytes(self: Self, byte_size: u32) -> [u8] { self.to_le_radix(256, byte_size) } diff --git a/test_programs/execution_success/unsafe_range_constraint/Nargo.toml b/test_programs/execution_success/unsafe_range_constraint/Nargo.toml new file mode 100644 index 0000000000..8714d95ed5 --- /dev/null +++ b/test_programs/execution_success/unsafe_range_constraint/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unsafe_range_constraint" +version = "0.1.0" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/execution_success/unsafe_range_constraint/Prover.toml b/test_programs/execution_success/unsafe_range_constraint/Prover.toml new file mode 100644 index 0000000000..07890234a1 --- /dev/null +++ b/test_programs/execution_success/unsafe_range_constraint/Prover.toml @@ -0,0 +1 @@ +x = "3" diff --git a/test_programs/execution_success/unsafe_range_constraint/src/main.nr b/test_programs/execution_success/unsafe_range_constraint/src/main.nr new file mode 100644 index 0000000000..ead5613bcc --- /dev/null +++ b/test_programs/execution_success/unsafe_range_constraint/src/main.nr @@ -0,0 +1,5 @@ +// Test that we can apply a range constraint to a field using +// a builtin. +fn main(x: Field) { + x.assert_max_bit_size(48); +}