diff --git a/cairo_programs/is_quad_residue_test.cairo b/cairo_programs/is_quad_residue_test.cairo new file mode 100644 index 0000000000..4b20e3a2e9 --- /dev/null +++ b/cairo_programs/is_quad_residue_test.cairo @@ -0,0 +1,43 @@ +%builtins output +from starkware.cairo.common.serialize import serialize_word +from starkware.cairo.common.math import is_quad_residue +from starkware.cairo.common.alloc import alloc + +func fill_array(array_start: felt*, iter: felt) -> () { + if (iter == 8) { + return (); + } + assert array_start[iter] = iter; + return fill_array(array_start, iter + 1); +} + +func check_quad_res{output_ptr: felt*}(inputs: felt*, expected: felt*, iter: felt) { + if (iter == 8) { + return (); + } + serialize_word(inputs[iter]); + serialize_word(expected[iter]); + + assert is_quad_residue(inputs[iter]) = expected[iter]; + return check_quad_res(inputs, expected, iter + 1); +} + +func main{output_ptr: felt*}() { + alloc_locals; + let (inputs: felt*) = alloc(); + fill_array(inputs, 0); + + let (expected: felt*) = alloc(); + assert expected[0] = 1; + assert expected[1] = 1; + assert expected[2] = 1; + assert expected[3] = 0; + assert expected[4] = 1; + assert expected[5] = 1; + assert expected[6] = 0; + assert expected[7] = 1; + + check_quad_res(inputs, expected, 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 519c35b312..535db3b7ec 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 @@ -174,6 +174,9 @@ impl HintProcessor for BuiltinHintProcessor { hint_code::ASSERT_NOT_ZERO => { assert_not_zero(vm, &hint_data.ids_data, &hint_data.ap_tracking) } + hint_code::IS_QUAD_RESIDUE => { + is_quad_residue(vm, &hint_data.ids_data, &hint_data.ap_tracking) + } hint_code::VM_EXIT_SCOPE => exit_scope(exec_scopes), hint_code::MEMCPY_ENTER_SCOPE => { memcpy_enter_scope(vm, exec_scopes, &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 5ee26e2deb..aa0472104f 100644 --- a/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -124,6 +124,15 @@ assert -ids.bound <= q < ids.bound, \ ids.biased_q = q + ids.bound"#; +pub(crate) const IS_QUAD_RESIDUE: &str = r#"from starkware.crypto.signature.signature import FIELD_PRIME +from starkware.python.math_utils import div_mod, is_quad_residue, sqrt + +x = ids.x +if is_quad_residue(x, FIELD_PRIME): + ids.y = sqrt(x, FIELD_PRIME) +else: + ids.y = sqrt(div_mod(x, 3, FIELD_PRIME), FIELD_PRIME)"#; + pub(crate) const FIND_ELEMENT: &str = r#"array_ptr = ids.array_ptr elm_size = ids.elm_size assert isinstance(elm_size, int) and elm_size > 0, \ diff --git a/src/hint_processor/builtin_hint_processor/math_utils.rs b/src/hint_processor/builtin_hint_processor/math_utils.rs index d439668097..77a3508b58 100644 --- a/src/hint_processor/builtin_hint_processor/math_utils.rs +++ b/src/hint_processor/builtin_hint_processor/math_utils.rs @@ -3,6 +3,7 @@ use crate::stdlib::{ ops::{Shl, Shr}, prelude::*, }; +use num_traits::{Bounded, Pow}; use crate::utils::CAIRO_PRIME; @@ -551,6 +552,28 @@ pub fn assert_lt_felt( Ok(()) } +pub fn is_quad_residue( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let x = get_integer_from_var_name("x", vm, ids_data, ap_tracking)?; + + if x.is_zero() || x.is_one() { + insert_value_from_var_name("y", x.as_ref().clone(), vm, ids_data, ap_tracking) + } else if Pow::pow(x.as_ref(), &(Felt252::max_value() >> 1)).is_one() { + insert_value_from_var_name("y", crate::math_utils::sqrt(&x), vm, ids_data, ap_tracking) + } else { + insert_value_from_var_name( + "y", + crate::math_utils::sqrt(&(x.as_ref() / Felt252::new(3_i32))), + vm, + ids_data, + ap_tracking, + ) + } +} + fn div_prime_by_bound(bound: Felt252) -> Result { let prime: &BigUint = &CAIRO_PRIME; #[allow(deprecated)] @@ -565,11 +588,13 @@ mod tests { use crate::vm::vm_memory::memory_segments::MemorySegmentManager; use crate::{ any_box, - hint_processor::builtin_hint_processor::{ - builtin_hint_processor_definition::{BuiltinHintProcessor, HintProcessorData}, - hint_code::ASSERT_LE_FELT, + hint_processor::{ + builtin_hint_processor::{ + builtin_hint_processor_definition::{BuiltinHintProcessor, HintProcessorData}, + hint_code, + }, + hint_processor_definition::HintProcessor, }, - hint_processor::hint_processor_definition::HintProcessor, relocatable, types::exec_scope::ExecutionScopes, types::relocatable::Relocatable, @@ -583,6 +608,9 @@ mod tests { use felt::felt_str; use num_traits::Zero; + #[cfg(not(target_arch = "wasm32"))] + use proptest::prelude::*; + #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -724,7 +752,6 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_assert_le_felt_valid() { - let hint_code = ASSERT_LE_FELT; let mut constants = HashMap::new(); constants.insert( "starkware.cairo.common.math.assert_le_felt.PRIME_OVER_3_HIGH".to_string(), @@ -745,7 +772,13 @@ mod tests { let ids_data = ids_data!["a", "b", "range_check_ptr"]; //Execute the hint assert_matches!( - run_hint!(vm, ids_data, hint_code, &mut exec_scopes, &constants), + run_hint!( + vm, + ids_data, + hint_code::ASSERT_LE_FELT, + &mut exec_scopes, + &constants + ), Ok(()) ); //Hint would return an error if the assertion fails @@ -916,7 +949,6 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_is_assert_le_felt_invalid() { - let hint_code = ASSERT_LE_FELT; let mut vm = vm_with_range_check!(); let mut constants = HashMap::new(); constants.insert( @@ -936,7 +968,7 @@ mod tests { add_segments!(vm, 1); //Execute the hint assert_matches!( - run_hint!(vm, ids_data, hint_code, &mut exec_scopes, &constants), + run_hint!(vm, ids_data, hint_code::ASSERT_LE_FELT, &mut exec_scopes, &constants), Err(HintError::NonLeFelt252(x, y)) if x == Felt252::new(2) && y == Felt252::one() ); } @@ -944,7 +976,6 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_is_assert_le_felt_a_is_not_integer() { - let hint_code = ASSERT_LE_FELT; let mut vm = vm_with_range_check!(); let mut constants = HashMap::new(); constants.insert( @@ -963,7 +994,7 @@ mod tests { let ids_data = ids_data!["a", "b", "range_check_ptr"]; //Execute the hint assert_matches!( - run_hint!(vm, ids_data, hint_code, &mut exec_scopes, &constants), + run_hint!(vm, ids_data, hint_code::ASSERT_LE_FELT, &mut exec_scopes, &constants), Err(HintError::IdentifierNotInteger(x, y )) if x == "a" && y == (1,0).into() ); @@ -972,7 +1003,6 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_is_assert_le_felt_b_is_not_integer() { - let hint_code = ASSERT_LE_FELT; let mut vm = vm_with_range_check!(); let mut constants = HashMap::new(); constants.insert( @@ -991,7 +1021,7 @@ mod tests { let ids_data = ids_data!["a", "b", "range_check_builtin"]; //Execute the hint assert_matches!( - run_hint!(vm, ids_data, hint_code, &mut exec_scopes, &constants), + run_hint!(vm, ids_data, hint_code::ASSERT_LE_FELT, &mut exec_scopes, &constants), Err(HintError::IdentifierNotInteger(x, y )) if x == "b" && y == (1,1).into() ); @@ -1973,4 +2003,28 @@ mod tests { )) if x == "b" && y == (1,2).into() ); } + + #[cfg(not(target_arch = "wasm32"))] + proptest! { + #[test] + // Proptest to check is_quad_residue hint function + fn run_is_quad_residue(ref x in "([1-9][0-9]*)") { + let mut vm = vm!(); + vm.run_context.fp = 2; + vm.segments = segments![((1, 1), (&x[..], 10))]; + let ids_data = ids_data!["y", "x"]; + + assert_matches!(run_hint!(vm, ids_data, hint_code::IS_QUAD_RESIDUE), Ok(())); + + let x = &Felt252::parse_bytes(x.as_bytes(), 10).unwrap(); + + if x.is_zero() || x.is_one() { + assert_eq!(vm.get_integer(Relocatable::from((1, 0))).unwrap().as_ref(), x); + } else if x.pow(&(Felt252::max_value() >> 1)).is_one() { + assert_eq!(vm.get_integer(Relocatable::from((1, 0))).unwrap().into_owned(), crate::math_utils::sqrt(x)); + } else { + assert_eq!(vm.get_integer(Relocatable::from((1, 0))).unwrap().into_owned(), crate::math_utils::sqrt(&(x / Felt252::new(3)))); + } + } + } } diff --git a/src/tests/cairo_run_test.rs b/src/tests/cairo_run_test.rs index 96a4c70b69..9c12f8b0e3 100644 --- a/src/tests/cairo_run_test.rs +++ b/src/tests/cairo_run_test.rs @@ -1266,3 +1266,17 @@ fn cairo_run_recover_y() { let program_data = include_bytes!("../../cairo_programs/recover_y.json"); run_program_simple(program_data.as_slice()); } + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn cairo_run_math_integration() { + let program_data = include_bytes!("../../cairo_programs/math_integration_tests.json"); + run_program_simple(program_data.as_slice()); +} + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn cairo_run_is_quad_residue_test() { + let program_data = include_bytes!("../../cairo_programs/is_quad_residue_test.json"); + run_program_simple(program_data.as_slice()); +}