Repository: lambdaclass/cairo-vm (language: rust)

Issue:
Division by zero bug
**Describe the bug**
executing `EC_RECOVER_PRODUCT_MOD` hint with `m = 0` triggers a division by zero panic

**To Reproduce**
Create a cairo program using
```cairo
%{

from starkware.cairo.common.cairo_secp.secp_utils import pack
from starkware.python.math_utils import div_mod, safe_div

a = pack(ids.a, PRIME)
b = pack(ids.b, PRIME)
product = a * b
m = pack(ids.m, PRIME)

value = res = product % m

%}
```
Then initialize m with: `let m = BigInt3(d0=0, d1=0, d2=0);`. Compile and run the program.

**Expected behavior**
The VM shouldn't panic, an error should be raised instead

**What version/commit are you on?**
For example: v0.8.7



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 73364f3374..94710b2a69 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
 
 #### Upcoming Changes
 
+* fix: ec_recover hints no longer panic when divisor is 0 [#1433](https://github.com/lambdaclass/cairo-vm/pull/1433)
+
 * feat: Implement the Serialize and Deserialize traits for the CairoPie struct [#1438](https://github.com/lambdaclass/cairo-vm/pull/1438)
 
 * fix: Using UINT256_HINT no longer panics when b is greater than 2^256 [#1430](https://github.com/lambdaclass/cairo-vm/pull/1430)
diff --git a/cairo_programs/bad_programs/ec_recover_div_mod_n_packed_n_zero.cairo b/cairo_programs/bad_programs/ec_recover_div_mod_n_packed_n_zero.cairo
new file mode 100644
index 0000000000..976739d087
--- /dev/null
+++ b/cairo_programs/bad_programs/ec_recover_div_mod_n_packed_n_zero.cairo
@@ -0,0 +1,26 @@
+struct BigInt3 {
+    d0: felt,
+    d1: felt,
+    d2: felt,
+}
+
+func main() {
+    let x = BigInt3(d0=0, d1=0, d2=1);
+    let s = BigInt3(d0=0, d1=0, d2=1);
+    let n = BigInt3(d0=0, d1=0, d2=0);
+    ec_recover_product(x, s, n);
+    return();
+}
+
+func ec_recover_product(x:BigInt3, s:BigInt3, n:BigInt3) {
+    %{
+        from starkware.cairo.common.cairo_secp.secp_utils import pack
+        from starkware.python.math_utils import div_mod, safe_div
+
+        N = pack(ids.n, PRIME)
+        x = pack(ids.x, PRIME) % N
+        s = pack(ids.s, PRIME) % N
+        value = res = div_mod(x, s, N)
+    %}
+    return();
+}
diff --git a/cairo_programs/bad_programs/ec_recover_product_mod_m_zero.cairo b/cairo_programs/bad_programs/ec_recover_product_mod_m_zero.cairo
new file mode 100644
index 0000000000..986a67a2b8
--- /dev/null
+++ b/cairo_programs/bad_programs/ec_recover_product_mod_m_zero.cairo
@@ -0,0 +1,28 @@
+struct BigInt3 {
+    d0: felt,
+    d1: felt,
+    d2: felt,
+}
+
+func main() {
+    let a = BigInt3(d0=0, d1=0, d2=1);
+    let b = BigInt3(d0=0, d1=0, d2=1);
+    let m = BigInt3(d0=0, d1=0, d2=0);
+    ec_recover_product(a, b, m);
+    return();
+}
+
+func ec_recover_product(a:BigInt3, b:BigInt3, m:BigInt3) {
+    %{
+        from starkware.cairo.common.cairo_secp.secp_utils import pack
+        from starkware.python.math_utils import div_mod, safe_div
+
+        a = pack(ids.a, PRIME)
+        b = pack(ids.b, PRIME)
+        product = a * b
+        m = pack(ids.m, PRIME)
+
+        value = res = product % m
+    %}
+    return();
+}
diff --git a/cairo_programs/bad_programs/uint512_unsigned_div_rem_div_is_zero.cairo b/cairo_programs/bad_programs/uint512_unsigned_div_rem_div_is_zero.cairo
new file mode 100644
index 0000000000..1a6b33a09d
--- /dev/null
+++ b/cairo_programs/bad_programs/uint512_unsigned_div_rem_div_is_zero.cairo
@@ -0,0 +1,57 @@
+
+struct MyStruct1 {
+	d0: felt,
+	d1: felt,
+	d2: felt,
+	d3: felt,
+}
+struct MyStruct0 {
+	low: felt,
+	high: felt,
+}
+func main() {
+	let x =MyStruct1(d0=1, d1=1, d2=1, d3=1);
+	let div = MyStruct0(low=0, high=0);
+	hint_func(x, div);
+	return();
+}
+
+func hint_func(x: MyStruct1, div: MyStruct0) {
+	alloc_locals;
+    local quotient: MyStruct1;
+    local remainder: MyStruct0;
+
+%{
+def split(num: int, num_bits_shift: int, length: int):
+    a = []
+    for _ in range(length):
+        a.append( num & ((1 << num_bits_shift) - 1) )
+        num = num >> num_bits_shift
+    return tuple(a)
+
+def pack(z, num_bits_shift: int) -> int:
+    limbs = (z.low, z.high)
+    return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs))
+
+def pack_extended(z, num_bits_shift: int) -> int:
+    limbs = (z.d0, z.d1, z.d2, z.d3)
+    return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs))
+
+x = pack_extended(ids.x, num_bits_shift = 128)
+div = pack(ids.div, num_bits_shift = 128)
+
+quotient, remainder = divmod(x, div)
+
+quotient_split = split(quotient, num_bits_shift=128, length=4)
+
+ids.quotient.d0 = quotient_split[0]
+ids.quotient.d1 = quotient_split[1]
+ids.quotient.d2 = quotient_split[2]
+ids.quotient.d3 = quotient_split[3]
+
+remainder_split = split(remainder, num_bits_shift=128, length=2)
+ids.remainder.low = remainder_split[0]
+ids.remainder.high = remainder_split[1]
+%}
+	return();
+}
diff --git a/vm/src/hint_processor/builtin_hint_processor/ec_recover.rs b/vm/src/hint_processor/builtin_hint_processor/ec_recover.rs
index 9bf47b77d4..dec7784461 100644
--- a/vm/src/hint_processor/builtin_hint_processor/ec_recover.rs
+++ b/vm/src/hint_processor/builtin_hint_processor/ec_recover.rs
@@ -6,10 +6,11 @@ use crate::{
     hint_processor::hint_processor_definition::HintReference,
     math_utils::div_mod,
     serde::deserialize_program::ApTracking,
-    types::exec_scope::ExecutionScopes,
+    types::{errors::math_errors::MathError, exec_scope::ExecutionScopes},
     vm::{errors::hint_errors::HintError, vm_core::VirtualMachine},
 };
 use num_bigint::BigInt;
+use num_traits::Zero;
 
 /* Implements Hint:
 %{
@@ -29,6 +30,9 @@ pub fn ec_recover_divmod_n_packed(
     ap_tracking: &ApTracking,
 ) -> Result<(), HintError> {
     let n = BigInt3::from_var_name("n", vm, ids_data, ap_tracking)?.pack86();
+    if n.is_zero() {
+        return Err(MathError::DividedByZero.into());
+    }
     let x = BigInt3::from_var_name("x", vm, ids_data, ap_tracking)?
         .pack86()
         .mod_floor(&n);
@@ -89,6 +93,9 @@ pub fn ec_recover_product_mod(
     let a = BigInt3::from_var_name("a", vm, ids_data, ap_tracking)?.pack86();
     let b = BigInt3::from_var_name("b", vm, ids_data, ap_tracking)?.pack86();
     let m = BigInt3::from_var_name("m", vm, ids_data, ap_tracking)?.pack86();
+    if m.is_zero() {
+        return Err(MathError::DividedByZero.into());
+    }
 
     let product = a * b;
     let value = product.mod_floor(&m);
@@ -107,6 +114,9 @@ pub fn ec_recover_product_mod(
 pub fn ec_recover_product_div_m(exec_scopes: &mut ExecutionScopes) -> Result<(), HintError> {
     let product: &BigInt = exec_scopes.get_ref("product")?;
     let m: &BigInt = exec_scopes.get_ref("m")?;
+    if m.is_zero() {
+        return Err(MathError::DividedByZero.into());
+    }
     let value = product.div_floor(m);
     exec_scopes.insert_value("k", value.clone());
     exec_scopes.insert_value("value", value);
@@ -132,6 +142,7 @@ mod tests {
         },
         types::exec_scope::ExecutionScopes,
     };
+    use assert_matches::assert_matches;
 
     #[cfg(target_arch = "wasm32")]
     use wasm_bindgen_test::*;
@@ -174,6 +185,41 @@ mod tests {
         );
     }
 
+    #[test]
+    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+    fn run_ec_recover_divmod_n_is_zero() {
+        let mut vm = vm!();
+        let mut exec_scopes = ExecutionScopes::new();
+
+        vm.run_context.fp = 8;
+        let ids_data = non_continuous_ids_data![("n", -8), ("x", -5), ("s", -2)];
+
+        vm.segments = segments![
+            //n
+            ((1, 0), 0),
+            ((1, 1), 0),
+            ((1, 2), 0),
+            //x
+            ((1, 3), 25),
+            ((1, 4), 0),
+            ((1, 5), 0),
+            //s
+            ((1, 6), 5),
+            ((1, 7), 0),
+            ((1, 8), 0)
+        ];
+
+        assert_matches!(
+            run_hint!(
+                vm,
+                ids_data,
+                hint_code::EC_RECOVER_DIV_MOD_N_PACKED,
+                &mut exec_scopes
+            ),
+            Err(HintError::Math(MathError::DividedByZero))
+        );
+    }
+
     #[test]
     #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
     fn run_ec_recover_sub_a_b_ok() {
@@ -251,6 +297,41 @@ mod tests {
         );
     }
 
+    #[test]
+    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+    fn run_ec_recover_product_mod_m_zero() {
+        let mut vm = vm!();
+        let mut exec_scopes = ExecutionScopes::new();
+
+        vm.run_context.fp = 8;
+        let ids_data = non_continuous_ids_data![("a", -8), ("b", -5), ("m", -2)];
+
+        vm.segments = segments![
+            //a
+            ((1, 0), 60),
+            ((1, 1), 0),
+            ((1, 2), 0),
+            //b
+            ((1, 3), 2),
+            ((1, 4), 0),
+            ((1, 5), 0),
+            //m
+            ((1, 6), 0),
+            ((1, 7), 0),
+            ((1, 8), 0)
+        ];
+
+        assert_matches!(
+            run_hint!(
+                vm,
+                ids_data,
+                hint_code::EC_RECOVER_PRODUCT_MOD,
+                &mut exec_scopes
+            ),
+            Err(HintError::Math(MathError::DividedByZero))
+        );
+    }
+
     #[test]
     #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
     fn run_ec_recover_product_div_m_ok() {
@@ -274,4 +355,25 @@ mod tests {
             [("value", BigInt::from(2)), ("k", BigInt::from(2))]
         );
     }
+
+    #[test]
+    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+    fn run_ec_recover_product_div_m_zero() {
+        let mut vm = vm!();
+        let mut exec_scopes = ExecutionScopes::new();
+        exec_scopes.insert_value("product", BigInt::from(250));
+        exec_scopes.insert_value("m", BigInt::from(0));
+
+        let ids_data = ids_data!["none"];
+
+        assert_matches!(
+            run_hint!(
+                vm,
+                ids_data,
+                hint_code::EC_RECOVER_PRODUCT_DIV_M,
+                &mut exec_scopes
+            ),
+            Err(HintError::Math(MathError::DividedByZero))
+        );
+    }
 }
diff --git a/vm/src/hint_processor/builtin_hint_processor/vrf/fq.rs b/vm/src/hint_processor/builtin_hint_processor/vrf/fq.rs
index 3efac0df3f..91ca9e7f8e 100644
--- a/vm/src/hint_processor/builtin_hint_processor/vrf/fq.rs
+++ b/vm/src/hint_processor/builtin_hint_processor/vrf/fq.rs
@@ -9,11 +9,12 @@ use crate::{
     math_utils::div_mod,
     serde::deserialize_program::ApTracking,
     stdlib::{collections::HashMap, prelude::*},
+    types::errors::math_errors::MathError,
     vm::{errors::hint_errors::HintError, vm_core::VirtualMachine},
 };
 use num_bigint::{BigInt, ToBigInt};
 use num_integer::div_rem;
-use num_traits::One;
+use num_traits::{One, Zero};
 
 /// Implements hint:
 /// ```python
@@ -58,6 +59,9 @@ pub fn uint512_unsigned_div_rem(
 
     // Main logic:
     //  quotient, remainder = divmod(x, div)
+    if div.is_zero() {
+        return Err(MathError::DividedByZero.into());
+    }
     let (quotient, remainder) = div_rem(x, div);
 
     Uint512::from(&quotient).insert_from_var_name("quotient", vm, ids_data, ap_tracking)?;
@@ -161,6 +165,29 @@ mod tests {
         ];
     }
 
+    #[test]
+    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+    fn test_uint512_unsigned_div_rem_div_is_zero() {
+        let hint_code = hint_code::UINT512_UNSIGNED_DIV_REM;
+        let mut vm = vm_with_range_check!();
+
+        vm.segments = segments![
+            ((1, 0), 2363463),
+            ((1, 1), 566795),
+            ((1, 2), 8760799),
+            ((1, 3), 62362634),
+            ((1, 4), 0),
+            ((1, 5), 0)
+        ];
+        // Create hint_data
+        let ids_data =
+            non_continuous_ids_data![("x", 0), ("div", 4), ("quotient", 6), ("remainder", 10)];
+        assert_matches!(
+            run_hint!(vm, ids_data, hint_code, exec_scopes_ref!()),
+            Err(HintError::Math(MathError::DividedByZero))
+        );
+    }
+
     #[test]
     #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
     fn test_inv_mod_p_uint256_ok() {
diff --git a/vm/src/vm/errors/vm_exception.rs b/vm/src/vm/errors/vm_exception.rs
index 77d58ec140..e12b413ff9 100644
--- a/vm/src/vm/errors/vm_exception.rs
+++ b/vm/src/vm/errors/vm_exception.rs
@@ -908,6 +908,123 @@ cairo_programs/bad_programs/bad_usort.cairo:64:5: (pc=0:60)
         assert_eq!(vm_excepction.to_string(), expected_error_string);
     }
 
+    #[test]
+    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+    fn run_bad_ec_recover_product_mod() {
+        #[cfg(feature = "std")]
+        let expected_error_string = r#"cairo_programs/bad_programs/ec_recover_product_mod_m_zero.cairo:16:5: Error at pc=0:21:
+Got an exception while executing a hint: Attempted to divide by zero
+    %{
+    ^^
+Cairo traceback (most recent call last):
+cairo_programs/bad_programs/ec_recover_product_mod_m_zero.cairo:11:5: (pc=0:18)
+    ec_recover_product(a, b, m);
+    ^*************************^
+"#;
+        #[cfg(not(feature = "std"))]
+        let expected_error_string = r#"cairo_programs/bad_programs/ec_recover_product_mod_m_zero.cairo:16:5: Error at pc=0:21:
+Got an exception while executing a hint: Attempted to divide by zero
+Cairo traceback (most recent call last):
+cairo_programs/bad_programs/ec_recover_product_mod_m_zero.cairo:11:5: (pc=0:18)
+"#;
+        let program = Program::from_bytes(
+            include_bytes!(
+                "../../../../cairo_programs/bad_programs/ec_recover_product_mod_m_zero.json"
+            ),
+            Some("main"),
+        )
+        .unwrap();
+
+        let mut hint_processor = BuiltinHintProcessor::new_empty();
+        let mut cairo_runner = cairo_runner!(program, "all_cairo", false);
+        let mut vm = vm!();
+
+        let end = cairo_runner.initialize(&mut vm).unwrap();
+        let error = cairo_runner
+            .run_until_pc(end, &mut vm, &mut hint_processor)
+            .unwrap_err();
+        let vm_excepction = VmException::from_vm_error(&cairo_runner, &vm, error);
+        assert_eq!(vm_excepction.to_string(), expected_error_string);
+    }
+
+    #[test]
+    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+    fn run_bad_ec_recover_div_mod_n_packed_n_zero() {
+        #[cfg(feature = "std")]
+        let expected_error_string = r#"cairo_programs/bad_programs/ec_recover_div_mod_n_packed_n_zero.cairo:16:5: Error at pc=0:21:
+Got an exception while executing a hint: Attempted to divide by zero
+    %{
+    ^^
+Cairo traceback (most recent call last):
+cairo_programs/bad_programs/ec_recover_div_mod_n_packed_n_zero.cairo:11:5: (pc=0:18)
+    ec_recover_product(x, s, n);
+    ^*************************^
+"#;
+        #[cfg(not(feature = "std"))]
+        let expected_error_string = r#"cairo_programs/bad_programs/ec_recover_div_mod_n_packed_n_zero.cairo:16:5: Error at pc=0:21:
+Got an exception while executing a hint: Attempted to divide by zero
+Cairo traceback (most recent call last):
+cairo_programs/bad_programs/ec_recover_div_mod_n_packed_n_zero.cairo:11:5: (pc=0:18)
+"#;
+        let program = Program::from_bytes(
+            include_bytes!(
+                "../../../../cairo_programs/bad_programs/ec_recover_div_mod_n_packed_n_zero.json"
+            ),
+            Some("main"),
+        )
+        .unwrap();
+
+        let mut hint_processor = BuiltinHintProcessor::new_empty();
+        let mut cairo_runner = cairo_runner!(program, "all_cairo", false);
+        let mut vm = vm!();
+
+        let end = cairo_runner.initialize(&mut vm).unwrap();
+        let error = cairo_runner
+            .run_until_pc(end, &mut vm, &mut hint_processor)
+            .unwrap_err();
+        let vm_excepction = VmException::from_vm_error(&cairo_runner, &vm, error);
+        assert_eq!(vm_excepction.to_string(), expected_error_string);
+    }
+
+    #[test]
+    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+    fn run_bad_uint512_unsigned_div_rem() {
+        #[cfg(feature = "std")]
+        let expected_error_string = r#"cairo_programs/bad_programs/uint512_unsigned_div_rem_div_is_zero.cairo:24:1: Error at pc=0:17:
+Got an exception while executing a hint: Attempted to divide by zero
+%{
+^^
+Cairo traceback (most recent call last):
+cairo_programs/bad_programs/uint512_unsigned_div_rem_div_is_zero.cairo:15:2: (pc=0:12)
+	hint_func(x, div);
+ ^***************^
+"#;
+        #[cfg(not(feature = "std"))]
+        let expected_error_string = r#"cairo_programs/bad_programs/uint512_unsigned_div_rem_div_is_zero.cairo:24:1: Error at pc=0:17:
+Got an exception while executing a hint: Attempted to divide by zero
+Cairo traceback (most recent call last):
+cairo_programs/bad_programs/uint512_unsigned_div_rem_div_is_zero.cairo:15:2: (pc=0:12)
+"#;
+        let program = Program::from_bytes(
+            include_bytes!(
+                "../../../../cairo_programs/bad_programs/uint512_unsigned_div_rem_div_is_zero.json"
+            ),
+            Some("main"),
+        )
+        .unwrap();
+
+        let mut hint_processor = BuiltinHintProcessor::new_empty();
+        let mut cairo_runner = cairo_runner!(program, "all_cairo", false);
+        let mut vm = vm!();
+
+        let end = cairo_runner.initialize(&mut vm).unwrap();
+        let error = cairo_runner
+            .run_until_pc(end, &mut vm, &mut hint_processor)
+            .unwrap_err();
+        let vm_excepction = VmException::from_vm_error(&cairo_runner, &vm, error);
+        assert_eq!(vm_excepction.to_string(), expected_error_string);
+    }
+
     #[test]
     #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
     fn run_bad_uint256_sub_check_error_displayed() {


Test patch: