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 Aug 24, 2023
1 parent 53a5ab1 commit 602957b
Show file tree
Hide file tree
Showing 10 changed files with 875 additions and 59 deletions.
30 changes: 28 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,35 @@ 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: NonZero<u256>, n: NonZero<u256>
) -> Result<(
NonZero<u256>,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee,
U128MulGuarantee
),
(U128MulGuarantee, U128MulGuarantee)> implicits(RangeCheck) nopanic;

/// Returns `1 / b (mod n)`, or None if `b` is not invertible modulo `n`.
#[inline(always)]
fn u256_inv_mod_n(b: NonZero<u256>, n: NonZero<u256>) -> Option<NonZero<u256>> {
match u256_guarantee_inv_mod_n(b, n) {
Result::Ok((inv_b, _, _, _, _, _, _, _, _)) => Option::Some(inv_b),
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))
Option::Some(u256_mul_mod_n(a, u256_inv_mod_n(b, n)?.into(), n))
}

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

#[test]
#[available_gas(10000000)]
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 @@ -48,48 +45,57 @@ fn test_egcd() {
#[test]
#[available_gas(10000000)]
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]
#[available_gas(10000000)]
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)');

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(6, nz(2), nz(7)) == Option::Some(3), '6 / 2 != 3 (7)');
assert(math::u256_div_mod_n(5, nz(1), nz(7)) == Option::Some(5), '5 / 1 != 5 (7)');
assert(math::u256_div_mod_n(1, nz(1), nz(7)) == Option::Some(1), '1 / 1 != 1 (7)');
assert(math::u256_div_mod_n(7, nz(2), nz(13)) == Option::Some(10), '7 / 2 != 10 (13)');
assert(math::u256_div_mod_n(0, nz(3), nz(13)) == Option::Some(0), '0 / 3 != 0 (13)');
assert(math::u256_div_mod_n(4, nz(3), nz(6)).is_none(), '4 / 3 == None (6)');
assert(math::u256_div_mod_n(2, nz(8), nz(4)).is_none(), '2 / 8 == None (4)');
assert(
math::u256_div_mod_n(
0x2ab1f535168b19cef4bf517c5b010e089820273ac99e934bba57b1afb49856aa,
nz(0xea9195982bd472e30e5146ad7cb0acd954cbc75032a298ac73234b6b05e28cc1),
nz(0x4075f980fab77a3fde536dbaae600f5ea1540e01837dcec64c1f379613aa4d18)
)
.is_none(),
'Random no inverse 1'
);
assert(
math::u256_div_mod_n(
0xe3f5c3c783073fdcf77c0459634bd8111698220fb18ef110d9a7b8b39de6289a,
nz(0x85ef555d7a0aa34019c138defc40a1d3683dc1caa505bff286dd8069a28a2e4c),
nz(0xd71e5a5f4a4d1af45e703f9e13d1305ce149313037956247ad5edfe3e81d6353)
)
.is_none(),
'Random no inverse 2'
);
assert(
math::u256_div_mod_n(
0xfa855081cc80656250605b2ecd7958ba4f0aa6799053da0d68bf76f2484decc6,
nz(0xe8e94a59a951af1b4c8cbd45fb8d01c1dd946de2533e3ad18845f9dbb6d12f4f),
nz(0xa3db605888ac3cd19e70c5b52220ad693566b996ef078e907578fec7758dabc9)
) == Option::Some(0x8e70aea916ee4b782a0da9c18083ed9d867148a703615a2a88d0e7fddd4c900d),
'Random large values 1'
);
assert(
math::u256_div_mod_n(
0x759426f1c0ba213b6378196b5091f5fa48f49f1d0cecfb00a7d59a51be35f609,
nz(0x57ff2c2e0900fce82331e396a71787a837783cca8145538eb32cb4b52104a3be),
nz(0xcb514e4d4672d8f1d952c0312afb5baae86121aa5817030d8439ce759295a029)
) == Option::Some(0x7c9d22b40f98075c0bfd674d546bc77d775dcf021d30b88afb099834dffa951b),
'Random large values 2'
);
}
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 @@ -681,6 +708,52 @@ impl PythonicHint for CoreHint {
ResOperandAsIntegerFormatter(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(ResOperandAsIntegerFormatter);
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 @@ -1914,6 +1914,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 @@ -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(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 @@ -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: 41, holes: 0, range_checks: 9 },
ConstCost { steps: 23, holes: 0, range_checks: 7 },
],
}
}

Expand Down
Loading

0 comments on commit 602957b

Please sign in to comment.