Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added libfunc for u256_inv_mod_n. #3603

Merged
merged 1 commit into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 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,38 @@ 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_guarantee_inv_mod_n(
b: u256, n: NonZero<u256>
) -> Result<
(
NonZero<u256>,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee
),
(U128MulGuarantee, U128MulGuarantee)
> implicits(RangeCheck) nopanic;

/// Returns the inverse of `a` modulo `n`, or None if `a` is not invertible modulo `n`.
#[inline(always)]
fn u256_inv_mod(a: u256, n: NonZero<u256>) -> Option<NonZero<u256>> {
match u256_guarantee_inv_mod_n(a, n) {
Result::Ok((inv_a, _, _, _, _, _, _, _, _)) => Option::Some(inv_a),
Result::Err(_) => Option::None(())
}
}

/// 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> {
Option::Some(u256_mul_mod_n(a, inv_mod(b, n)?, n))
fn u256_div_mod_n(a: u256, b: u256, n: NonZero<u256>) -> Option<u256> {
Option::Some(u256_mul_mod_n(a, u256_inv_mod(b, n)?.into(), n))
}

/// Returns `a * b (mod n)`.
Expand Down
124 changes: 73 additions & 51 deletions corelib/src/test/math_test.cairo
Original file line number Diff line number Diff line change
@@ -1,42 +1,39 @@
/// Helper for making a non-zero value.
fn nz<N, +TryInto<N, NonZero<N>>>(n: N) -> NonZero<N> {
n.try_into().unwrap()
}

#[test]
fn test_egcd() {
let (g, s, t, sub_direction) = math::egcd(68_u8.try_into().unwrap(), 16_u8.try_into().unwrap());
let (g, s, t, sub_direction) = math::egcd(nz(68_u8), nz(16_u8));
assert(g == 4, 'g != 4');
assert(s == 1, 's != 1');
assert(t == 4, 't != 4');
assert(sub_direction, 'sub_direction is wrong');
assert(1 * 68 - 4 * 16 == 4, 'Sanity check failed');

let (g, s, t, sub_direction) = math::egcd(
240_u256.try_into().unwrap(), 46_u256.try_into().unwrap()
);
let (g, s, t, sub_direction) = math::egcd(nz(240_u256), nz(46_u256));
assert(g == 2, 'g != 2');
assert(s == 9, 's != 9');
assert(t == 47, 't != 47');
assert(!sub_direction, 'sub_direction is wrong');
assert(47 * 46 - 9 * 240 == 2, 'Sanity check failed');

let (g, s, t, sub_direction) = math::egcd(
50_u128.try_into().unwrap(), 17_u128.try_into().unwrap()
);
let (g, s, t, sub_direction) = math::egcd(nz(50_u128), nz(17_u128));
assert(g == 1, 'g != 1');
assert(s == 1, 's != 1');
assert(t == 3, 't != 3');
assert(!sub_direction, 'sub_direction is wrong');
assert(3 * 17 - 1 * 50 == 1, 'Sanity check failed');

let (g, s, t, sub_direction) = math::egcd(
5_u128.try_into().unwrap(), 15_u128.try_into().unwrap()
);
let (g, s, t, sub_direction) = math::egcd(nz(5_u128), nz(15_u128));
assert(g == 5, 'g != 5');
assert(s == 1, 's != 1');
assert(t == 0, 't != 0');
assert(sub_direction, 'sub_direction is wrong');
assert(1 * 5 - 0 * 15 == 5, 'Sanity check failed');

let (g, s, t, sub_direction) = math::egcd(
1_u128.try_into().unwrap(), 1_u128.try_into().unwrap()
);
let (g, s, t, sub_direction) = math::egcd(nz(1_u128), nz(1_u128));
assert(g == 1, 'g != 1');
assert(s == 0, 's != 0');
assert(t == 1, 't != 1');
Expand All @@ -46,47 +43,72 @@ fn test_egcd() {

#[test]
fn test_inv_mod() {
let inv = math::inv_mod(5_u256.try_into().unwrap(), 24_u256.try_into().unwrap()).unwrap();
assert(inv == 5, 'inv != 5');

let inv = math::inv_mod(29_u128.try_into().unwrap(), 24_u128.try_into().unwrap()).unwrap();
assert(inv == 5, 'inv != 5');

let inv = math::inv_mod(1_u16.try_into().unwrap(), 24_u16.try_into().unwrap()).unwrap();
assert(inv == 1, 'inv != 1');

let inv = math::inv_mod(1_u32.try_into().unwrap(), 5_u32.try_into().unwrap()).unwrap();
assert(inv == 1, 'inv != 1');

let inv = math::inv_mod(8_usize.try_into().unwrap(), 24_usize.try_into().unwrap());
assert(inv.is_none(), 'inv should be None');

let inv = math::inv_mod(1_usize.try_into().unwrap(), 1_usize.try_into().unwrap()).unwrap();
assert(inv == 0, 'inv != 0');

let inv = math::inv_mod(7_usize.try_into().unwrap(), 1_usize.try_into().unwrap()).unwrap();
assert(inv == 0, 'inv != 0');
assert(math::inv_mod(nz(5), nz(24)) == Option::Some(5_u256), 'inv_mov(5, 24) != 5');
assert(math::inv_mod(nz(29), nz(24)) == Option::Some(5_u128), 'inv_mov(29, 24) != 5');
assert(math::inv_mod(nz(1), nz(24)) == Option::Some(1_u16), 'inv_mov(1, 24) != 1');
assert(math::inv_mod(nz(1), nz(5)) == Option::Some(1_u32), 'inv_mov(1, 5) != 1');
assert(math::inv_mod(nz(8_usize), nz(24_usize)).is_none(), 'inv_mov(8, 24) != None');
assert(math::inv_mod(nz(1), nz(1)) == Option::Some(0_usize), 'inv_mov(1, 1) != 0');
assert(math::inv_mod(nz(7), nz(1)) == Option::Some(0_usize), 'inv_mov(7, 1) != 0');
}

#[test]
fn test_u256_div_mod_n() {
let q = math::u256_div_mod_n(6_u256, 2_u256.try_into().unwrap(), 7_u256.try_into().unwrap())
.unwrap();
assert(q == 3, '6 / 2 != 3 (7)');

let q = math::u256_div_mod_n(5_u256, 1_u256.try_into().unwrap(), 7_u256.try_into().unwrap())
.unwrap();
assert(q == 5, '5 / 1 != 5 (7)');

let q = math::u256_div_mod_n(1_u256, 1_u256.try_into().unwrap(), 7_u256.try_into().unwrap())
.unwrap();
assert(q == 1, '1 / 1 != 1 (7)');

let q = math::u256_div_mod_n(7_u256, 2_u256.try_into().unwrap(), 13_u256.try_into().unwrap())
.unwrap();
assert(q == 10, '7 / 2 != 10 (13)');
assert(math::u256_div_mod_n(6, 2, nz(7)) == Option::Some(3), '6 / 2 != 3 (7)');
assert(math::u256_div_mod_n(5, 1, nz(7)) == Option::Some(5), '5 / 1 != 5 (7)');
assert(math::u256_div_mod_n(1, 1, nz(7)) == Option::Some(1), '1 / 1 != 1 (7)');
assert(math::u256_div_mod_n(7, 2, nz(13)) == Option::Some(10), '7 / 2 != 10 (13)');
assert(math::u256_div_mod_n(0, 3, nz(13)) == Option::Some(0), '0 / 3 != 0 (13)');
assert(math::u256_div_mod_n(4, 3, nz(6)).is_none(), '4 / 3 == None (6)');
assert(math::u256_div_mod_n(5, 4, nz(6)).is_none(), '5 / 4 == None (6)');
assert(math::u256_div_mod_n(2, 8, nz(4)).is_none(), '2 / 8 == None (4)');
assert(
math::u256_div_mod_n(
0xfa855081cc80656250605b2ecd7958ba4f0aa6799053da0d68bf76f2484decc6,
0xe8e94a59a951af1b4c8cbd45fb8d01c1dd946de2533e3ad18845f9dbb6d12f4f,
nz(0xa3db605888ac3cd19e70c5b52220ad693566b996ef078e907578fec7758dabc9)
) == Option::Some(0x8e70aea916ee4b782a0da9c18083ed9d867148a703615a2a88d0e7fddd4c900d),
'Random large values 1'
);
assert(
math::u256_div_mod_n(
0x759426f1c0ba213b6378196b5091f5fa48f49f1d0cecfb00a7d59a51be35f609,
0x57ff2c2e0900fce82331e396a71787a837783cca8145538eb32cb4b52104a3be,
nz(0xcb514e4d4672d8f1d952c0312afb5baae86121aa5817030d8439ce759295a029)
) == Option::Some(0x7c9d22b40f98075c0bfd674d546bc77d775dcf021d30b88afb099834dffa951b),
'Random large values 2'
);
}

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)');
#[test]
fn test_u256_inv_mod_no_inverse() {
assert(math::u256_inv_mod(3, nz(6)).is_none(), 'inv_mod(3, 6)');
assert(math::u256_inv_mod(4, nz(6)).is_none(), 'inv_mod(4, 6)');
assert(math::u256_inv_mod(8, nz(4)).is_none(), 'inv_mod(8, 4)');
assert(
math::u256_inv_mod(
0xea9195982bd472e30e5146ad7cb0acd954cbc75032a298ac73234b6b05e28cc1,
nz(0x4075f980fab77a3fde536dbaae600f5ea1540e01837dcec64c1f379613aa4d18)
)
.is_none(),
'Random 1'
);
assert(
math::u256_inv_mod(
0x85ef555d7a0aa34019c138defc40a1d3683dc1caa505bff286dd8069a28a2e4c,
nz(0xd71e5a5f4a4d1af45e703f9e13d1305ce149313037956247ad5edfe3e81d6353)
)
.is_none(),
'Random 2'
);
let large_gcd = 0x63f7a7326f84cfca7738923db2b6d6c1b1b;
assert(
math::u256_inv_mod(
large_gcd * 0x51e28b744cc00edfb6bbbe6a9, nz(large_gcd * 0xc09497df4aa02be7fd25f10d3)
)
.is_none(),
'gcd ~ 2**140'
);
let very_large_gcd = 0x74c5ef92be07ee4ad43ae8ca337390e4a5dfdbf4f1a5f09cdf412ab7ce343503;
assert(math::u256_inv_mod(very_large_gcd, nz(very_large_gcd)).is_none(), 'gcd ~ 2**256');
}
72 changes: 72 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,34 @@ pub enum CoreHint {
/// Returns an address with `size` free locations afterwards.
#[codec(index = 26)]
AllocConstantSize { size: ResOperand, dst: CellRef },
/// Provides the inverse of b (represented by 2 128-bit limbs) modulo n (represented by 2
/// 128-bit limbs), or a proof that b has no inverse.
///
/// In case b has an inverse: Returns `r` and `k` such that:
/// * `r = 1 / b (mod n)`
/// * `k = (r * b - 1) / n`
/// * `g0_or_no_inv = 0`
///
/// In case b has no inverse: Returns `g`, `s`, and `t`, such that:
/// `g > 1`
/// `g == 2 || g % 2 == 1` (in particular, `g0_or_no_inv = g0 != 0`)
/// `g * s = b`
/// `g * t = 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,
s_or_r0: CellRef,
s_or_r1: CellRef,
t_or_k0: CellRef,
t_or_k1: CellRef,
},
}

/// Represents a deprecated hint which is kept for backward compatibility of previously deployed
Expand Down Expand Up @@ -681,6 +709,50 @@ impl PythonicHint for CoreHint {
ResOperandAsIntegerFormatter(size)
)
}
CoreHint::U256InvModN {
b0,
b1,
n0,
n1,
g0_or_no_inv,
g1_option,
s_or_r0,
s_or_r1,
t_or_k0,
t_or_k1,
} => {
let [b0, b1, n0, n1] = [b0, b1, n0, n1].map(ResOperandAsIntegerFormatter);
formatdoc!(
"

from starkware.python.math_utils import igcdex

b = {b0} + ({b1} << 128)
n = {n0} + ({n1} << 128)

(_, r, g) = igcdex(n, b)
if g != 1:
if g % 2 == 0:
g = 2
s = b // g
t = n // g
memory{g0_or_no_inv} = g & 0xffffffffffffffffffffffffffffffff
memory{g1_option} = g >> 128
memory{s_or_r0} = s & 0xffffffffffffffffffffffffffffffff
memory{s_or_r1} = s >> 128
memory{t_or_k0} = t & 0xffffffffffffffffffffffffffffffff
memory{t_or_k1} = t >> 128
else:
r %= n
k = (r * b - 1) // n
memory{g0_or_no_inv} = 0
memory{s_or_r0} = r & 0xffffffffffffffffffffffffffffffff
memory{s_or_r1} = r >> 128
memory{t_or_k0} = k & 0xffffffffffffffffffffffffffffffff
memory{t_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 @@ -1913,6 +1913,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,
s_or_r0,
s_or_r1,
t_or_k0,
t_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, s_or_r0, Felt252::from(limb0))?;
insert_value_to_cellref!(vm, s_or_r1, Felt252::from(limb1))?;
let (limb1, limb0) = (&n / &g).div_rem(&pow_2_128);
insert_value_to_cellref!(vm, t_or_k0, Felt252::from(limb0))?;
insert_value_to_cellref!(vm, t_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, s_or_r0, Felt252::from(limb0))?;
insert_value_to_cellref!(vm, s_or_r1, Felt252::from(limb1))?;
let (limb1, limb0) = k.div_rem(&pow_2_128);
insert_value_to_cellref!(vm, t_or_k0, Felt252::from(limb0))?;
insert_value_to_cellref!(vm, t_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 @@ -171,6 +171,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(46), 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 @@ -596,6 +596,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: 40, holes: 0, range_checks: 9 },
ConstCost { steps: 23, holes: 0, range_checks: 7 },
],
}
}

Expand Down
Loading