cairo_vm/hint_processor/builtin_hint_processor/vrf/
fq.rs

1//! Fq stands for "a finite field of q elements"
2
3use crate::{
4    hint_processor::builtin_hint_processor::uint256_utils::Uint256,
5    hint_processor::{
6        builtin_hint_processor::secp::bigint_utils::Uint512,
7        hint_processor_definition::HintReference,
8    },
9    math_utils::div_mod,
10    serde::deserialize_program::ApTracking,
11    stdlib::{collections::HashMap, prelude::*},
12    types::errors::math_errors::MathError,
13    vm::{errors::hint_errors::HintError, vm_core::VirtualMachine},
14};
15use num_bigint::{BigInt, ToBigInt};
16use num_integer::div_rem;
17use num_traits::{One, Zero};
18
19/// Implements hint:
20/// ```python
21/// def split(num: int, num_bits_shift: int, length: int):
22///     a = []
23///     for _ in range(length):
24///         a.append( num & ((1 << num_bits_shift) - 1) )
25///         num = num >> num_bits_shift
26///     return tuple(a)
27///
28/// def pack(z, num_bits_shift: int) -> int:
29///     limbs = (z.low, z.high)
30///     return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs))
31///
32/// def pack_extended(z, num_bits_shift: int) -> int:
33///     limbs = (z.d0, z.d1, z.d2, z.d3)
34///     return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs))
35///
36/// x = pack_extended(ids.x, num_bits_shift = 128)
37/// div = pack(ids.div, num_bits_shift = 128)
38///
39/// quotient, remainder = divmod(x, div)
40///
41/// quotient_split = split(quotient, num_bits_shift=128, length=4)
42///
43/// ids.quotient.d0 = quotient_split[0]
44/// ids.quotient.d1 = quotient_split[1]
45/// ids.quotient.d2 = quotient_split[2]
46/// ids.quotient.d3 = quotient_split[3]
47///
48/// remainder_split = split(remainder, num_bits_shift=128, length=2)
49/// ids.remainder.low = remainder_split[0]
50/// ids.remainder.high = remainder_split[1]
51/// ```
52pub fn uint512_unsigned_div_rem(
53    vm: &mut VirtualMachine,
54    ids_data: &HashMap<String, HintReference>,
55    ap_tracking: &ApTracking,
56) -> Result<(), HintError> {
57    let x = Uint512::from_var_name("x", vm, ids_data, ap_tracking)?.pack();
58    let div = Uint256::from_var_name("div", vm, ids_data, ap_tracking)?.pack();
59
60    // Main logic:
61    //  quotient, remainder = divmod(x, div)
62    if div.is_zero() {
63        return Err(MathError::DividedByZero.into());
64    }
65    let (quotient, remainder) = div_rem(x, div);
66
67    Uint512::from(&quotient).insert_from_var_name("quotient", vm, ids_data, ap_tracking)?;
68    Uint256::from(&remainder).insert_from_var_name("remainder", vm, ids_data, ap_tracking)
69}
70
71/// Implements hint:
72/// ```python
73/// from starkware.python.math_utils import div_mod
74///
75/// def split(a: int):
76/// return (a & ((1 << 128) - 1), a >> 128)
77///
78/// def pack(z, num_bits_shift: int) -> int:
79/// limbs = (z.low, z.high)
80/// return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs))
81///
82/// a = pack(ids.a, 128)
83/// b = pack(ids.b, 128)
84/// p = pack(ids.p, 128)
85/// # For python3.8 and above the modular inverse can be computed as follows:
86/// # b_inverse_mod_p = pow(b, -1, p)
87/// # Instead we use the python3.7-friendly function div_mod from starkware.python.math_utils
88/// b_inverse_mod_p = div_mod(1, b, p)
89///
90/// b_inverse_mod_p_split = split(b_inverse_mod_p)
91///
92/// ids.b_inverse_mod_p.low = b_inverse_mod_p_split[0]
93/// ids.b_inverse_mod_p.high = b_inverse_mod_p_split[1]
94/// ```
95pub fn inv_mod_p_uint256(
96    vm: &mut VirtualMachine,
97    ids_data: &HashMap<String, HintReference>,
98    ap_tracking: &ApTracking,
99) -> Result<(), HintError> {
100    // 'a' is not used here or in following hints, so we skip it
101    let b = Uint256::from_var_name("b", vm, ids_data, ap_tracking)?
102        .pack()
103        .to_bigint()
104        .unwrap_or_default();
105    let p = Uint256::from_var_name("p", vm, ids_data, ap_tracking)?
106        .pack()
107        .to_bigint()
108        .unwrap_or_default();
109
110    // Main logic:
111    let b_inverse_mod_p = div_mod(&BigInt::one(), &b, &p)?;
112
113    let res = Uint256::from(&b_inverse_mod_p.to_biguint().unwrap_or_default());
114    res.insert_from_var_name("b_inverse_mod_p", vm, ids_data, ap_tracking)
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use crate::any_box;
121    use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor;
122    use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData;
123    use crate::hint_processor::builtin_hint_processor::hint_code;
124    use crate::hint_processor::hint_processor_definition::HintProcessorLogic;
125    use crate::types::errors::math_errors::MathError;
126    use crate::utils::test_utils::*;
127    use assert_matches::assert_matches;
128
129    #[cfg(target_arch = "wasm32")]
130    use wasm_bindgen_test::*;
131
132    #[test]
133    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
134    fn test_uint512_unsigned_div_rem_ok() {
135        let hint_code = hint_code::UINT512_UNSIGNED_DIV_REM;
136        let mut vm = vm_with_range_check!();
137
138        vm.segments = segments![
139            ((1, 0), 2363463),
140            ((1, 1), 566795),
141            ((1, 2), 8760799),
142            ((1, 3), 62362634),
143            ((1, 4), 8340843),
144            ((1, 5), 124152)
145        ];
146        // Create hint_data
147        let ids_data =
148            non_continuous_ids_data![("x", 0), ("div", 4), ("quotient", 6), ("remainder", 10)];
149        assert_matches!(
150            run_hint!(vm, ids_data, hint_code, exec_scopes_ref!()),
151            Ok(())
152        );
153        //Check hint memory inserts
154        check_memory![
155            vm.segments.memory,
156            // quotient
157            ((1, 6), 158847186690949537631480225217589612243),
158            ((1, 7), 105056890940778813909974456334651647691),
159            ((1, 8), 502),
160            ((1, 9), 0),
161            // remainder
162            ((1, 10), ("235556430256711128858231095164527378198", 10)),
163            ((1, 11), 83573),
164        ];
165    }
166
167    #[test]
168    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
169    fn test_uint512_unsigned_div_rem_div_is_zero() {
170        let hint_code = hint_code::UINT512_UNSIGNED_DIV_REM;
171        let mut vm = vm_with_range_check!();
172
173        vm.segments = segments![
174            ((1, 0), 2363463),
175            ((1, 1), 566795),
176            ((1, 2), 8760799),
177            ((1, 3), 62362634),
178            ((1, 4), 0),
179            ((1, 5), 0)
180        ];
181        // Create hint_data
182        let ids_data =
183            non_continuous_ids_data![("x", 0), ("div", 4), ("quotient", 6), ("remainder", 10)];
184        assert_matches!(
185            run_hint!(vm, ids_data, hint_code, exec_scopes_ref!()),
186            Err(HintError::Math(MathError::DividedByZero))
187        );
188    }
189
190    #[test]
191    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
192    fn test_inv_mod_p_uint256_ok() {
193        let hint_code = hint_code::INV_MOD_P_UINT256;
194        let mut vm = vm_with_range_check!();
195
196        vm.segments = segments![
197            ((1, 0), 2363463),
198            ((1, 1), 566795),
199            ((1, 2), 8760799),
200            ((1, 3), 62362634),
201            ((1, 4), 8340842),
202            ((1, 5), 124152)
203        ];
204        // Create hint_data
205        let ids_data =
206            non_continuous_ids_data![("a", 0), ("b", 2), ("p", 4), ("b_inverse_mod_p", 6)];
207        assert_matches!(
208            run_hint!(vm, ids_data, hint_code, exec_scopes_ref!()),
209            Ok(())
210        );
211        //Check hint memory inserts
212        check_memory![
213            vm.segments.memory,
214            // b_inverse_mod_p
215            ((1, 6), ("320134454404400884259649806286603992559", 10)),
216            ((1, 7), 106713),
217        ];
218    }
219
220    #[test]
221    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
222    fn test_inv_mod_p_uint256_igcdex_not_1() {
223        let hint_code = hint_code::INV_MOD_P_UINT256;
224        let mut vm = vm_with_range_check!();
225
226        vm.segments = segments![
227            ((1, 0), 2363463),
228            ((1, 1), 566795),
229            ((1, 2), 1),
230            ((1, 3), 1),
231            ((1, 4), 1),
232            ((1, 5), 1)
233        ];
234        // Create hint_data
235        let ids_data =
236            non_continuous_ids_data![("a", 0), ("b", 2), ("p", 4), ("b_inverse_mod_p", 6)];
237        assert_matches!(
238            run_hint!(vm, ids_data, hint_code, exec_scopes_ref!()),
239            Err(HintError::Math(MathError::DivModIgcdexNotZero(_)))
240        );
241    }
242}