Skip to content

Commit

Permalink
feat(hints): add NewHint#49 (lambdaclass#1043)
Browse files Browse the repository at this point in the history
* Add tests for new hint

* Refactor `inv_mod_p_uint512` to use new helpers

* Add NewHint#49

* Update changelog

* Trigger recompilation of fq_test.cairo

* Revert "Trigger recompilation of fq_test.cairo"

This reverts commit ae30228.
  • Loading branch information
MegaRedHand authored and kariy committed Jun 23, 2023
1 parent 60536c8 commit 59198bb
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 91 deletions.
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,34 @@
* Remove duplicated tests in cairo_run_test
* BREAKING CHANGE: `MemorySegmentManager.get_memory_holes` now also receives the amount of builtins in the vm. Signature is now `pub fn get_memory_holes(&self, builtin_count: usize) -> Result<usize, MemoryError>`

* Add missing hint on vrf.json lib [#1043](https://github.com/lambdaclass/cairo-rs/pull/1043):

`BuiltinHintProcessor` now supports the following hint:

```python
from starkware.python.math_utils import div_mod

def split(a: int):
return (a & ((1 << 128) - 1), a >> 128)

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))

a = pack(ids.a, 128)
b = pack(ids.b, 128)
p = pack(ids.p, 128)
# For python3.8 and above the modular inverse can be computed as follows:
# b_inverse_mod_p = pow(b, -1, p)
# Instead we use the python3.7-friendly function div_mod from starkware.python.math_utils
b_inverse_mod_p = div_mod(1, b, p)

b_inverse_mod_p_split = split(b_inverse_mod_p)

ids.b_inverse_mod_p.low = b_inverse_mod_p_split[0]
ids.b_inverse_mod_p.high = b_inverse_mod_p_split[1]
```

* Add missing hints `NewHint#35` and `NewHint#36` [#975](https://github.com/lambdaclass/cairo-rs/issues/975)

`BuiltinHintProcessor` now supports the following hint:
Expand Down
63 changes: 61 additions & 2 deletions cairo_programs/fq.cairo
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
from starkware.cairo.common.uint256 import Uint256, split_64
from starkware.cairo.common.uint256 import Uint256, split_64, uint256_mul_div_mod
from starkware.cairo.common.math_cmp import is_le
from starkware.cairo.common.cairo_secp.constants import BASE
from starkware.cairo.common.cairo_secp.bigint import (
BigInt3,
uint256_to_bigint,
bigint_to_uint256,
UnreducedBigInt5,
bigint_mul,
nondet_bigint3,
)

from cairo_programs.uint384_extension import Uint384, Uint768, u384

// src: https://github.com/rdubois-crypto/garaga/blob/48a5b1d7d530baba2338698ffebf988ed3d19e6d/src/curve.cairo
const P0 = 60193888514187762220203335;
const P1 = 27625954992973055882053025;
const P2 = 3656382694611191768777988;

const P_low = 201385395114098847380338600778089168199;
const P_high = 64323764613183177041862057485226039389;
// ------------------

struct Uint512 {
d0: felt,
d1: felt,
Expand All @@ -14,7 +32,7 @@ const SHIFT = 2 ** 128;
const ALL_ONES = 2 ** 128 - 1;
const HALF_SHIFT = 2 ** 64;

namespace u512 {
namespace fq {
func add_u512_and_u256{range_check_ptr}(a: Uint512, b: Uint256) -> Uint512 {
alloc_locals;

Expand Down Expand Up @@ -188,6 +206,47 @@ namespace u512 {
}
return is_le(a.high + 1, b.high);
}

// Computes a * b^{-1} modulo p
// NOTE: The modular inverse of b modulo p is computed in a hint and verified outside the hind with a multiplicaiton
func div{range_check_ptr}(a: Uint256, b: Uint256, p: Uint256) -> Uint256 {
alloc_locals;
local b_inverse_mod_p: Uint256;
// To whitelist
%{
from starkware.python.math_utils import div_mod
def split(a: int):
return (a & ((1 << 128) - 1), a >> 128)
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))
a = pack(ids.a, 128)
b = pack(ids.b, 128)
p = pack(ids.p, 128)
# For python3.8 and above the modular inverse can be computed as follows:
# b_inverse_mod_p = pow(b, -1, p)
# Instead we use the python3.7-friendly function div_mod from starkware.python.math_utils
b_inverse_mod_p = div_mod(1, b, p)
b_inverse_mod_p_split = split(b_inverse_mod_p)
ids.b_inverse_mod_p.low = b_inverse_mod_p_split[0]
ids.b_inverse_mod_p.high = b_inverse_mod_p_split[1]
%}
let b_times_b_inverse = mul(b, b_inverse_mod_p, p);
assert b_times_b_inverse = Uint256(1, 0);

let res: Uint256 = mul(a, b_inverse_mod_p, p);
return res;
}

func mul{range_check_ptr}(a: Uint256, b: Uint256, p: Uint256) -> Uint256 {
let (low, high, remainder) = uint256_mul_div_mod(a, b, p);
return remainder;
}
}

func main() {
Expand Down
29 changes: 26 additions & 3 deletions cairo_programs/fq_test.cairo
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
%builtins range_check

from cairo_programs.fq import u512, Uint256, Uint512
from cairo_programs.fq import fq, Uint256, Uint512

func test_u512_unsigned_div_rem{range_check_ptr}() {
let x = Uint512(26362362, 32523523, 135525, 15521);
let div = Uint256(1, 0);

let (q, r) = u512.u512_unsigned_div_rem(x, div);
let (q, r) = fq.u512_unsigned_div_rem(x, div);

// x / 1 = x
assert q = Uint512(26362362, 32523523, 135525, 15521);
Expand All @@ -23,7 +23,7 @@ func test_u512_unsigned_div_rem{range_check_ptr}() {
103510830969771876705678198448587782120, 321696934602460025966614305804515599536
);

let (q, r) = u512.u512_unsigned_div_rem(x, div);
let (q, r) = fq.u512_unsigned_div_rem(x, div);

assert q = Uint512(
203702859112426540420143348051200561496, 231621784431619772183895351989849416356, 0, 0
Expand All @@ -35,8 +35,31 @@ func test_u512_unsigned_div_rem{range_check_ptr}() {
return ();
}

func test_div{range_check_ptr}() {
let a = Uint256(5, 0);
let b = Uint256(7, 0);

let p = Uint256(101, 0);

let res = fq.div(a, b, p);

assert res = Uint256(44, 0);

let a = Uint256(115251, 54253);
let b = Uint256(92, 514218);

let p = Uint256(307877160504247914927393058070787825931, 136606343208);

let res = fq.div(a, b, p);

assert res = Uint256(205638342597558693542746392024120778414, 66858624688);

return ();
}

func main{range_check_ptr}() {
test_u512_unsigned_div_rem();
test_div();

return ();
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use super::{
},
field_arithmetic::uint384_div,
secp::secp_utils::{SECP_P, SECP_P_V2},
vrf::{fq::uint512_unsigned_div_rem, inv_mod_p_uint512::inv_mod_p_uint512},
vrf::{
fq::{inv_mod_p_uint256, uint512_unsigned_div_rem},
inv_mod_p_uint512::inv_mod_p_uint512,
},
};
use crate::{
hint_processor::{
Expand Down Expand Up @@ -616,6 +619,9 @@ impl HintProcessor for BuiltinHintProcessor {
hi_max_bitlen(vm, &hint_data.ids_data, &hint_data.ap_tracking)
}
hint_code::QUAD_BIT => quad_bit(vm, &hint_data.ids_data, &hint_data.ap_tracking),
hint_code::INV_MOD_P_UINT256 => {
inv_mod_p_uint256(vm, &hint_data.ids_data, &hint_data.ap_tracking)
}
hint_code::INV_MOD_P_UINT512 => {
inv_mod_p_uint512(vm, &hint_data.ids_data, &hint_data.ap_tracking)
}
Expand Down
47 changes: 47 additions & 0 deletions src/hint_processor/builtin_hint_processor/hint_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,7 @@ ids.sqrt_x.d2 = split_root_x[2]
ids.sqrt_gx.d0 = split_root_gx[0]
ids.sqrt_gx.d1 = split_root_gx[1]
ids.sqrt_gx.d2 = split_root_gx[2]";

pub const UINT384_DIV: &str = "from starkware.python.math_utils import div_mod
def split(num: int, num_bits_shift: int, length: int):
Expand Down Expand Up @@ -981,6 +982,29 @@ b_inverse_mod_p_split = split(b_inverse_mod_p, num_bits_shift=128, length=3)
ids.b_inverse_mod_p.d0 = b_inverse_mod_p_split[0]
ids.b_inverse_mod_p.d1 = b_inverse_mod_p_split[1]
ids.b_inverse_mod_p.d2 = b_inverse_mod_p_split[2]";

pub const INV_MOD_P_UINT256: &str = r#"from starkware.python.math_utils import div_mod
def split(a: int):
return (a & ((1 << 128) - 1), a >> 128)
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))
a = pack(ids.a, 128)
b = pack(ids.b, 128)
p = pack(ids.p, 128)
# For python3.8 and above the modular inverse can be computed as follows:
# b_inverse_mod_p = pow(b, -1, p)
# Instead we use the python3.7-friendly function div_mod from starkware.python.math_utils
b_inverse_mod_p = div_mod(1, b, p)
b_inverse_mod_p_split = split(b_inverse_mod_p)
ids.b_inverse_mod_p.low = b_inverse_mod_p_split[0]
ids.b_inverse_mod_p.high = b_inverse_mod_p_split[1]"#;

pub const HI_MAX_BITLEN: &str =
"ids.len_hi = max(ids.scalar_u.d2.bit_length(), ids.scalar_v.d2.bit_length())-1";

Expand Down Expand Up @@ -1062,6 +1086,29 @@ m = pack(ids.m, PRIME)
value = res = product % m"#;

pub const UINT256_MUL_INV_MOD_P: &str = r#"from starkware.python.math_utils import div_mod
def split(a: int):
return (a & ((1 << 128) - 1), a >> 128)
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))
a = pack(ids.a, 128)
b = pack(ids.b, 128)
p = pack(ids.p, 128)
# For python3.8 and above the modular inverse can be computed as follows:
# b_inverse_mod_p = pow(b, -1, p)
# Instead we use the python3.7-friendly function div_mod from starkware.python.math_utils
b_inverse_mod_p = div_mod(1, b, p)
b_inverse_mod_p_split = split(b_inverse_mod_p)
ids.b_inverse_mod_p.low = b_inverse_mod_p_split[0]
ids.b_inverse_mod_p.high = b_inverse_mod_p_split[1]"#;

pub const EC_RECOVER_PRODUCT_DIV_M: &str = "value = k = product // m";

#[cfg(feature = "skip_next_instruction_hint")]
pub const SKIP_NEXT_INSTRUCTION: &str = "skip_next_instruction()";
84 changes: 83 additions & 1 deletion src/hint_processor/builtin_hint_processor/vrf/fq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
use crate::{
hint_processor::builtin_hint_processor::{uint256_utils::Uint256, uint512_utils::Uint512},
hint_processor::hint_processor_definition::HintReference,
math_utils::mul_inv,
serde::deserialize_program::ApTracking,
stdlib::{collections::HashMap, prelude::*},
vm::{errors::hint_errors::HintError, vm_core::VirtualMachine},
};
use num_integer::div_rem;
use num_bigint::ToBigInt;
use num_integer::{div_rem, Integer};

/// Implements hint:
/// ```python
Expand Down Expand Up @@ -58,6 +60,56 @@ pub fn uint512_unsigned_div_rem(
Uint256::from(&remainder).insert_from_var_name("remainder", vm, ids_data, ap_tracking)
}

/// Implements hint:
/// ```python
/// from starkware.python.math_utils import div_mod
/// def split(a: int):
/// return (a & ((1 << 128) - 1), a >> 128)
///
/// 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))
///
/// a = pack(ids.a, 128)
/// b = pack(ids.b, 128)
/// p = pack(ids.p, 128)
/// # For python3.8 and above the modular inverse can be computed as follows:
/// # b_inverse_mod_p = pow(b, -1, p)
/// # Instead we use the python3.7-friendly function div_mod from starkware.python.math_utils
/// b_inverse_mod_p = div_mod(1, b, p)
///
/// b_inverse_mod_p_split = split(b_inverse_mod_p)
///
/// ids.b_inverse_mod_p.low = b_inverse_mod_p_split[0]
/// ids.b_inverse_mod_p.high = b_inverse_mod_p_split[1]
/// ```
pub fn inv_mod_p_uint256(
vm: &mut VirtualMachine,
ids_data: &HashMap<String, HintReference>,
ap_tracking: &ApTracking,
) -> Result<(), HintError> {
// 'a' is not used here or in following hints, so we skip it
let b = Uint256::from_var_name("b", vm, ids_data, ap_tracking)?
.pack()
.to_bigint()
.unwrap_or_default();
let p = Uint256::from_var_name("p", vm, ids_data, ap_tracking)?
.pack()
.to_bigint()
.unwrap_or_default();

// Main logic:
// b_inverse_mod_p = div_mod(1, b, p)
let b_inverse_mod_p = mul_inv(&b, &p)
.mod_floor(&p)
.to_biguint()
.unwrap_or_default();

let res = Uint256::from(&b_inverse_mod_p);
res.insert_from_var_name("b_inverse_mod_p", vm, ids_data, ap_tracking)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -107,4 +159,34 @@ mod tests {
((1, 11), 83573),
];
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_inv_mod_p_uint256_ok() {
let hint_code = hint_code::INV_MOD_P_UINT256;
let mut vm = vm_with_range_check!();

vm.segments = segments![
((1, 0), 2363463),
((1, 1), 566795),
((1, 2), 8760799),
((1, 3), 62362634),
((1, 4), 8340842),
((1, 5), 124152)
];
// Create hint_data
let ids_data =
non_continuous_ids_data![("a", 0), ("b", 2), ("p", 4), ("b_inverse_mod_p", 6)];
assert_matches!(
run_hint!(vm, ids_data, hint_code, exec_scopes_ref!()),
Ok(())
);
//Check hint memory inserts
check_memory![
vm.segments.memory,
// b_inverse_mod_p
((1, 6), ("320134454404400884259649806286603992559", 10)),
((1, 7), 106713),
];
}
}
Loading

0 comments on commit 59198bb

Please sign in to comment.