diff --git a/CHANGELOG.md b/CHANGELOG.md index 67f5c8302b..75eee13815 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -160,6 +160,23 @@ ``` +* Add missing hints on whitelist [#1073](https://github.com/lambdaclass/cairo-rs/pull/1073): + + `BuiltinHintProcessor` now supports the following hints: + + ```python + ids.is_250 = 1 if ids.addr < 2**250 else 0 + ``` + + ```python + # Verify the assumptions on the relationship between 2**250, ADDR_BOUND and PRIME. + ADDR_BOUND = ids.ADDR_BOUND % PRIME + assert (2**250 < ADDR_BOUND <= 2**251) and (2 * 2**250 < PRIME) and ( + ADDR_BOUND * 2 > PRIME), \ + 'normalize_address() cannot be used with the current constants.' + ids.is_small = 1 if ids.addr < ADDR_BOUND else 0 + ``` + * Implement hint on ec_recover.json whitelist [#1038](https://github.com/lambdaclass/cairo-rs/pull/1038): `BuiltinHintProcessor` now supports the following hint: diff --git a/cairo_programs/normalize_address.cairo b/cairo_programs/normalize_address.cairo new file mode 100644 index 0000000000..b54c4daac4 --- /dev/null +++ b/cairo_programs/normalize_address.cairo @@ -0,0 +1,32 @@ +%builtins range_check + +from starkware.starknet.common.storage import normalize_address +from starkware.cairo.common.math import assert_250_bit +from starkware.cairo.common.alloc import alloc + +func normalize_address_element_array{range_check_ptr: felt}( + array: felt*, array_length: felt, iterator: felt +) { + if (iterator == array_length) { + return (); + } + normalize_address(array[iterator]); + return normalize_address_element_array(array, array_length, iterator + 1); +} + +func fill_array(array: felt*, base: felt, step: felt, array_length: felt, iterator: felt) { + if (iterator == array_length) { + return (); + } + assert array[iterator] = base + step * iterator; + return fill_array(array, base, step, array_length, iterator + 1); +} + +func main{range_check_ptr: felt}() { + alloc_locals; + tempvar array_length = 10; + let (array: felt*) = alloc(); + fill_array(array, 70000000000000000000, 300000000000000000, array_length, 0); + normalize_address_element_array(array, array_length, 0); + return (); +} diff --git a/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs b/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs index f2a3fe4c48..dbaec30d0d 100644 --- a/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs +++ b/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs @@ -195,6 +195,10 @@ impl HintProcessor for BuiltinHintProcessor { hint_code::ASSERT_250_BITS => { assert_250_bit(vm, &hint_data.ids_data, &hint_data.ap_tracking) } + hint_code::IS_250_BITS => is_250_bits(vm, &hint_data.ids_data, &hint_data.ap_tracking), + hint_code::IS_ADDR_BOUNDED => { + is_addr_bounded(vm, &hint_data.ids_data, &hint_data.ap_tracking, constants) + } hint_code::IS_POSITIVE => is_positive(vm, &hint_data.ids_data, &hint_data.ap_tracking), hint_code::SPLIT_INT_ASSERT_RANGE => { split_int_assert_range(vm, &hint_data.ids_data, &hint_data.ap_tracking) diff --git a/src/hint_processor/builtin_hint_processor/hint_code.rs b/src/hint_processor/builtin_hint_processor/hint_code.rs index 80cde7b140..01046888f3 100644 --- a/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -98,6 +98,15 @@ assert value < ids.UPPER_BOUND, f'{value} is outside of the range [0, 2**250).' # Calculation for the assertion. ids.high, ids.low = divmod(ids.value, ids.SHIFT)"#; +pub const IS_250_BITS: &str = r#"ids.is_250 = 1 if ids.addr < 2**250 else 0"#; + +pub const IS_ADDR_BOUNDED: &str = r#"# Verify the assumptions on the relationship between 2**250, ADDR_BOUND and PRIME. +ADDR_BOUND = ids.ADDR_BOUND % PRIME +assert (2**250 < ADDR_BOUND <= 2**251) and (2 * 2**250 < PRIME) and ( + ADDR_BOUND * 2 > PRIME), \ + 'normalize_address() cannot be used with the current constants.' +ids.is_small = 1 if ids.addr < ADDR_BOUND else 0"#; + pub const SPLIT_INT: &str = r#"memory[ids.output] = res = (int(ids.value) % PRIME) % ids.base assert res < ids.bound, f'split_int(): Limb {res} is out of range.'"#; diff --git a/src/hint_processor/builtin_hint_processor/math_utils.rs b/src/hint_processor/builtin_hint_processor/math_utils.rs index 54a1797cc0..04e291ec97 100644 --- a/src/hint_processor/builtin_hint_processor/math_utils.rs +++ b/src/hint_processor/builtin_hint_processor/math_utils.rs @@ -32,6 +32,8 @@ use num_traits::{Signed, Zero}; use super::hint_utils::get_maybe_relocatable_from_var_name; +const ADDR_BOUND: &str = "starkware.starknet.common.storage.ADDR_BOUND"; + //Implements hint: memory[ap] = 0 if 0 <= (ids.a % PRIME) < range_check_builtin.bound else 1 pub fn is_nn( vm: &mut VirtualMachine, @@ -565,6 +567,65 @@ pub fn assert_250_bit( insert_value_from_var_name("low", low, vm, ids_data, ap_tracking) } +// Implements hint: +// %{ ids.is_250 = 1 if ids.addr < 2**250 else 0 %} +pub fn is_250_bits( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let addr = get_integer_from_var_name("addr", vm, ids_data, ap_tracking)?; + + // Main logic: ids.is_250 = 1 if ids.addr < 2**250 else 0 + let is_250 = Felt252::from((addr.as_ref().bits() <= 250) as u8); + + insert_value_from_var_name("is_250", is_250, vm, ids_data, ap_tracking) +} + +/* +Implements hint: +%{ + # Verify the assumptions on the relationship between 2**250, ADDR_BOUND and PRIME. + ADDR_BOUND = ids.ADDR_BOUND % PRIME + assert (2**250 < ADDR_BOUND <= 2**251) and (2 * 2**250 < PRIME) and ( + ADDR_BOUND * 2 > PRIME), \ + 'normalize_address() cannot be used with the current constants.' + ids.is_small = 1 if ids.addr < ADDR_BOUND else 0 +%} +*/ +pub fn is_addr_bounded( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, + constants: &HashMap, +) -> Result<(), HintError> { + let addr = get_integer_from_var_name("addr", vm, ids_data, ap_tracking)?; + let prime = Felt252::prime(); + + let addr_bound = constants + .get(ADDR_BOUND) + .ok_or(HintError::MissingConstant(ADDR_BOUND))? + .to_biguint(); + + let lower_bound = BigUint::one() << 250_u32; + let upper_bound = BigUint::one() << 251_u32; + + // assert (2**250 < ADDR_BOUND <= 2**251) and (2 * 2**250 < PRIME) and ( + // ADDR_BOUND * 2 > PRIME), \ + // 'normalize_address() cannot be used with the current constants.' + // The second check is not needed, as it's true for the CAIRO_PRIME + if !(lower_bound < addr_bound && addr_bound <= upper_bound && (&addr_bound << 1_u32) > prime) { + return Err(HintError::AssertionFailed( + "normalize_address() cannot be used with the current constants.".to_string(), + )); + } + + // Main logic: ids.is_small = 1 if ids.addr < ADDR_BOUND else 0 + let is_small = Felt252::from((addr.as_ref() < &Felt252::from(addr_bound)) as u8); + + insert_value_from_var_name("is_small", is_small, vm, ids_data, ap_tracking) +} + /* Implements hint: %{ @@ -1771,7 +1832,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_assert_250_bit_valid() { - let hint_code = "from starkware.cairo.common.math_utils import as_int\n\n# Correctness check.\nvalue = as_int(ids.value, PRIME) % PRIME\nassert value < ids.UPPER_BOUND, f'{value} is outside of the range [0, 2**250).'\n\n# Calculation for the assertion.\nids.high, ids.low = divmod(ids.value, ids.SHIFT)"; + let hint_code = hint_code::ASSERT_250_BITS; let mut vm = vm!(); //Initialize fp vm.run_context.fp = 3; @@ -1789,7 +1850,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_assert_250_bit_invalid() { - let hint_code = "from starkware.cairo.common.math_utils import as_int\n\n# Correctness check.\nvalue = as_int(ids.value, PRIME) % PRIME\nassert value < ids.UPPER_BOUND, f'{value} is outside of the range [0, 2**250).'\n\n# Calculation for the assertion.\nids.high, ids.low = divmod(ids.value, ids.SHIFT)"; + let hint_code = hint_code::ASSERT_250_BITS; let mut vm = vm!(); //Initialize fp vm.run_context.fp = 3; @@ -1811,6 +1872,135 @@ mod tests { ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_is_250_bits_valid() { + let hint_code = "ids.is_250 = 1 if ids.addr < 2**250 else 0"; + let mut vm = vm!(); + //Initialize fp + vm.run_context.fp = 2; + //Insert ids into memory + vm.segments = segments![((1, 0), 1152251)]; + //Create ids + let ids_data = ids_data!["addr", "is_250"]; + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + //Check ids.is_low + check_memory![vm.segments.memory, ((1, 1), 1)]; + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_is_250_bits_invalid() { + let hint_code = "ids.is_250 = 1 if ids.addr < 2**250 else 0"; + let mut vm = vm!(); + //Initialize fp + vm.run_context.fp = 2; + //Insert ids into memory + //ids.value + vm.segments = segments![( + (1, 0), + ( + "3618502788666131106986593281521497120414687020801267626233049500247285301248", + 10 + ) + )]; + //Create ids + let ids_data = ids_data!["addr", "is_250"]; + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + //Check ids.is_low + check_memory![vm.segments.memory, ((1, 1), 0)]; + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_is_addr_bounded_ok() { + let hint_code = hint_code::IS_ADDR_BOUNDED; + let mut vm = vm!(); + let addr_bound = felt_str!( + "3618502788666131106986593281521497120414687020801267626233049500247285301000" + ); + //Initialize fp + vm.run_context.fp = 2; + //Insert ids into memory + vm.segments = segments![( + (1, 0), + ( + "1809251394333067160431340899751024102169435851563236335319518532916477952000", + 10 + ) + ),]; + //Create ids + let ids_data = ids_data!["addr", "is_small"]; + //Execute the hint + assert_matches!( + run_hint!( + vm, + ids_data, + hint_code, + exec_scopes_ref!(), + &[(ADDR_BOUND, addr_bound)] + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect() + ), + Ok(()) + ); + //Check ids.is_low + check_memory![vm.segments.memory, ((1, 1), 1)]; + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_is_addr_bounded_assert_fail() { + let hint_code = hint_code::IS_ADDR_BOUNDED; + let mut vm = vm!(); + let addr_bound = Felt252::one(); + //Initialize fp + vm.run_context.fp = 2; + //Insert ids into memory + vm.segments = segments![( + (1, 0), + ( + "3618502788666131106986593281521497120414687020801267626233049500247285301000", + 10 + ) + ),]; + //Create ids + let ids_data = ids_data!["addr", "is_small"]; + //Execute the hint + assert_matches!( + run_hint!( + vm, + ids_data, + hint_code, + exec_scopes_ref!(), + &HashMap::from([(ADDR_BOUND.to_string(), addr_bound)]) + ), + Err(HintError::AssertionFailed(msg)) + if msg == "normalize_address() cannot be used with the current constants." + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_is_addr_bounded_missing_const() { + let hint_code = hint_code::IS_ADDR_BOUNDED; + let mut vm = vm!(); + //Initialize fp + vm.run_context.fp = 2; + //Insert ids into memory + vm.segments = segments![((1, 0), 0),]; + //Create ids + let ids_data = ids_data!["addr", "is_small"]; + //Execute the hint + assert_matches!( + run_hint!(vm, ids_data, hint_code), + Err(HintError::MissingConstant(ADDR_BOUND)) + ); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_split_felt_ok() { diff --git a/src/tests/cairo_run_test.rs b/src/tests/cairo_run_test.rs index 1885053a54..03ce1f25b3 100644 --- a/src/tests/cairo_run_test.rs +++ b/src/tests/cairo_run_test.rs @@ -918,3 +918,10 @@ fn cairo_run_ec_double_slope() { let program_data = include_bytes!("../../cairo_programs/ec_double_slope.json"); run_program_simple_with_memory_holes(program_data.as_slice(), 0); } + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn cairo_run_normalize_address() { + let program_data = include_bytes!("../../cairo_programs/normalize_address.json"); + run_program_simple_with_memory_holes(program_data.as_slice(), 110); +}