diff --git a/CHANGELOG.md b/CHANGELOG.md index b1af70896b..095db26230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,46 @@ #### Upcoming Changes +* Implement hint on uint384_extension lib [#983](https://github.com/lambdaclass/cairo-rs/pull/983) + + `BuiltinHintProcessor` now supports the following hint: + + ```python + 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.d0, z.d1, z.d2) + 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, z.d4, z.d5) + return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs)) + + a = pack_extended(ids.a, num_bits_shift = 128) + div = pack(ids.div, num_bits_shift = 128) + + quotient, remainder = divmod(a, div) + + quotient_split = split(quotient, num_bits_shift=128, length=6) + + ids.quotient.d0 = quotient_split[0] + ids.quotient.d1 = quotient_split[1] + ids.quotient.d2 = quotient_split[2] + ids.quotient.d3 = quotient_split[3] + ids.quotient.d4 = quotient_split[4] + ids.quotient.d5 = quotient_split[5] + + remainder_split = split(remainder, num_bits_shift=128, length=3) + ids.remainder.d0 = remainder_split[0] + ids.remainder.d1 = remainder_split[1] + ids.remainder.d2 = remainder_split[2] + ``` + * Add missing `\n` character in traceback string [#997](https://github.com/lambdaclass/cairo-rs/pull/997) * BugFix: Add missing `\n` character after traceback lines when the filename is missing ("Unknown Location") diff --git a/cairo_programs/uint384.cairo b/cairo_programs/uint384.cairo index f578b29eb3..d2d5b91f10 100644 --- a/cairo_programs/uint384.cairo +++ b/cairo_programs/uint384.cairo @@ -77,6 +77,20 @@ namespace uint384_lib { return (res, carry_d2); } + // Return true if both integers are equal. + func eq(a: Uint384, b: Uint384) -> (res: felt) { + if (a.d2 != b.d2) { + return (0,); + } + if (a.d1 != b.d1) { + return (0,); + } + if (a.d0 != b.d0) { + return (0,); + } + return (1,); + } + // Subtracts two integers. Returns the result as a 384-bit integer. func sub{range_check_ptr}(a: Uint384, b: Uint384) -> (res: Uint384) { let (b_neg) = neg(b); diff --git a/cairo_programs/uint384_extension.cairo b/cairo_programs/uint384_extension.cairo new file mode 100644 index 0000000000..a4b467811d --- /dev/null +++ b/cairo_programs/uint384_extension.cairo @@ -0,0 +1,304 @@ +// Code taken from https://github.com/NethermindEth/research-basic-Cairo-operations-big-integers/blob/main/lib/uint384_extension.cairo +from starkware.cairo.common.bitwise import bitwise_and, bitwise_or, bitwise_xor +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin +from starkware.cairo.common.math import assert_in_range, assert_le, assert_nn_le, assert_not_zero +from starkware.cairo.common.math_cmp import is_le +from starkware.cairo.common.pow import pow +from starkware.cairo.common.registers import get_ap, get_fp_and_pc +// Import uint384 files +from cairo_programs.uint384 import uint384_lib, Uint384, Uint384_expand, ALL_ONES +// Functions for operating 384-bit integers with 768-bit integers + +// Represents an integer in the range [0, 2^768). +// NOTE: As in Uint256 and Uint384, all functions expect each d_0, d_1, ..., d_5 to be less than 2**128 +struct Uint768 { + d0: felt, + d1: felt, + d2: felt, + d3: felt, + d4: felt, + d5: felt, +} + +const HALF_SHIFT = 2 ** 64; + +namespace uint384_extension_lib { + // Verifies that the given integer is valid. + func check{range_check_ptr}(a: Uint768) { + [range_check_ptr] = a.d0; + [range_check_ptr + 1] = a.d1; + [range_check_ptr + 2] = a.d2; + [range_check_ptr + 3] = a.d3; + [range_check_ptr + 4] = a.d4; + [range_check_ptr + 5] = a.d5; + let range_check_ptr = range_check_ptr + 6; + return (); + } + + // Adds a 768-bit integer and a 384-bit integer. Returns the result as a 768-bit integer and the (1-bit) carry. + func add_uint768_and_uint384{range_check_ptr}(a: Uint768, b: Uint384) -> ( + res: Uint768, carry: felt + ) { + alloc_locals; + + let a_low = Uint384(d0=a.d0, d1=a.d1, d2=a.d2); + let a_high = Uint384(d0=a.d3, d1=a.d4, d2=a.d5); + + let (sum_low, carry0) = uint384_lib.add(a_low, b); + + local res: Uint768; + + res.d0 = sum_low.d0; + res.d1 = sum_low.d1; + res.d2 = sum_low.d2; + + let (a_high_plus_carry, carry1) = uint384_lib.add(a_high, Uint384(carry0, 0, 0)); + + res.d3 = a_high_plus_carry.d0; + res.d4 = a_high_plus_carry.d1; + res.d5 = a_high_plus_carry.d2; + + return (res, carry1); + } + + func mul_uint768_by_uint384_d{range_check_ptr}(a: Uint768, b: Uint384) -> ( + low: Uint768, high: Uint384 + ) { + alloc_locals; + let (a0, a1) = uint384_lib.split_64(a.d0); + let (a2, a3) = uint384_lib.split_64(a.d1); + let (a4, a5) = uint384_lib.split_64(a.d2); + let (a6, a7) = uint384_lib.split_64(a.d3); + let (a8, a9) = uint384_lib.split_64(a.d4); + let (a10, a11) = uint384_lib.split_64(a.d5); + let (b0, b1) = uint384_lib.split_64(b.d0); + let (b2, b3) = uint384_lib.split_64(b.d1); + let (b4, b5) = uint384_lib.split_64(b.d2); + + local B0 = b0 * HALF_SHIFT; + local b12 = b1 + b2 * HALF_SHIFT; + local b34 = b3 + b4 * HALF_SHIFT; + + let (res0, carry) = uint384_lib.split_128(a1 * B0 + a0 * b.d0); + let (res2, carry) = uint384_lib.split_128( + a3 * B0 + a2 * b.d0 + a1 * b12 + a0 * b.d1 + carry + ); + let (res4, carry) = uint384_lib.split_128( + a5 * B0 + a4 * b.d0 + a3 * b12 + a2 * b.d1 + a1 * b34 + a0 * b.d2 + carry + ); + let (res6, carry) = uint384_lib.split_128( + a7 * B0 + a6 * b.d0 + a5 * b12 + a4 * b.d1 + a3 * b34 + a2 * b.d2 + a1 * b5 + carry + ); + let (res8, carry) = uint384_lib.split_128( + a9 * B0 + a8 * b.d0 + a7 * b12 + a6 * b.d1 + a5 * b34 + a4 * b.d2 + a3 * b5 + carry + ); + let (res10, carry) = uint384_lib.split_128( + a11 * B0 + a10 * b.d0 + a9 * b12 + a8 * b.d1 + a7 * b34 + a6 * b.d2 + a5 * b5 + carry + ); + let (res12, carry) = uint384_lib.split_128( + a11 * b12 + a10 * b.d1 + a9 * b34 + a8 * b.d2 + a7 * b5 + carry + ); + let (res14, carry) = uint384_lib.split_128(a11 * b34 + a10 * b.d2 + a9 * b5 + carry); + // let (res16, carry) = split_64(a11 * b5 + carry) + + return ( + low=Uint768(d0=res0, d1=res2, d2=res4, d3=res6, d4=res8, d5=res10), + high=Uint384(d0=res12, d1=res14, d2=a11 * b5 + carry), + ); + } + + // Unsigned integer division between a 768-bit integer and a 384-bit integer. Returns the quotient (768 bits) and the remainder (384 bits). + func unsigned_div_rem_uint768_by_uint384_expand{range_check_ptr}( + a: Uint768, div: Uint384_expand + ) -> (quotient: Uint768, remainder: Uint384) { + alloc_locals; + local quotient: Uint768; + local remainder: Uint384; + + %{ + 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.b01, z.b23, z.b45) + 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, z.d4, z.d5) + return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs)) + + a = pack_extended(ids.a, num_bits_shift = 128) + div = pack(ids.div, num_bits_shift = 128) + + quotient, remainder = divmod(a, div) + + quotient_split = split(quotient, num_bits_shift=128, length=6) + + ids.quotient.d0 = quotient_split[0] + ids.quotient.d1 = quotient_split[1] + ids.quotient.d2 = quotient_split[2] + ids.quotient.d3 = quotient_split[3] + ids.quotient.d4 = quotient_split[4] + ids.quotient.d5 = quotient_split[5] + + remainder_split = split(remainder, num_bits_shift=128, length=3) + ids.remainder.d0 = remainder_split[0] + ids.remainder.d1 = remainder_split[1] + ids.remainder.d2 = remainder_split[2] + %} + check(quotient); + uint384_lib.check(remainder); + + let (res_mul_low: Uint768, res_mul_high: Uint384) = mul_uint768_by_uint384_expanded( + quotient, div + ); + + assert res_mul_high = Uint384(0, 0, 0); + + let (check_val: Uint768, add_carry: felt) = add_uint768_and_uint384(res_mul_low, remainder); + + assert add_carry = 0; + assert check_val = a; + + let div2 = Uint384(div.b01, div.b23, div.b45); + let (is_valid) = uint384_lib.lt(remainder, div2); + assert is_valid = 1; + + return (quotient=quotient, remainder=remainder); + } + + func mul_uint768_by_uint384_expanded{range_check_ptr}(a: Uint768, b: Uint384_expand) -> ( + low: Uint768, high: Uint384 + ) { + let (a0, a1) = uint384_lib.split_64(a.d0); + let (a2, a3) = uint384_lib.split_64(a.d1); + let (a4, a5) = uint384_lib.split_64(a.d2); + let (a6, a7) = uint384_lib.split_64(a.d3); + let (a8, a9) = uint384_lib.split_64(a.d4); + let (a10, a11) = uint384_lib.split_64(a.d5); + + let (res0, carry) = uint384_lib.split_128(a1 * b.B0 + a0 * b.b01); + let (res2, carry) = uint384_lib.split_128( + a3 * b.B0 + a2 * b.b01 + a1 * b.b12 + a0 * b.b23 + carry + ); + let (res4, carry) = uint384_lib.split_128( + a5 * b.B0 + a4 * b.b01 + a3 * b.b12 + a2 * b.b23 + a1 * b.b34 + a0 * b.b45 + carry + ); + let (res6, carry) = uint384_lib.split_128( + a7 * b.B0 + a6 * b.b01 + a5 * b.b12 + a4 * b.b23 + a3 * b.b34 + a2 * b.b45 + a1 * b.b5 + + carry, + ); + let (res8, carry) = uint384_lib.split_128( + a9 * b.B0 + a8 * b.b01 + a7 * b.b12 + a6 * b.b23 + a5 * b.b34 + a4 * b.b45 + a3 * b.b5 + + carry, + ); + let (res10, carry) = uint384_lib.split_128( + a11 * b.B0 + a10 * b.b01 + a9 * b.b12 + a8 * b.b23 + a7 * b.b34 + a6 * b.b45 + a5 * + b.b5 + carry, + ); + let (res12, carry) = uint384_lib.split_128( + a11 * b.b12 + a10 * b.b23 + a9 * b.b34 + a8 * b.b45 + a7 * b.b5 + carry + ); + let (res14, carry) = uint384_lib.split_128(a11 * b.b34 + a10 * b.b45 + a9 * b.b5 + carry); + // let (res16, carry) = split_64(a11 * b.b5 + carry) + + return ( + low=Uint768(d0=res0, d1=res2, d2=res4, d3=res6, d4=res8, d5=res10), + high=Uint384(d0=res12, d1=res14, d2=a11 * b.b5 + carry), + ); + } + + // Unsigned integer division between a 768-bit integer and a 384-bit integer. Returns the quotient (768 bits) and the remainder (384 bits). + // Conforms to EVM specifications: division by 0 yields 0. + func unsigned_div_rem_uint768_by_uint384{range_check_ptr}(a: Uint768, div: Uint384) -> ( + quotient: Uint768, remainder: Uint384 + ) { + alloc_locals; + local quotient: Uint768; + local remainder: Uint384; + + // If div == 0, return (0, 0). + if (div.d0 + div.d1 + div.d2 == 0) { + return (quotient=Uint768(0, 0, 0, 0, 0, 0), remainder=Uint384(0, 0, 0)); + } + + %{ + 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.d0, z.d1, z.d2) + 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, z.d4, z.d5) + return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs)) + + a = pack_extended(ids.a, num_bits_shift = 128) + div = pack(ids.div, num_bits_shift = 128) + + quotient, remainder = divmod(a, div) + + quotient_split = split(quotient, num_bits_shift=128, length=6) + + ids.quotient.d0 = quotient_split[0] + ids.quotient.d1 = quotient_split[1] + ids.quotient.d2 = quotient_split[2] + ids.quotient.d3 = quotient_split[3] + ids.quotient.d4 = quotient_split[4] + ids.quotient.d5 = quotient_split[5] + + remainder_split = split(remainder, num_bits_shift=128, length=3) + ids.remainder.d0 = remainder_split[0] + ids.remainder.d1 = remainder_split[1] + ids.remainder.d2 = remainder_split[2] + %} + check(quotient); + uint384_lib.check(remainder); + + let (res_mul_low: Uint768, res_mul_high: Uint384) = mul_uint768_by_uint384_d(quotient, div); + + assert res_mul_high = Uint384(0, 0, 0); + + let (check_val: Uint768, add_carry: felt) = add_uint768_and_uint384(res_mul_low, remainder); + + assert add_carry = 0; + assert check_val = a; + + let (is_valid) = uint384_lib.lt(remainder, div); + assert is_valid = 1; + + return (quotient=quotient, remainder=remainder); + } +} + +func test_uint384_extension_operations{range_check_ptr}() { + // Test unsigned_div_rem_uint768_by_uint384 + let a = Uint768(1, 2, 3, 4, 5, 6); + let div = Uint384(6, 7, 8); + let (q, r) = uint384_extension_lib.unsigned_div_rem_uint768_by_uint384(a, div); + assert q.d0 = 328319314958874220607240343889245110272; + assert q.d1 = 329648542954659136480144150949525454847; + assert q.d2 = 255211775190703847597530955573826158591; + assert q.d3 = 0; + assert q.d4 = 0; + assert q.d5 = 0; + + assert r.d0 = 71778311772385457136805581255138607105; + assert r.d1 = 147544307532125661892322583691118247938; + assert r.d2 = 3; + return (); +} + +func main{range_check_ptr: felt}() { + test_uint384_extension_operations(); + 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 93534f35f1..76db67251d 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 @@ -79,6 +79,8 @@ use felt::Felt252; #[cfg(feature = "skip_next_instruction_hint")] use crate::hint_processor::builtin_hint_processor::skip_next_instruction::skip_next_instruction; +use super::uint384_extension::unsigned_div_rem_uint768_by_uint384; + pub struct HintProcessorData { pub code: String, pub ap_tracking: ApTracking, @@ -520,6 +522,9 @@ impl HintProcessor for BuiltinHintProcessor { hint_code::UINT384_SQRT => { uint384_sqrt(vm, &hint_data.ids_data, &hint_data.ap_tracking) } + hint_code::UNSIGNED_DIV_REM_UINT768_BY_UINT384 => { + unsigned_div_rem_uint768_by_uint384(vm, &hint_data.ids_data, &hint_data.ap_tracking) + } hint_code::UINT384_SIGNED_NN => { uint384_signed_nn(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 dc9f36b4ab..2e14f1ca65 100644 --- a/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -779,6 +779,40 @@ root_split = split(root, num_bits_shift=128, length=3) ids.root.d0 = root_split[0] ids.root.d1 = root_split[1] ids.root.d2 = root_split[2]"; +pub const UNSIGNED_DIV_REM_UINT768_BY_UINT384: &str = + "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.d0, z.d1, z.d2) + 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, z.d4, z.d5) + return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs)) + +a = pack_extended(ids.a, num_bits_shift = 128) +div = pack(ids.div, num_bits_shift = 128) + +quotient, remainder = divmod(a, div) + +quotient_split = split(quotient, num_bits_shift=128, length=6) + +ids.quotient.d0 = quotient_split[0] +ids.quotient.d1 = quotient_split[1] +ids.quotient.d2 = quotient_split[2] +ids.quotient.d3 = quotient_split[3] +ids.quotient.d4 = quotient_split[4] +ids.quotient.d5 = quotient_split[5] + +remainder_split = split(remainder, num_bits_shift=128, length=3) +ids.remainder.d0 = remainder_split[0] +ids.remainder.d1 = remainder_split[1] +ids.remainder.d2 = remainder_split[2]"; pub const UINT384_SIGNED_NN: &str = "memory[ap] = 1 if 0 <= (ids.a.d2 % PRIME) < 2 ** 127 else 0"; #[cfg(feature = "skip_next_instruction_hint")] diff --git a/src/hint_processor/builtin_hint_processor/mod.rs b/src/hint_processor/builtin_hint_processor/mod.rs index 19348b601c..28a6e2e27e 100644 --- a/src/hint_processor/builtin_hint_processor/mod.rs +++ b/src/hint_processor/builtin_hint_processor/mod.rs @@ -25,4 +25,5 @@ pub mod skip_next_instruction; pub mod squash_dict_utils; pub mod uint256_utils; pub mod uint384; +pub mod uint384_extension; pub mod usort; diff --git a/src/hint_processor/builtin_hint_processor/uint384.rs b/src/hint_processor/builtin_hint_processor/uint384.rs index 3781c0bb85..fd9662c873 100644 --- a/src/hint_processor/builtin_hint_processor/uint384.rs +++ b/src/hint_processor/builtin_hint_processor/uint384.rs @@ -6,6 +6,7 @@ use num_traits::{One, Zero}; use crate::math_utils::isqrt; use crate::stdlib::{borrow::Cow, collections::HashMap, prelude::*}; +use crate::types::errors::math_errors::MathError; use crate::types::relocatable::Relocatable; use crate::{ hint_processor::hint_processor_definition::HintReference, @@ -61,7 +62,7 @@ impl Uint384ExpandReduced<'_> { } } -fn split(num: &BigUint, num_bits_shift: u32) -> [BigUint; T] { +pub(crate) fn split(num: &BigUint, num_bits_shift: u32) -> [BigUint; T] { let mut num = num.clone(); [0; T].map(|_| { let a = &num & &((BigUint::one() << num_bits_shift) - 1_u32); @@ -70,7 +71,7 @@ fn split(num: &BigUint, num_bits_shift: u32) -> [BigUint; T] { }) } -fn pack(num: BigInt3, num_bits_shift: usize) -> BigUint { +pub(crate) fn pack(num: BigInt3, num_bits_shift: usize) -> BigUint { let limbs = [num.d0, num.d1, num.d2]; #[allow(deprecated)] limbs @@ -131,6 +132,9 @@ pub fn uint384_unsigned_div_rem( ); let quotient_addr = get_relocatable_from_var_name("quotient", vm, ids_data, ap_tracking)?; let remainder_addr = get_relocatable_from_var_name("remainder", vm, ids_data, ap_tracking)?; + if div.is_zero() { + return Err(MathError::DividedByZero.into()); + } let (quotient, remainder) = a.div_mod_floor(&div); let quotient_split = split::<3>("ient, 128); for (i, quotient_split) in quotient_split.iter().enumerate() { @@ -248,6 +252,9 @@ pub fn uint384_unsigned_div_rem_expanded( ); let quotient_addr = get_relocatable_from_var_name("quotient", vm, ids_data, ap_tracking)?; let remainder_addr = get_relocatable_from_var_name("remainder", vm, ids_data, ap_tracking)?; + if div.is_zero() { + return Err(MathError::DividedByZero.into()); + } let (quotient, remainder) = a.div_mod_floor(&div); let quotient_split = split::<3>("ient, 128); for (i, quotient_split) in quotient_split.iter().enumerate() { @@ -404,6 +411,33 @@ mod tests { ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_unsigned_div_rem_divide_by_zero() { + let mut vm = vm_with_range_check!(); + //Initialize fp + vm.run_context.fp = 10; + //Create hint_data + let ids_data = + non_continuous_ids_data![("a", -9), ("div", -6), ("quotient", -3), ("remainder", 0)]; + //Insert ids into memory + vm.segments = segments![ + //a + ((1, 1), 83434123481193248), + ((1, 2), 82349321849739284), + ((1, 3), 839243219401320423), + //div + ((1, 4), 0), + ((1, 5), 0), + ((1, 6), 0) + ]; + //Execute the hint + assert_matches!( + run_hint!(vm, ids_data, hint_code::UINT384_UNSIGNED_DIV_REM), + Err(HintError::Math(MathError::DividedByZero)) + ); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_unsigned_div_rem_invalid_memory_insert() { @@ -674,6 +708,37 @@ mod tests { ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_unsigned_div_rem_expand_divide_by_zero() { + let mut vm = vm_with_range_check!(); + //Initialize fp + vm.run_context.fp = 13; + //Create hint_data + let ids_data = + non_continuous_ids_data![("a", -13), ("div", -10), ("quotient", -3), ("remainder", 0)]; + //Insert ids into memory + vm.segments = segments![ + //a + ((1, 0), 83434123481193248), + ((1, 1), 82349321849739284), + ((1, 2), 839243219401320423), + //div + ((1, 3), 0), + ((1, 4), 0), + ((1, 5), 0), + ((1, 6), 0), + ((1, 7), 0), + ((1, 8), 0), + ((1, 9), 0) + ]; + //Execute the hint + assert_matches!( + run_hint!(vm, ids_data, hint_code::UINT384_UNSIGNED_DIV_REM_EXPANDED), + Err(HintError::Math(MathError::DividedByZero)) + ); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_sqrt_ok() { diff --git a/src/hint_processor/builtin_hint_processor/uint384_extension.rs b/src/hint_processor/builtin_hint_processor/uint384_extension.rs new file mode 100644 index 0000000000..083ed8bf72 --- /dev/null +++ b/src/hint_processor/builtin_hint_processor/uint384_extension.rs @@ -0,0 +1,391 @@ +use core::ops::Shl; + +use super::secp::bigint_utils::BigInt3; +use super::uint384::{pack, split}; +use crate::stdlib::{borrow::Cow, collections::HashMap, prelude::*}; +use crate::types::errors::math_errors::MathError; +use crate::{ + hint_processor::{ + builtin_hint_processor::hint_utils::get_relocatable_from_var_name, + hint_processor_definition::HintReference, + }, + serde::deserialize_program::ApTracking, + types::relocatable::Relocatable, + vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, +}; +use felt::Felt252; +use num_bigint::BigUint; +use num_integer::Integer; +use num_traits::Zero; + +#[derive(Debug, PartialEq)] +pub(crate) struct Uint768<'a> { + pub d0: Cow<'a, Felt252>, + pub d1: Cow<'a, Felt252>, + pub d2: Cow<'a, Felt252>, + pub d3: Cow<'a, Felt252>, + pub d4: Cow<'a, Felt252>, + pub d5: Cow<'a, Felt252>, +} + +impl Uint768<'_> { + pub(crate) fn from_base_addr<'a>( + addr: Relocatable, + name: &str, + vm: &'a VirtualMachine, + ) -> Result, HintError> { + Ok(Uint768 { + d0: vm.get_integer(addr).map_err(|_| { + HintError::IdentifierHasNoMember(name.to_string(), "d0".to_string()) + })?, + d1: vm.get_integer((addr + 1)?).map_err(|_| { + HintError::IdentifierHasNoMember(name.to_string(), "d1".to_string()) + })?, + d2: vm.get_integer((addr + 2)?).map_err(|_| { + HintError::IdentifierHasNoMember(name.to_string(), "d2".to_string()) + })?, + d3: vm.get_integer((addr + 3)?).map_err(|_| { + HintError::IdentifierHasNoMember(name.to_string(), "d3".to_string()) + })?, + d4: vm.get_integer((addr + 4)?).map_err(|_| { + HintError::IdentifierHasNoMember(name.to_string(), "d4".to_string()) + })?, + d5: vm.get_integer((addr + 5)?).map_err(|_| { + HintError::IdentifierHasNoMember(name.to_string(), "d5".to_string()) + })?, + }) + } + + pub(crate) fn from_var_name<'a>( + name: &str, + vm: &'a VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, + ) -> Result, HintError> { + let base_addr = get_relocatable_from_var_name(name, vm, ids_data, ap_tracking)?; + Uint768::from_base_addr(base_addr, name, vm) + } +} + +fn pack_extended(num: Uint768, num_bits_shift: usize) -> BigUint { + let limbs = [num.d0, num.d1, num.d2, num.d3, num.d4, num.d5]; + #[allow(deprecated)] + limbs + .into_iter() + .enumerate() + .map(|(idx, value)| value.to_biguint().shl(idx * num_bits_shift)) + .sum() +} + +/* Implements Hint: + %{ + 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.d0, z.d1, z.d2) + 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, z.d4, z.d5) + return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs)) + + a = pack_extended(ids.a, num_bits_shift = 128) + div = pack(ids.div, num_bits_shift = 128) + + quotient, remainder = divmod(a, div) + + quotient_split = split(quotient, num_bits_shift=128, length=6) + + ids.quotient.d0 = quotient_split[0] + ids.quotient.d1 = quotient_split[1] + ids.quotient.d2 = quotient_split[2] + ids.quotient.d3 = quotient_split[3] + ids.quotient.d4 = quotient_split[4] + ids.quotient.d5 = quotient_split[5] + + remainder_split = split(remainder, num_bits_shift=128, length=3) + ids.remainder.d0 = remainder_split[0] + ids.remainder.d1 = remainder_split[1] + ids.remainder.d2 = remainder_split[2] + %} +*/ +pub fn unsigned_div_rem_uint768_by_uint384( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let a = pack_extended(Uint768::from_var_name("a", vm, ids_data, ap_tracking)?, 128); + let div = pack( + BigInt3::from_var_name("div", vm, ids_data, ap_tracking)?, + 128, + ); + let quotient_addr = get_relocatable_from_var_name("quotient", vm, ids_data, ap_tracking)?; + let remainder_addr = get_relocatable_from_var_name("remainder", vm, ids_data, ap_tracking)?; + if div.is_zero() { + return Err(MathError::DividedByZero.into()); + } + let (quotient, remainder) = a.div_mod_floor(&div); + let quotient_split = split::<6>("ient, 128); + for (i, quotient_split) in quotient_split.iter().enumerate() { + vm.insert_value((quotient_addr + i)?, Felt252::from(quotient_split))?; + } + let remainder_split = split::<3>(&remainder, 128); + for (i, remainder_split) in remainder_split.iter().enumerate() { + vm.insert_value((remainder_addr + i)?, Felt252::from(remainder_split))?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::any_box; + use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; + use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData; + use crate::hint_processor::builtin_hint_processor::hint_code; + use crate::hint_processor::hint_processor_definition::HintProcessor; + use crate::types::exec_scope::ExecutionScopes; + use crate::types::relocatable::MaybeRelocatable; + use crate::utils::test_utils::*; + use crate::vm::errors::memory_errors::MemoryError; + use crate::vm::runners::builtin_runner::RangeCheckBuiltinRunner; + use crate::vm::vm_memory::memory::Memory; + use crate::vm::vm_memory::memory_segments::MemorySegmentManager; + use assert_matches::assert_matches; + + use felt::felt_str; + use num_traits::One; + #[cfg(target_arch = "wasm32")] + use wasm_bindgen_test::*; + + #[test] + fn get_uint768_from_base_addr_ok() { + //Uint768(1,2,3,4,5,6) + let mut vm = vm!(); + vm.segments = segments![ + ((1, 0), 1), + ((1, 1), 2), + ((1, 2), 3), + ((1, 3), 4), + ((1, 4), 5), + ((1, 5), 6) + ]; + let x = Uint768::from_base_addr((1, 0).into(), "x", &vm).unwrap(); + assert_eq!(x.d0.as_ref(), &Felt252::one()); + assert_eq!(x.d1.as_ref(), &Felt252::from(2)); + assert_eq!(x.d2.as_ref(), &Felt252::from(3)); + } + + #[test] + fn get_uint768_from_base_addr_missing_member_d0() { + //Uint768(x,2,x,x,x,x) + let mut vm = vm!(); + vm.segments = segments![((0, 1), 2)]; + let r = Uint768::from_base_addr((0, 0).into(), "x", &vm); + assert_matches!(r, Err(HintError::IdentifierHasNoMember(x, y)) if x == "x" && y == "d0") + } + + #[test] + fn get_uint768_from_base_addr_missing_member_d1() { + //Uint768(1,x,x,x,x,x) + let mut vm = vm!(); + vm.segments = segments![((0, 0), 1)]; + let r = Uint768::from_base_addr((0, 0).into(), "x", &vm); + assert_matches!(r, Err(HintError::IdentifierHasNoMember(x, y)) if x == "x" && y == "d1") + } + + #[test] + fn get_uint768_from_base_addr_missing_member_d2() { + //Uint768(1,2,x,x,x,x) + let mut vm = vm!(); + vm.segments = segments![((0, 0), 1), ((0, 1), 2)]; + let r = Uint768::from_base_addr((0, 0).into(), "x", &vm); + assert_matches!(r, Err(HintError::IdentifierHasNoMember(x, y)) if x == "x" && y == "d2") + } + + #[test] + fn get_uint768_from_base_addr_missing_member_d3() { + //Uint768(1,2,3,x,x,x) + let mut vm = vm!(); + vm.segments = segments![((0, 0), 1), ((0, 1), 2), ((0, 2), 3)]; + let r = Uint768::from_base_addr((0, 0).into(), "x", &vm); + assert_matches!(r, Err(HintError::IdentifierHasNoMember(x, y)) if x == "x" && y == "d3") + } + + #[test] + fn get_uint768_from_base_addr_missing_member_d4() { + //Uint768(1,2,3,4,x,x) + let mut vm = vm!(); + vm.segments = segments![((0, 0), 1), ((0, 1), 2), ((0, 2), 3), ((0, 3), 4)]; + let r = Uint768::from_base_addr((0, 0).into(), "x", &vm); + assert_matches!(r, Err(HintError::IdentifierHasNoMember(x, y)) if x == "x" && y == "d4") + } + + #[test] + fn get_uint768_from_base_addr_missing_member_d5() { + //Uint768(1,2,3,4,5,x) + let mut vm = vm!(); + vm.segments = segments![ + ((0, 0), 1), + ((0, 1), 2), + ((0, 2), 3), + ((0, 3), 4), + ((0, 4), 5) + ]; + let r = Uint768::from_base_addr((0, 0).into(), "x", &vm); + assert_matches!(r, Err(HintError::IdentifierHasNoMember(x, y)) if x == "x" && y == "d5") + } + + #[test] + fn get_uint768_from_var_name_ok() { + //Uint768(1,2,3,4,5,6) + let mut vm = vm!(); + vm.set_fp(1); + vm.segments = segments![ + ((1, 0), 1), + ((1, 1), 2), + ((1, 2), 3), + ((1, 3), 4), + ((1, 4), 5), + ((1, 5), 6) + ]; + let ids_data = ids_data!["x"]; + let x = Uint768::from_var_name("x", &vm, &ids_data, &ApTracking::default()).unwrap(); + assert_eq!(x.d0.as_ref(), &Felt252::one()); + assert_eq!(x.d1.as_ref(), &Felt252::from(2)); + assert_eq!(x.d2.as_ref(), &Felt252::from(3)); + } + + #[test] + fn get_uint768_from_var_name_missing_member() { + //Uint768(1,2,x,x,x) + let mut vm = vm!(); + vm.set_fp(1); + vm.segments = segments![((1, 0), 1), ((1, 1), 2)]; + let ids_data = ids_data!["x"]; + let r = Uint768::from_var_name("x", &vm, &ids_data, &ApTracking::default()); + assert_matches!(r, Err(HintError::IdentifierHasNoMember(x, y)) if x == "x" && y == "d2") + } + + #[test] + fn get_uint768_from_var_name_invalid_reference() { + let mut vm = vm!(); + vm.segments = segments![((1, 0), 1), ((1, 1), 2), ((1, 2), 3)]; + let ids_data = ids_data!["x"]; + let r = Uint768::from_var_name("x", &vm, &ids_data, &ApTracking::default()); + assert_matches!(r, Err(HintError::UnknownIdentifier(x)) if x == "x") + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_unsigned_div_rem_ok() { + let mut vm = vm_with_range_check!(); + //Initialize fp + vm.run_context.fp = 17; + //Create hint_data + let ids_data = non_continuous_ids_data![ + ("a", -17), + ("div", -11), + ("quotient", -8), + ("remainder", -2) + ]; + //Insert ids into memory + vm.segments = segments![ + //a + ((1, 0), 1), + ((1, 1), 2), + ((1, 2), 3), + ((1, 3), 4), + ((1, 4), 5), + ((1, 5), 6), + //div + ((1, 6), 6), + ((1, 7), 7), + ((1, 8), 8) + ]; + //Execute the hint + assert_matches!( + run_hint!(vm, ids_data, hint_code::UNSIGNED_DIV_REM_UINT768_BY_UINT384), + Ok(()) + ); + //Check hint memory inserts + check_memory![ + vm.segments.memory, + // quotient + //((1, 9), 328319314958874220607240343889245110272), + //((1, 10), 329648542954659136480144150949525454847), + //((1, 11), 255211775190703847597530955573826158591), + ((1, 12), 0), + ((1, 13), 0), + ((1, 14), 0), + // remainder + ((1, 15), 71778311772385457136805581255138607105), + ((1, 16), 147544307532125661892322583691118247938), + ((1, 17), 3) + ]; + assert_eq!( + vm.segments + .memory + .get_integer((1, 9).into()) + .unwrap() + .as_ref(), + &felt_str!("328319314958874220607240343889245110272") + ); + assert_eq!( + vm.segments + .memory + .get_integer((1, 10).into()) + .unwrap() + .as_ref(), + &felt_str!("329648542954659136480144150949525454847") + ); + assert_eq!( + vm.segments + .memory + .get_integer((1, 11).into()) + .unwrap() + .as_ref(), + &felt_str!("255211775190703847597530955573826158591") + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_unsigned_div_rem_divide_by_zero() { + let mut vm = vm_with_range_check!(); + //Initialize fp + vm.run_context.fp = 17; + //Create hint_data + let ids_data = non_continuous_ids_data![ + ("a", -17), + ("div", -11), + ("quotient", -8), + ("remainder", -2) + ]; + //Insert ids into memory + vm.segments = segments![ + //a + ((1, 0), 1), + ((1, 1), 2), + ((1, 2), 3), + ((1, 3), 4), + ((1, 4), 5), + ((1, 5), 6), + //div + ((1, 6), 0), + ((1, 7), 0), + ((1, 8), 0) + ]; + //Execute the hint + assert_matches!( + run_hint!(vm, ids_data, hint_code::UNSIGNED_DIV_REM_UINT768_BY_UINT384), + Err(HintError::Math(MathError::DividedByZero)) + ); + } +} diff --git a/src/tests/cairo_run_test.rs b/src/tests/cairo_run_test.rs index 5650c38998..4e42fe4b4b 100644 --- a/src/tests/cairo_run_test.rs +++ b/src/tests/cairo_run_test.rs @@ -1302,6 +1302,13 @@ fn cairo_run_uint384() { run_program_simple(program_data.as_slice()); } +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn cairo_run_uint384_extension() { + let program_data = include_bytes!("../../cairo_programs/uint384_extension.json"); + run_program_simple(program_data.as_slice()); +} + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn cairo_run_ed25519_field() { diff --git a/src/types/errors/math_errors.rs b/src/types/errors/math_errors.rs index b78988b336..b614453290 100644 --- a/src/types/errors/math_errors.rs +++ b/src/types/errors/math_errors.rs @@ -21,7 +21,7 @@ pub enum MathError { SafeDivFailBigUint(BigUint, BigUint), #[error("{0} is not divisible by {1}")] SafeDivFailU32(u32, u32), - #[error("Attempted to divide by zero")] + #[error("{0} is not divisible by {1}")] SafeDivFailUsize(usize, usize), #[error("Attempted to divide by zero")] DividedByZero,