Skip to content

Commit

Permalink
Added libfunc for u256_inv_mod_n.
Browse files Browse the repository at this point in the history
  • Loading branch information
orizi committed Jul 6, 2023
1 parent c0b57e4 commit 6f30f2c
Show file tree
Hide file tree
Showing 10 changed files with 826 additions and 7 deletions.
26 changes: 24 additions & 2 deletions corelib/src/math.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use zeroable::{IsZeroResult, NonZeroIntoImpl, Zeroable};
use traits::{Into, TryInto};
use option::OptionTrait;
use integer::{u256_wide_mul, u512_safe_div_rem_by_u256};
use integer::{u256_wide_mul, u512_safe_div_rem_by_u256, U128MulGuarantee};

// TODO(yuval): use signed integers once supported.
// TODO(yuval): use a single impl of a trait with associated impls, once associated impls are
Expand Down Expand Up @@ -77,9 +77,31 @@ fn inv_mod<
}
}

/// Returns `1 / b (mod n)`, or None if `b` is not invertible modulo `n`.
/// Additionally returns several `U128MulGuarantee`s that are required for validating the calculation.
extern fn u256_inv_mod_n(
b: NonZero<u256>, n: NonZero<u256>
) -> Result<(
NonZero<u256>,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee
),
(U128MulGuarantee, U128MulGuarantee)> implicits(RangeCheck) nopanic;

/// Returns `a / b (mod n)`, or None if `b` is not invertible modulo `n`.
fn u256_div_mod_n(a: u256, b: NonZero<u256>, n: NonZero<u256>) -> Option<u256> {
let inv_b = inv_mod(b, n)?;
let inv_b = match u256_inv_mod_n(b, n) {
Result::Ok((inv_b, _, _, _, _, _, _, _, _)) => inv_b.into(),
Result::Err(_) => {
return Option::None(());
}
};
let quotient = u256_wide_mul(a, inv_b);
let (_, quotient_mod_n) = u512_safe_div_rem_by_u256(quotient, n);
Option::Some(quotient_mod_n)
Expand Down
10 changes: 10 additions & 0 deletions corelib/src/test/math_test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,14 @@ fn test_u256_div_mod_n() {
let q = math::u256_div_mod_n(0_u256, 3_u256.try_into().unwrap(), 13_u256.try_into().unwrap())
.unwrap();
assert(q == 0, '0 / 3 != 0 (13)');
assert(
math::u256_div_mod_n(4_u256, 3_u256.try_into().unwrap(), 6_u256.try_into().unwrap())
.is_none(),
'4 / 3 == None (6)'
);
assert(
math::u256_div_mod_n(2_u256, 8_u256.try_into().unwrap(), 4_u256.try_into().unwrap())
.is_none(),
'2 / 8 == None (4)'
);
}
73 changes: 73 additions & 0 deletions crates/cairo-lang-casm/src/hints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,33 @@ pub enum CoreHint {
/// Returns an address with `size` free locations afterwards.
#[codec(index = 26)]
AllocConstantSize { size: ResOperand, dst: CellRef },
/// Provides inverse of b (represented by 2 128bit limbs) modulo n (represented by 2 128bit
/// limbs), or proof it is not possible. In case it is possible (meaning b has an inverse
/// mod n): Returns `r` and `k` such that:
/// `r = 1 / divisor mod n`
/// `k = (result * divisor - 1) / n`
///
/// In case it is not possible (meaning divisor does not have an inverse mod n):
/// Returns `g`, `b_div_g` and `n_div_g` such that:
/// `g > 1`
/// `g == 2 || g % 2 == 1`
/// `g * b_div_g = b`
/// `g * n_div_g = n`
///
/// In all cases - `name`0 is the least significant limb.
#[codec(index = 27)]
U256InvModN {
b0: ResOperand,
b1: ResOperand,
n0: ResOperand,
n1: ResOperand,
g0_or_no_inv: CellRef,
g1_option: CellRef,
b_div_g_or_r0: CellRef,
b_div_g_or_r1: CellRef,
n_div_g_or_k0: CellRef,
n_div_g_or_k1: CellRef,
},
}

/// Represents a deprecated hint which is kept for backward compatibility of previously deployed
Expand Down Expand Up @@ -651,6 +678,52 @@ impl PythonicHint for CoreHint {
ResOperandFormatter(size)
)
}
CoreHint::U256InvModN {
b0,
b1,
n0,
n1,
g0_or_no_inv,
g1_option,
b_div_g_or_r0,
b_div_g_or_r1,
n_div_g_or_k0,
n_div_g_or_k1,
} => {
let [b0, b1, n0, n1] = [b0, b1, n0, n1].map(ResOperandFormatter);
formatdoc!(
"
from starkware.python.math_utils import igcdex
b = {b0} + {b1} * 2**128
n = {n0} + {n1} * 2**128
(_, r, g) = igcdex(n, b)
if g != 1:
if g % 2 == 0:
g = 2
b_div_g = b // g
n_div_g = n // g
memory{g0_or_no_inv} = g & 0xffffffffffffffffffffffffffffffff
memory{g1_option} = g >> 128
memory{b_div_g_or_r0} = b_div_g & 0xffffffffffffffffffffffffffffffff
memory{b_div_g_or_r1} = b_div_g >> 128
memory{n_div_g_or_k0} = n_div_g & 0xffffffffffffffffffffffffffffffff
memory{n_div_g_or_k1} = n_div_g >> 128
else:
r %= n
if r < 0:
r += n
k = (r * b - 1) // n
memory{g0_or_no_inv} = 0
memory{b_div_g_or_r0} = r & 0xffffffffffffffffffffffffffffffff
memory{b_div_g_or_r1} = r >> 128
memory{n_div_g_or_k0} = k & 0xffffffffffffffffffffffffffffffff
memory{n_div_g_or_k1} = k >> 128
"
)
}
}
}
}
Expand Down
55 changes: 52 additions & 3 deletions crates/cairo-lang-runner/src/casm_run/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ use cairo_vm::vm::errors::vm_errors::VirtualMachineError;
use cairo_vm::vm::runners::cairo_runner::{CairoRunner, ResourceTracker, RunResources};
use cairo_vm::vm::vm_core::VirtualMachine;
use dict_manager::DictManagerExecScope;
use num_bigint::BigUint;
use num_integer::Integer;
use num_traits::{FromPrimitive, ToPrimitive, Zero};
use num_bigint::{BigInt, BigUint};
use num_integer::{ExtendedGcd, Integer};
use num_traits::{FromPrimitive, Signed, ToPrimitive, Zero};
use {ark_secp256k1 as secp256k1, ark_secp256r1 as secp256r1};

use self::dict_manager::DictSquashExecScope;
Expand Down Expand Up @@ -1818,6 +1818,55 @@ pub fn execute_core_hint(
insert_value_to_cellref!(vm, dst, memory_exec_scope.next_address)?;
memory_exec_scope.next_address.offset += object_size;
}
CoreHint::U256InvModN {
b0,
b1,
n0,
n1,
g0_or_no_inv,
g1_option,
b_div_g_or_r0,
b_div_g_or_r1,
n_div_g_or_k0,
n_div_g_or_k1,
} => {
let pow_2_128 = BigInt::from(u128::MAX) + 1u32;
let b0 = get_val(vm, b0)?.to_bigint();
let b1 = get_val(vm, b1)?.to_bigint();
let n0 = get_val(vm, n0)?.to_bigint();
let n1 = get_val(vm, n1)?.to_bigint();
let b: BigInt = b0 + b1.shl(128);
let n: BigInt = n0 + n1.shl(128);
let ExtendedGcd { gcd: mut g, x: _, y: mut r } = n.extended_gcd(&b);
if g != 1.into() {
// This makes sure `g0_or_no_inv` is alway non-zero in the no inverse case.
if g.is_even() {
g = 2u32.into();
}
let (limb1, limb0) = (&b / &g).div_rem(&pow_2_128);
insert_value_to_cellref!(vm, b_div_g_or_r0, Felt252::from(limb0))?;
insert_value_to_cellref!(vm, b_div_g_or_r1, Felt252::from(limb1))?;
let (limb1, limb0) = (&n / &g).div_rem(&pow_2_128);
insert_value_to_cellref!(vm, n_div_g_or_k0, Felt252::from(limb0))?;
insert_value_to_cellref!(vm, n_div_g_or_k1, Felt252::from(limb1))?;
let (limb1, limb0) = g.div_rem(&pow_2_128);
insert_value_to_cellref!(vm, g0_or_no_inv, Felt252::from(limb0))?;
insert_value_to_cellref!(vm, g1_option, Felt252::from(limb1))?;
} else {
r %= &n;
if r.is_negative() {
r += &n;
}
let k: BigInt = (&r * b - 1) / n;
let (limb1, limb0) = r.div_rem(&pow_2_128);
insert_value_to_cellref!(vm, b_div_g_or_r0, Felt252::from(limb0))?;
insert_value_to_cellref!(vm, b_div_g_or_r1, Felt252::from(limb1))?;
let (limb1, limb0) = k.div_rem(&pow_2_128);
insert_value_to_cellref!(vm, n_div_g_or_k0, Felt252::from(limb0))?;
insert_value_to_cellref!(vm, n_div_g_or_k1, Felt252::from(limb1))?;
insert_value_to_cellref!(vm, g0_or_no_inv, Felt252::from(0))?;
}
}
};
Ok(())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ pub fn core_libfunc_ap_change<InfoProvider: InvocationApChangeInfoProvider>(
Uint256Concrete::IsZero(_) => vec![ApChange::Known(0), ApChange::Known(0)],
Uint256Concrete::Divmod(_) => vec![ApChange::Known(19)],
Uint256Concrete::SquareRoot(_) => vec![ApChange::Known(25)],
Uint256Concrete::InvModN(_) => vec![ApChange::Known(47), ApChange::Known(14)],
},
CoreConcreteLibfunc::Uint512(libfunc) => match libfunc {
Uint512Concrete::DivModU256(_) => vec![ApChange::Known(43)],
Expand Down
4 changes: 4 additions & 0 deletions crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,10 @@ fn u256_libfunc_cost(libfunc: &Uint256Concrete) -> Vec<ConstCost> {
}
Uint256Concrete::Divmod(_) => vec![ConstCost { steps: 26, holes: 0, range_checks: 6 }],
Uint256Concrete::SquareRoot(_) => vec![ConstCost { steps: 30, holes: 0, range_checks: 7 }],
Uint256Concrete::InvModN(_) => vec![
ConstCost { steps: 41, holes: 0, range_checks: 9 },
ConstCost { steps: 23, holes: 0, range_checks: 7 },
],
}
}

Expand Down
Loading

0 comments on commit 6f30f2c

Please sign in to comment.