Skip to content

Commit 6e32e71

Browse files
fmolettaOppen
andauthored
feat: Implement hints on field_arithmetic lib (Part 2) (#1004)
* Add hint code for UINT348_UNSIGNED_DIV_REM * Add file for uint348 files * Add pack & split for uint348 * Move comment * Implement uint348_unsigned_div_rem hint * Add integration test * Add integration test * Add unit tests * Add hint on split_128 * Test split_128 hint * Add add_no_uint384_hint * Fix hint + add tests * Add hint code for UINT348_UNSIGNED_DIV_REM_EXPAND * Msc fixes * Add integration test * Reduce Uint384_expand representation to the 3 used limbs * Add unit test * Add hint code for UINT384_SQRT * Add implementation for hint on sqrt * Integration test * Add unit tests * Fix missing directive * Run cairo-format * Add changelog entry * Spelling * Add hint code + Uint768 type * Implement hint unsigned_div_rem_uint768_by_uint384 * Update src/hint_processor/builtin_hint_processor/uint384.rs Co-authored-by: Mario Rugiero <[email protected]> * Update src/hint_processor/builtin_hint_processor/uint384.rs Co-authored-by: Mario Rugiero <[email protected]> * Update src/hint_processor/builtin_hint_processor/uint384.rs Co-authored-by: Mario Rugiero <[email protected]> * Make hint code more readable * Add integration test * Add test * Add unit test * Add changelog entry + fmt * Fix plural * cargo fmt * Add first draft of get_square_root * Fix test * Fix syntax * Fix test * Add necessary lib fns * fix fmt * Fix test value * Add test program * Add hint to execute_hint * Fix wrong hint being tested * Implement sqrt * Add test fix file * Fix _sqrt_mod_tonelli_shanks implementation * Expand integration test * Add unit test * Add proptests * Fix merge conflict * Fix merge conflict * Add changelog entry * Use no-std compatible rng when std is not enabled * Clippy * Use seeded rng instead of from_entropy * Catch potential zero divison errors * Implement hint on div * Expand field_arithmetic integration test * Expand field_arithmetic integration test * Add test + fix hint * Fix merge conflict * Use mul_inv instead of div_mod * Add Changelog entry * Add unit tests * remove unused feature * Fix test value * Update cairo_programs/field_arithmetic.cairo Co-authored-by: Mario Rugiero <[email protected]> * Increase number of memory holes for field_arithmetic test --------- Co-authored-by: Mario Rugiero <[email protected]>
1 parent 31a05d9 commit 6e32e71

File tree

7 files changed

+295
-5
lines changed

7 files changed

+295
-5
lines changed

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,42 @@
22

33
#### Upcoming Changes
44

5+
* Implement hints on field_arithmetic lib (Part 2) [#1004](https://github.com/lambdaclass/cairo-rs/pull/1004)
6+
7+
`BuiltinHintProcessor` now supports the following hint:
8+
9+
```python
10+
%{
11+
from starkware.python.math_utils import div_mod
12+
13+
def split(num: int, num_bits_shift: int, length: int):
14+
a = []
15+
for _ in range(length):
16+
a.append( num & ((1 << num_bits_shift) - 1) )
17+
num = num >> num_bits_shift
18+
return tuple(a)
19+
20+
def pack(z, num_bits_shift: int) -> int:
21+
limbs = (z.d0, z.d1, z.d2)
22+
return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs))
23+
24+
a = pack(ids.a, num_bits_shift = 128)
25+
b = pack(ids.b, num_bits_shift = 128)
26+
p = pack(ids.p, num_bits_shift = 128)
27+
# For python3.8 and above the modular inverse can be computed as follows:
28+
# b_inverse_mod_p = pow(b, -1, p)
29+
# Instead we use the python3.7-friendly function div_mod from starkware.python.math_utils
30+
b_inverse_mod_p = div_mod(1, b, p)
31+
32+
33+
b_inverse_mod_p_split = split(b_inverse_mod_p, num_bits_shift=128, length=3)
34+
35+
ids.b_inverse_mod_p.d0 = b_inverse_mod_p_split[0]
36+
ids.b_inverse_mod_p.d1 = b_inverse_mod_p_split[1]
37+
ids.b_inverse_mod_p.d2 = b_inverse_mod_p_split[2]
38+
%}
39+
```
40+
541
* Optimizations for hash builtin [#1029](https://github.com/lambdaclass/cairo-rs/pull/1029):
642
* Track the verified addresses by offset in a `Vec<bool>` rather than storing the address in a `Vec<Relocatable>`
743

cairo_programs/field_arithmetic.cairo

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,51 @@ namespace field_arithmetic {
124124
}
125125
}
126126

127+
// Computes a * b^{-1} modulo p
128+
// NOTE: The modular inverse of b modulo p is computed in a hint and verified outside the hint with a multiplicaiton
129+
func div{range_check_ptr}(a: Uint384, b: Uint384, p: Uint384) -> (res: Uint384) {
130+
alloc_locals;
131+
local b_inverse_mod_p: Uint384;
132+
%{
133+
from starkware.python.math_utils import div_mod
134+
135+
def split(num: int, num_bits_shift: int, length: int):
136+
a = []
137+
for _ in range(length):
138+
a.append( num & ((1 << num_bits_shift) - 1) )
139+
num = num >> num_bits_shift
140+
return tuple(a)
141+
142+
def pack(z, num_bits_shift: int) -> int:
143+
limbs = (z.d0, z.d1, z.d2)
144+
return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs))
145+
146+
a = pack(ids.a, num_bits_shift = 128)
147+
b = pack(ids.b, num_bits_shift = 128)
148+
p = pack(ids.p, num_bits_shift = 128)
149+
# For python3.8 and above the modular inverse can be computed as follows:
150+
# b_inverse_mod_p = pow(b, -1, p)
151+
# Instead we use the python3.7-friendly function div_mod from starkware.python.math_utils
152+
b_inverse_mod_p = div_mod(1, b, p)
153+
154+
155+
b_inverse_mod_p_split = split(b_inverse_mod_p, num_bits_shift=128, length=3)
156+
157+
ids.b_inverse_mod_p.d0 = b_inverse_mod_p_split[0]
158+
ids.b_inverse_mod_p.d1 = b_inverse_mod_p_split[1]
159+
ids.b_inverse_mod_p.d2 = b_inverse_mod_p_split[2]
160+
%}
161+
uint384_lib.check(b_inverse_mod_p);
162+
let (b_times_b_inverse) = mul(b, b_inverse_mod_p, p);
163+
assert b_times_b_inverse = Uint384(1, 0, 0);
164+
165+
let (res: Uint384) = mul(a, b_inverse_mod_p, p);
166+
return (res,);
167+
}
127168
}
128169

129170
func test_field_arithmetics_extension_operations{range_check_ptr, bitwise_ptr: BitwiseBuiltin*}() {
171+
alloc_locals;
130172
// Test get_square
131173

132174
//Small prime
@@ -162,6 +204,25 @@ func test_field_arithmetics_extension_operations{range_check_ptr, bitwise_ptr: B
162204
assert r_c.d1 = 0;
163205
assert r_c.d2 = 0;
164206

207+
// Test div
208+
// Small inputs
209+
let a = Uint384(25, 0, 0);
210+
let a_div = Uint384(5, 0, 0);
211+
let a_p = Uint384(31, 0, 0);
212+
let (a_r) = field_arithmetic.div(a, a_div, a_p);
213+
assert a_r.d0 = 5;
214+
assert a_r.d1 = 0;
215+
assert a_r.d2 = 0;
216+
217+
// Cairo Prime
218+
let b = Uint384(1, 0, 5044639098474805171426);
219+
let b_div = Uint384(1, 0, 2);
220+
let b_p = Uint384(1, 0, 604462909807314605178880);
221+
let (b_r) = field_arithmetic.div(b, b_div, b_p);
222+
assert b_r.d0 = 280171807489444591652763463227596156607;
223+
assert b_r.d1 = 122028556426724038784654414222572127555;
224+
assert b_r.d2 = 410614585309032623322981;
225+
165226
return ();
166227
}
167228

src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ use felt::Felt252;
8484
#[cfg(feature = "skip_next_instruction_hint")]
8585
use crate::hint_processor::builtin_hint_processor::skip_next_instruction::skip_next_instruction;
8686

87+
use super::field_arithmetic::uint384_div;
8788
use super::vrf::inv_mod_p_uint512::inv_mod_p_uint512;
8889

8990
pub struct HintProcessorData {
@@ -562,6 +563,7 @@ impl HintProcessor for BuiltinHintProcessor {
562563
hint_code::UINT384_SIGNED_NN => {
563564
uint384_signed_nn(vm, &hint_data.ids_data, &hint_data.ap_tracking)
564565
}
566+
hint_code::UINT384_DIV => uint384_div(vm, &hint_data.ids_data, &hint_data.ap_tracking),
565567
hint_code::UINT256_MUL_DIV_MOD => {
566568
uint256_mul_div_mod(vm, &hint_data.ids_data, &hint_data.ap_tracking)
567569
}

src/hint_processor/builtin_hint_processor/field_arithmetic.rs

Lines changed: 166 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use felt::Felt252;
2-
use num_bigint::BigUint;
2+
use num_bigint::{BigUint, ToBigInt};
3+
use num_integer::Integer;
34
use num_traits::Zero;
45

5-
use crate::math_utils::{is_quad_residue, sqrt_prime_power};
6+
use crate::math_utils::{is_quad_residue, mul_inv, sqrt_prime_power};
67
use crate::serde::deserialize_program::ApTracking;
78
use crate::stdlib::{collections::HashMap, prelude::*};
9+
use crate::types::errors::math_errors::MathError;
810
use crate::vm::errors::hint_errors::HintError;
911
use crate::{
1012
hint_processor::hint_processor_definition::HintReference, vm::vm_core::VirtualMachine,
@@ -112,6 +114,68 @@ pub fn get_square_root(
112114

113115
Ok(())
114116
}
117+
118+
/* Implements Hint:
119+
%{
120+
from starkware.python.math_utils import div_mod
121+
122+
def split(num: int, num_bits_shift: int, length: int):
123+
a = []
124+
for _ in range(length):
125+
a.append( num & ((1 << num_bits_shift) - 1) )
126+
num = num >> num_bits_shift
127+
return tuple(a)
128+
129+
def pack(z, num_bits_shift: int) -> int:
130+
limbs = (z.d0, z.d1, z.d2)
131+
return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs))
132+
133+
a = pack(ids.a, num_bits_shift = 128)
134+
b = pack(ids.b, num_bits_shift = 128)
135+
p = pack(ids.p, num_bits_shift = 128)
136+
# For python3.8 and above the modular inverse can be computed as follows:
137+
# b_inverse_mod_p = pow(b, -1, p)
138+
# Instead we use the python3.7-friendly function div_mod from starkware.python.math_utils
139+
b_inverse_mod_p = div_mod(1, b, p)
140+
141+
142+
b_inverse_mod_p_split = split(b_inverse_mod_p, num_bits_shift=128, length=3)
143+
144+
ids.b_inverse_mod_p.d0 = b_inverse_mod_p_split[0]
145+
ids.b_inverse_mod_p.d1 = b_inverse_mod_p_split[1]
146+
ids.b_inverse_mod_p.d2 = b_inverse_mod_p_split[2]
147+
%}
148+
*/
149+
pub fn uint384_div(
150+
vm: &mut VirtualMachine,
151+
ids_data: &HashMap<String, HintReference>,
152+
ap_tracking: &ApTracking,
153+
) -> Result<(), HintError> {
154+
// Note: ids.a is not used here, nor is it used by following hints, so we dont need to extract it.
155+
let b = pack(BigInt3::from_var_name("b", vm, ids_data, ap_tracking)?, 128)
156+
.to_bigint()
157+
.unwrap_or_default();
158+
let p = pack(BigInt3::from_var_name("p", vm, ids_data, ap_tracking)?, 128)
159+
.to_bigint()
160+
.unwrap_or_default();
161+
let b_inverse_mod_p_addr =
162+
get_relocatable_from_var_name("b_inverse_mod_p", vm, ids_data, ap_tracking)?;
163+
if b.is_zero() {
164+
return Err(MathError::DividedByZero.into());
165+
}
166+
let b_inverse_mod_p = mul_inv(&b, &p)
167+
.mod_floor(&p)
168+
.to_biguint()
169+
.unwrap_or_default();
170+
let b_inverse_mod_p_split = split::<3>(&b_inverse_mod_p, 128);
171+
for (i, b_inverse_mod_p_split) in b_inverse_mod_p_split.iter().enumerate() {
172+
vm.insert_value(
173+
(b_inverse_mod_p_addr + i)?,
174+
Felt252::from(b_inverse_mod_p_split),
175+
)?;
176+
}
177+
Ok(())
178+
}
115179
#[cfg(test)]
116180
mod tests {
117181
use super::*;
@@ -268,4 +332,104 @@ mod tests {
268332
((1, 15), 0)
269333
];
270334
}
335+
336+
#[test]
337+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
338+
fn run_uint384_div_ok() {
339+
let mut vm = vm_with_range_check!();
340+
//Initialize fp
341+
vm.run_context.fp = 11;
342+
//Create hint_data
343+
let ids_data =
344+
non_continuous_ids_data![("a", -11), ("b", -8), ("p", -5), ("b_inverse_mod_p", -2)];
345+
//Insert ids into memory
346+
vm.segments = segments![
347+
//a
348+
((1, 0), 25),
349+
((1, 1), 0),
350+
((1, 2), 0),
351+
//b
352+
((1, 3), 5),
353+
((1, 4), 0),
354+
((1, 5), 0),
355+
//p
356+
((1, 6), 31),
357+
((1, 7), 0),
358+
((1, 8), 0)
359+
];
360+
//Execute the hint
361+
assert_matches!(run_hint!(vm, ids_data, hint_code::UINT384_DIV), Ok(()));
362+
//Check hint memory inserts
363+
check_memory![
364+
vm.segments.memory,
365+
// b_inverse_mod_p
366+
((1, 9), 25),
367+
((1, 10), 0),
368+
((1, 11), 0)
369+
];
370+
}
371+
372+
#[test]
373+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
374+
fn run_uint384_div_b_is_zero() {
375+
let mut vm = vm_with_range_check!();
376+
//Initialize fp
377+
vm.run_context.fp = 11;
378+
//Create hint_data
379+
let ids_data =
380+
non_continuous_ids_data![("a", -11), ("b", -8), ("p", -5), ("b_inverse_mod_p", -2)];
381+
//Insert ids into memory
382+
vm.segments = segments![
383+
//a
384+
((1, 0), 25),
385+
((1, 1), 0),
386+
((1, 2), 0),
387+
//b
388+
((1, 3), 0),
389+
((1, 4), 0),
390+
((1, 5), 0),
391+
//p
392+
((1, 6), 31),
393+
((1, 7), 0),
394+
((1, 8), 0)
395+
];
396+
//Execute the hint
397+
assert_matches!(
398+
run_hint!(vm, ids_data, hint_code::UINT384_DIV),
399+
Err(HintError::Math(MathError::DividedByZero))
400+
);
401+
}
402+
403+
#[test]
404+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
405+
fn run_uint384_div_inconsistent_memory() {
406+
let mut vm = vm_with_range_check!();
407+
//Initialize fp
408+
vm.run_context.fp = 11;
409+
//Create hint_data
410+
let ids_data =
411+
non_continuous_ids_data![("a", -11), ("b", -8), ("p", -5), ("b_inverse_mod_p", -2)];
412+
//Insert ids into memory
413+
vm.segments = segments![
414+
//a
415+
((1, 0), 25),
416+
((1, 1), 0),
417+
((1, 2), 0),
418+
//b
419+
((1, 3), 5),
420+
((1, 4), 0),
421+
((1, 5), 0),
422+
//p
423+
((1, 6), 31),
424+
((1, 7), 0),
425+
((1, 8), 0),
426+
//b_inverse_mod_p
427+
((1, 9), 0)
428+
];
429+
//Execute the hint
430+
assert_matches!(
431+
run_hint!(vm, ids_data, hint_code::UINT384_DIV),
432+
Err(HintError::Memory(MemoryError::InconsistentMemory(_, _, _)))
433+
);
434+
}
271435
}

src/hint_processor/builtin_hint_processor/hint_code.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,7 @@ s = pack(ids.s, PRIME) % N
749749
value = res = div_mod(x, s, N)";
750750
pub(crate) const XS_SAFE_DIV: &str = "value = k = safe_div(res * s - x, N)";
751751

752-
// The following hints support the lib https://github.com/NethermindEth/research-basic-Cairo-operations-big-integers/blob/main/lib/uint384.cairo
752+
// The following hints support the lib https://github.com/NethermindEth/research-basic-Cairo-operations-big-integers/blob/main/lib
753753
pub const UINT384_UNSIGNED_DIV_REM: &str = "def split(num: int, num_bits_shift: int, length: int):
754754
a = []
755755
for _ in range(length):
@@ -914,6 +914,33 @@ ids.sqrt_x.d2 = split_root_x[2]
914914
ids.sqrt_gx.d0 = split_root_gx[0]
915915
ids.sqrt_gx.d1 = split_root_gx[1]
916916
ids.sqrt_gx.d2 = split_root_gx[2]";
917+
pub const UINT384_DIV: &str = "from starkware.python.math_utils import div_mod
918+
919+
def split(num: int, num_bits_shift: int, length: int):
920+
a = []
921+
for _ in range(length):
922+
a.append( num & ((1 << num_bits_shift) - 1) )
923+
num = num >> num_bits_shift
924+
return tuple(a)
925+
926+
def pack(z, num_bits_shift: int) -> int:
927+
limbs = (z.d0, z.d1, z.d2)
928+
return sum(limb << (num_bits_shift * i) for i, limb in enumerate(limbs))
929+
930+
a = pack(ids.a, num_bits_shift = 128)
931+
b = pack(ids.b, num_bits_shift = 128)
932+
p = pack(ids.p, num_bits_shift = 128)
933+
# For python3.8 and above the modular inverse can be computed as follows:
934+
# b_inverse_mod_p = pow(b, -1, p)
935+
# Instead we use the python3.7-friendly function div_mod from starkware.python.math_utils
936+
b_inverse_mod_p = div_mod(1, b, p)
937+
938+
939+
b_inverse_mod_p_split = split(b_inverse_mod_p, num_bits_shift=128, length=3)
940+
941+
ids.b_inverse_mod_p.d0 = b_inverse_mod_p_split[0]
942+
ids.b_inverse_mod_p.d1 = b_inverse_mod_p_split[1]
943+
ids.b_inverse_mod_p.d2 = b_inverse_mod_p_split[2]";
917944
pub const HI_MAX_BITLEN: &str =
918945
"ids.len_hi = max(ids.scalar_u.d2.bit_length(), ids.scalar_v.d2.bit_length())-1";
919946

src/math_utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ pub fn safe_div_usize(x: usize, y: usize) -> Result<usize, MathError> {
8383
}
8484

8585
///Returns num_a^-1 mod p
86-
fn mul_inv(num_a: &BigInt, p: &BigInt) -> BigInt {
86+
pub(crate) fn mul_inv(num_a: &BigInt, p: &BigInt) -> BigInt {
8787
if num_a.is_zero() {
8888
return BigInt::zero();
8989
}

src/tests/cairo_run_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,7 @@ fn uint384_extension() {
712712
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
713713
fn field_arithmetic() {
714714
let program_data = include_bytes!("../../cairo_programs/field_arithmetic.json");
715-
run_program_simple_with_memory_holes(program_data.as_slice(), 192);
715+
run_program_simple_with_memory_holes(program_data.as_slice(), 272);
716716
}
717717

718718
#[test]

0 commit comments

Comments
 (0)