diff --git a/corelib/src/math.cairo b/corelib/src/math.cairo index 708222ec1df..b0717d5e4d1 100644 --- a/corelib/src/math.cairo +++ b/corelib/src/math.cairo @@ -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 @@ -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, n: NonZero +) -> Result<( + NonZero, + 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, n: NonZero) -> Option> { + 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, n: NonZero) -> Option { - 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)`. diff --git a/corelib/src/test/math_test.cairo b/corelib/src/test/math_test.cairo index ef334287878..b4e29e4f63a 100644 --- a/corelib/src/test/math_test.cairo +++ b/corelib/src/test/math_test.cairo @@ -1,43 +1,40 @@ +/// Helper for making a non-zero value. +fn nz>>(n: N) -> NonZero { + 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'); @@ -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' + ); } diff --git a/crates/cairo-lang-casm/src/hints/mod.rs b/crates/cairo-lang-casm/src/hints/mod.rs index d5052d56098..8f0657b77f2 100644 --- a/crates/cairo-lang-casm/src/hints/mod.rs +++ b/crates/cairo-lang-casm/src/hints/mod.rs @@ -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 @@ -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 + " + ) + } } } } diff --git a/crates/cairo-lang-runner/src/casm_run/mod.rs b/crates/cairo-lang-runner/src/casm_run/mod.rs index d83d826f44a..54ab75cfd50 100644 --- a/crates/cairo-lang-runner/src/casm_run/mod.rs +++ b/crates/cairo-lang-runner/src/casm_run/mod.rs @@ -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; @@ -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(()) } diff --git a/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs b/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs index 41b087d810f..7c0bf34020e 100644 --- a/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs +++ b/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs @@ -171,6 +171,7 @@ pub fn core_libfunc_ap_change( 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)], diff --git a/crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs b/crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs index 288cd056204..47df911a367 100644 --- a/crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs +++ b/crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs @@ -596,6 +596,10 @@ fn u256_libfunc_cost(libfunc: &Uint256Concrete) -> Vec { } 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 }, + ], } } diff --git a/crates/cairo-lang-sierra-to-casm/src/invocations/int/unsigned256.rs b/crates/cairo-lang-sierra-to-casm/src/invocations/int/unsigned256.rs index 77aae4a656d..b0e4a65ede7 100644 --- a/crates/cairo-lang-sierra-to-casm/src/invocations/int/unsigned256.rs +++ b/crates/cairo-lang-sierra-to-casm/src/invocations/int/unsigned256.rs @@ -17,6 +17,7 @@ pub fn build( Uint256Concrete::IsZero(_) => build_u256_is_zero(builder), Uint256Concrete::Divmod(_) => build_u256_divmod(builder), Uint256Concrete::SquareRoot(_) => build_u256_sqrt(builder), + Uint256Concrete::InvModN(_) => build_u256_inv_mod_n(builder), } } @@ -324,3 +325,277 @@ fn build_u256_sqrt( }, )) } + +/// Generates casm instructions for `u256_inv_mod_n`. +fn build_u256_inv_mod_n( + builder: CompiledInvocationBuilder<'_>, +) -> Result { + let [range_check, b, n] = builder.try_get_refs()?; + let [range_check] = range_check.try_unpack()?; + let [b0, b1] = b.try_unpack()?; + let [n0, n1] = n.try_unpack()?; + + let mut casm_builder = CasmBuilder::default(); + add_input_variables! {casm_builder, + buffer(6) range_check; + deref b0; + deref b1; + deref n0; + deref n1; + }; + + casm_build_extend! {casm_builder, + const zero = 0; + const one = 1; + const u128_bound_minus_u65_bound = BigInt::from(2).pow(128) - BigInt::from(2).pow(65); + const u128_bound_minus_i16_upper_bound = u128::MAX - i16::MAX as u128; + const i16_lower_bound = i16::MIN; + const u128_limit = (BigInt::from(u128::MAX) + 1) as BigInt; + const u64_limit = (BigInt::from(u64::MAX) + 1) as BigInt; + let orig_range_check = range_check; + + tempvar g0_or_no_inv; + tempvar g1_option; + tempvar b_div_g_or_r0; + tempvar b_div_g_or_r1; + tempvar n_div_g_or_k0; + tempvar n_div_g_or_k1; + + // Find the inverse or an a proof there isn't one. + hint U256InvModN { + b0: b0, + b1: b1, + n0: n0, + n1: n1 + } into { + g0_or_no_inv: g0_or_no_inv, + g1_option: g1_option, + b_div_g_or_r0: b_div_g_or_r0, + b_div_g_or_r1: b_div_g_or_r1, + n_div_g_or_k0: n_div_g_or_k0, + n_div_g_or_k1: n_div_g_or_k1 + }; + + jump NoInverse if g0_or_no_inv != 0; + let r0 = b_div_g_or_r0; + let r1 = b_div_g_or_r1; + let k0 = n_div_g_or_k0; + let k1 = n_div_g_or_k1; + assert r0 = *(range_check++); + assert r1 = *(range_check++); + assert k0 = *(range_check++); + assert k1 = *(range_check++); + + // Assert r is less than n. + tempvar diff1 = n1 - r1; + + // Allocate memory cells for the hints, + // as well as for the memory used by just one branch. + ap += 18; + tempvar diff0; + tempvar diff0_min_1; + jump HighDiff if diff1 != 0; + assert diff0 = n0 - r0; + assert diff0_min_1 = diff0 - one; + assert diff0_min_1 = *(range_check++); + jump After; + HighDiff: + assert diff1 = *(range_check++); + After: + + tempvar r0b0_low; + tempvar r0b0_high; + hint WideMul128 { lhs: r0, rhs: b0 } into { low: r0b0_low, high: r0b0_high }; + tempvar r0b1_low; + tempvar r0b1_high; + hint WideMul128 { lhs: r0, rhs: b1 } into { low: r0b1_low, high: r0b1_high }; + tempvar r1b0_low; + tempvar r1b0_high; + hint WideMul128 { lhs: r1, rhs: b0 } into { low: r1b0_low, high: r1b0_high }; + tempvar r1b1_low; + tempvar r1b1_high; + hint WideMul128 { lhs: r1, rhs: b1 } into { low: r1b1_low, high: r1b1_high }; + + tempvar n0k0_low; + tempvar n0k0_high; + hint WideMul128 { lhs: n0, rhs: k0 } into { low: n0k0_low, high: n0k0_high }; + tempvar n0k1_low; + tempvar n0k1_high; + hint WideMul128 { lhs: n0, rhs: k1 } into { low: n0k1_low, high: n0k1_high }; + tempvar n1k0_low; + tempvar n1k0_high; + hint WideMul128 { lhs: n1, rhs: k0 } into { low: n1k0_low, high: n1k0_high }; + tempvar n1k1_low; + tempvar n1k1_high; + hint WideMul128 { lhs: n1, rhs: k1 } into { low: n1k1_low, high: n1k1_high }; + } + casm_build_extend! {casm_builder, + // Validating `r * b - 1 = k * n`. + // Validate limb0. + tempvar part0 = r0b0_low - one; + tempvar part1 = part0 - n0k0_low; + tempvar leftover = part1 / u128_limit; + // leftover is an integer in range: + // [(0 - 1 - u128::MAX) / u128_limit, (u128::MAX - 1 - 0) / u128_limit] ==> [-1, 0]. + tempvar leftover_sqr = leftover * leftover; + assert leftover_sqr = leftover_sqr * leftover_sqr; + + // Validate limb1. + tempvar part0 = r0b0_high + leftover; + tempvar part1 = part0 + r0b1_low; + tempvar part2 = part1 + r1b0_low; + tempvar part3 = part2 - n0k0_high; + tempvar part4 = part3 - n0k1_low; + tempvar part5 = part4 - n1k0_low; + tempvar leftover = part5 / u128_limit; + // leftover is an integer in range: + // [(-1 + 3 * 0 - 3 * u128::MAX) / u128_limit, (0 + 3 * u128::MAX - 3 * 0) / u128_limit] + // ==> [-2, 2]. + // TODO: Will this work? + // tempvar leftover_sqr = leftover * leftover; + // assert leftover_sqr = *(range_check++); + tempvar a = leftover - i16_lower_bound; + assert a = *(range_check++); + tempvar a = leftover + u128_bound_minus_i16_upper_bound; + assert a = *(range_check++); + + // Validate limb2. + tempvar part0 = r0b1_high + leftover; + tempvar part1 = part0 + r1b0_high; + tempvar part2 = part1 + r1b1_low; + tempvar part3 = part2 - n1k0_high; + tempvar part4 = part3 - n0k1_high; + tempvar part5 = part4 - n1k1_low; + tempvar leftover = part5 / u128_limit; + // leftover is an integer in range: + // [(-2 + 3 * 0 - 3 * u128::MAX) / u128_limit, (2 + 3 * u128::MAX - 3 * 0) / u128_limit] + // ==> [-2, 2]. + // TODO: Will this work? + // tempvar leftover_sqr = leftover * leftover; + // assert leftover_sqr = *(range_check++); + tempvar a = leftover - i16_lower_bound; + assert a = *(range_check++); + tempvar a = leftover + u128_bound_minus_i16_upper_bound; + assert a = *(range_check++); + + // Validate limb3. + assert n1k1_high = r1b1_high + leftover; + jump Done; + } + casm_build_extend! {casm_builder, + NoInverse: + let g0 = g0_or_no_inv; + let g1 = g1_option; + let b_div_g0 = b_div_g_or_r0; + let b_div_g1 = b_div_g_or_r1; + let n_div_g0 = n_div_g_or_k0; + let n_div_g1 = n_div_g_or_k1; + assert g0 = *(range_check++); + assert g1 = *(range_check++); + assert b_div_g0 = *(range_check++); + assert b_div_g1 = *(range_check++); + assert n_div_g0 = *(range_check++); + assert n_div_g1 = *(range_check++); + // Validating `g > 1`. + tempvar g0_minus_1; + jump GIsValid if g1 != 0; + assert g0_minus_1 = g0 - one; + jump GIsValid if g0_minus_1 != 0; + fail; + GIsValid: + + // Validating `g * b_div_g = b` and `g * n_div_g = n`. + + // Only calculate the upper word, since we already know the lower word is `b0`. + let g0bdg0_low = b0; + tempvar g0bdg0_high; + hint WideMul128 { lhs: g0, rhs: b_div_g0 } into { low: g0bdg0_low, high: g0bdg0_high }; + + // Only calculate the upper word, since we already know the lower word is `n0`. + let g0ndg0_low = n0; + tempvar g0ndg0_high; + hint WideMul128 { lhs: g0, rhs: n_div_g0 } into { low: g0ndg0_low, high: g0ndg0_high }; + + // No need for Limb 0 validations - since we didn't actually calculate it. + + // Validate limb1 for `b` and `n`. + // We know that limb2 and limb3 should be 0. + // Therfore `g1` is 0 or both `b1` and `n1` are 0. + // We also know that g0*b1, g1*b0, g0*n1 and g1*n0 should be smaller than 2**128. + // Therefore the smaller of each pair must be smaller than 2**64. + // So by checking this we can avoid wraparound on the prime. + // And we can instead check that the sum of small items of both is smaller than 2**65. + tempvar gbdg1; + tempvar gndg1; + tempvar smalls_sum; + jump BDG1_AND_NDG1_EQ_ZERO if g1 != 0; + // g1 is 0 - no need to multiply it by the b_div_g and n_div_g. + assert gbdg1 = b_div_g1 * g0; + assert gndg1 = n_div_g1 * g0; + tempvar g0_is_small; + hint TestLessThan { lhs: g0, rhs: u64_limit } into { dst: g0_is_small }; + jump G0IsSmall if g0_is_small != 0; + assert smalls_sum = b_div_g1 + n_div_g1; + jump MERGE; + G0IsSmall: + assert smalls_sum = g0; + jump MERGE; + BDG1_AND_NDG1_EQ_ZERO: + // b_div_g1 and n_div_g1 are 0 - no need to multiply them by g. + assert gbdg1 = b_div_g0 * g1; + assert gndg1 = n_div_g0 * g1; + assert b_div_g1 = zero; + assert n_div_g1 = zero; + tempvar g1_is_small; + hint TestLessThan { lhs: g1, rhs: u64_limit } into { dst: g1_is_small }; + jump G1IsSmall if g1_is_small != 0; + assert smalls_sum = b_div_g0 + n_div_g0; + jump MERGE; + G1IsSmall: + assert smalls_sum = g1; + MERGE: + tempvar smalls_sum_fixed = smalls_sum + u128_bound_minus_u65_bound; + assert smalls_sum_fixed = *(range_check++); + assert b1 = gbdg1 + g0bdg0_high; + assert n1 = gndg1 + g0ndg0_high; + + jump Failure; + Done: + }; + + let target_statement_id = get_non_fallthrough_statement_id(&builder); + Ok(builder.build_from_casm_builder( + casm_builder, + [ + ( + "Fallthrough", + &[ + &[range_check], + &[r0, r1], + &[r0, b0, r0b0_high, r0b0_low], + &[r0, b1, r0b1_high, r0b1_low], + &[r1, b0, r1b0_high, r1b0_low], + &[r1, b1, r1b1_high, r1b1_low], + &[n0, k0, n0k0_high, n0k0_low], + &[n0, k1, n0k1_high, n0k1_low], + &[n1, k0, n1k0_high, n1k0_low], + &[n1, k1, n1k1_high, n1k1_low], + ], + None, + ), + ( + "Failure", + &[ + &[range_check], + &[g0, b_div_g0, g0bdg0_high, g0bdg0_low], + &[g0, n_div_g0, g0ndg0_high, g0ndg0_low], + ], + Some(target_statement_id), + ), + ], + CostValidationInfo { + range_check_info: Some((orig_range_check, range_check)), + extra_costs: None, + }, + )) +} diff --git a/crates/cairo-lang-sierra/src/extensions/modules/int/unsigned256.rs b/crates/cairo-lang-sierra/src/extensions/modules/int/unsigned256.rs index 3a7e27d1ae9..d4b31cb1c32 100644 --- a/crates/cairo-lang-sierra/src/extensions/modules/int/unsigned256.rs +++ b/crates/cairo-lang-sierra/src/extensions/modules/int/unsigned256.rs @@ -1,8 +1,8 @@ use super::unsigned128::{U128MulGuaranteeType, Uint128Type}; use crate::define_libfunc_hierarchy; use crate::extensions::lib_func::{ - BranchSignature, LibfuncSignature, OutputVarInfo, ParamSignature, SierraApChange, - SignatureSpecializationContext, + BranchSignature, DeferredOutputKind, LibfuncSignature, OutputVarInfo, ParamSignature, + SierraApChange, SignatureSpecializationContext, }; use crate::extensions::modules::get_u256_type; use crate::extensions::non_zero::nonzero_ty; @@ -16,6 +16,7 @@ define_libfunc_hierarchy! { IsZero(Uint256IsZeroLibfunc), Divmod(Uint256DivmodLibfunc), SquareRoot(Uint256SquareRootLibfunc), + InvModN(Uint256InvModNLibfunc), }, Uint256Concrete } @@ -113,3 +114,62 @@ impl NoGenericArgsGenericLibfunc for Uint256SquareRootLibfunc { )) } } + +// Inverse Modulo N. +#[derive(Default)] +pub struct Uint256InvModNLibfunc; +impl NoGenericArgsGenericLibfunc for Uint256InvModNLibfunc { + const STR_ID: &'static str = "u256_guarantee_inv_mod_n"; + + fn specialize_signature( + &self, + context: &dyn SignatureSpecializationContext, + ) -> Result { + let nz_ty = nonzero_ty(context, &get_u256_type(context)?)?; + let range_check_type = context.get_concrete_type(RangeCheckType::id(), &[])?; + let rc_output = OutputVarInfo { + ty: range_check_type.clone(), + ref_info: OutputVarReferenceInfo::Deferred(DeferredOutputKind::AddConst { + param_idx: 0, + }), + }; + let guarantee_output = OutputVarInfo { + ty: context.get_concrete_type(U128MulGuaranteeType::id(), &[])?, + ref_info: OutputVarReferenceInfo::SimpleDerefs, + }; + Ok(LibfuncSignature { + param_signatures: vec![ + // Range check. + ParamSignature::new(range_check_type).with_allow_add_const(), + // b. + ParamSignature::new(nz_ty.clone()), + // N for modulos. + ParamSignature::new(nz_ty.clone()), + ], + branch_signatures: vec![ + // If the divisor has an inverse modulo N. + BranchSignature { + vars: vec![ + rc_output.clone(), + OutputVarInfo { ty: nz_ty, ref_info: OutputVarReferenceInfo::SimpleDerefs }, + guarantee_output.clone(), + guarantee_output.clone(), + guarantee_output.clone(), + guarantee_output.clone(), + guarantee_output.clone(), + guarantee_output.clone(), + guarantee_output.clone(), + guarantee_output.clone(), + ], + ap_change: SierraApChange::Known { new_vars_only: false }, + }, + // The divisor does not have an inverse modulo N. + BranchSignature { + vars: vec![rc_output, guarantee_output.clone(), guarantee_output], + ap_change: SierraApChange::Known { new_vars_only: false }, + }, + ], + fallthrough: Some(0), + }) + } +} diff --git a/crates/cairo-lang-starknet/src/allowed_libfuncs_lists/all.json b/crates/cairo-lang-starknet/src/allowed_libfuncs_lists/all.json index d6d7a86c9f6..d1794cc9f2f 100644 --- a/crates/cairo-lang-starknet/src/allowed_libfuncs_lists/all.json +++ b/crates/cairo-lang-starknet/src/allowed_libfuncs_lists/all.json @@ -148,6 +148,7 @@ "struct_construct", "struct_deconstruct", "struct_snapshot_deconstruct", + "u256_guarantee_inv_mod_n", "u256_safe_divmod", "u256_sqrt", "u256_is_zero", diff --git a/tests/e2e_test_data/libfuncs/u256 b/tests/e2e_test_data/libfuncs/u256 index 224b495c586..eacad942029 100644 --- a/tests/e2e_test_data/libfuncs/u256 +++ b/tests/e2e_test_data/libfuncs/u256 @@ -283,3 +283,324 @@ store_temp([3]) -> ([5]); // 2 return([4], [5]); // 3 test::foo@0([0]: RangeCheck, [1]: core::integer::u256) -> (RangeCheck, u128); + +//! > ========================================================================== + +//! > u256_inv_mod_n libfunc + +//! > test_comments + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +fn foo(b: NonZero, n: NonZero) -> Option> { + math::u256_inv_mod_n(b, n) +} + +//! > casm +%{ +from starkware.python.math_utils import igcdex + +b = memory[fp + -6] + memory[fp + -5] * 2**128 +n = memory[fp + -4] + memory[fp + -3] * 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[ap + 0] = g & 0xffffffffffffffffffffffffffffffff + memory[ap + 1] = g >> 128 + memory[ap + 2] = b_div_g & 0xffffffffffffffffffffffffffffffff + memory[ap + 3] = b_div_g >> 128 + memory[ap + 4] = n_div_g & 0xffffffffffffffffffffffffffffffff + memory[ap + 5] = n_div_g >> 128 +else: + r %= n + if r < 0: + r += n + k = (r * b - 1) // n + memory[ap + 0] = 0 + memory[ap + 2] = r & 0xffffffffffffffffffffffffffffffff + memory[ap + 3] = r >> 128 + memory[ap + 4] = k & 0xffffffffffffffffffffffffffffffff + memory[ap + 5] = k >> 128 +%} +jmp rel 56 if [ap + 0] != 0, ap++; +[ap + 1] = [[fp + -7] + 0], ap++; +[ap + 1] = [[fp + -7] + 1], ap++; +[ap + 1] = [[fp + -7] + 2], ap++; +[ap + 1] = [[fp + -7] + 3], ap++; +[fp + -3] = [ap + 1] + [ap + -2], ap++; +ap += 18; +jmp rel 8 if [ap + -18] != 0; +[fp + -4] = [ap + -17] + [ap + -22]; +[ap + -17] = [ap + -16] + 1; +[ap + -16] = [[fp + -7] + 4]; +jmp rel 3; +[ap + -18] = [[fp + -7] + 4]; +%{ (memory[ap + -14], memory[ap + -15]) = divmod(memory[ap + -22] * memory[fp + -6], 2**128) %} +%{ (memory[ap + -12], memory[ap + -13]) = divmod(memory[ap + -22] * memory[fp + -5], 2**128) %} +%{ (memory[ap + -10], memory[ap + -11]) = divmod(memory[ap + -21] * memory[fp + -6], 2**128) %} +%{ (memory[ap + -8], memory[ap + -9]) = divmod(memory[ap + -21] * memory[fp + -5], 2**128) %} +%{ (memory[ap + -6], memory[ap + -7]) = divmod(memory[fp + -4] * memory[ap + -20], 2**128) %} +%{ (memory[ap + -4], memory[ap + -5]) = divmod(memory[fp + -4] * memory[ap + -19], 2**128) %} +%{ (memory[ap + -2], memory[ap + -3]) = divmod(memory[fp + -3] * memory[ap + -20], 2**128) %} +%{ (memory[ap + 0], memory[ap + -1]) = divmod(memory[fp + -3] * memory[ap + -19], 2**128) %} +[ap + -15] = [ap + 1] + 1, ap++; +[ap + 0] = [ap + 1] + [ap + -8], ap++; +[ap + 0] = [ap + 1] * 340282366920938463463374607431768211456, ap++; +[ap + 1] = [ap + 0] * [ap + 0], ap++; +[ap + 0] = [ap + 0] * [ap + 0], ap++; +[ap + 0] = [ap + -19] + [ap + -2], ap++; +[ap + 0] = [ap + -1] + [ap + -19], ap++; +[ap + 0] = [ap + -1] + [ap + -18], ap++; +[ap + -1] = [ap + 0] + [ap + -14], ap++; +[ap + -1] = [ap + 0] + [ap + -14], ap++; +[ap + -1] = [ap + 0] + [ap + -13], ap++; +[ap + -1] = [ap + 0] * 340282366920938463463374607431768211456, ap++; +[ap + -1] = [ap + 0] + -32768, ap++; +[ap + -1] = [[fp + -7] + 5]; +[ap + 0] = [ap + -2] + 340282366920938463463374607431768178688, ap++; +[ap + -1] = [[fp + -7] + 6]; +[ap + 0] = [ap + -26] + [ap + -3], ap++; +[ap + 0] = [ap + -1] + [ap + -25], ap++; +[ap + 0] = [ap + -1] + [ap + -25], ap++; +[ap + -1] = [ap + 0] + [ap + -19], ap++; +[ap + -1] = [ap + 0] + [ap + -22], ap++; +[ap + -1] = [ap + 0] + [ap + -20], ap++; +[ap + -1] = [ap + 0] * 340282366920938463463374607431768211456, ap++; +[ap + -1] = [ap + 0] + -32768, ap++; +[ap + -1] = [[fp + -7] + 7]; +[ap + 0] = [ap + -2] + 340282366920938463463374607431768178688, ap++; +[ap + -1] = [[fp + -7] + 8]; +[ap + -23] = [ap + -31] + [ap + -3]; +jmp rel 47; +[ap + -1] = [[fp + -7] + 0], ap++; +[ap + -1] = [[fp + -7] + 1], ap++; +[ap + -1] = [[fp + -7] + 2], ap++; +[ap + -1] = [[fp + -7] + 3], ap++; +[ap + -1] = [[fp + -7] + 4], ap++; +[ap + -1] = [[fp + -7] + 5]; +jmp rel 8 if [ap + -5] != 0, ap++; +[ap + -7] = [ap + -1] + 1; +jmp rel 4 if [ap + -1] != 0; +[fp + -1] = [fp + -1] + 1; +%{ (memory[ap + 0], memory[fp + -6]) = divmod(memory[ap + -7] * memory[ap + -5], 2**128) %} +%{ (memory[ap + 1], memory[fp + -4]) = divmod(memory[ap + -7] * memory[ap + -3], 2**128) %} +jmp rel 12 if [ap + -6] != 0, ap++; +[ap + 1] = [ap + -5] * [ap + -8], ap++; +[ap + 1] = [ap + -4] * [ap + -9], ap++; +%{ memory[ap + 2] = memory[ap + -10] < 18446744073709551616 %} +jmp rel 5 if [ap + 2] != 0, ap++; +[ap + 0] = [ap + -8] + [ap + -6], ap++; +jmp rel 17, ap++; +[ap + 0] = [ap + -11], ap++; +jmp rel 14, ap++; +[ap + 1] = [ap + -6] * [ap + -7], ap++; +[ap + 1] = [ap + -5] * [ap + -8], ap++; +[ap + -7] = 0, ap++; +[ap + -6] = 0, ap++; +%{ memory[ap + 0] = memory[ap + -11] < 18446744073709551616 %} +jmp rel 5 if [ap + 0] != 0, ap++; +[ap + -2] = [ap + -11] + [ap + -9]; +jmp rel 3; +[ap + -2] = [ap + -12]; +[ap + 0] = [ap + -2] + 340282366920938463426481119284349108224, ap++; +[ap + -1] = [[fp + -7] + 6]; +[fp + -5] = [ap + -5] + [ap + -7]; +[fp + -3] = [ap + -4] + [ap + -6]; +jmp rel 66; +[ap + 0] = [fp + -7] + 9, ap++; +[ap + 0] = [fp + -3], ap++; +[ap + 0] = [ap + -44], ap++; +[ap + 0] = [ap + -26], ap++; +[ap + 0] = [ap + -28], ap++; +call rel 83; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [fp + -3], ap++; +[ap + 0] = [ap + -68], ap++; +[ap + 0] = [ap + -51], ap++; +[ap + 0] = [ap + -53], ap++; +call rel 76; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [fp + -4], ap++; +[ap + 0] = [ap + -90], ap++; +[ap + 0] = [ap + -76], ap++; +[ap + 0] = [ap + -78], ap++; +call rel 69; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [fp + -4], ap++; +[ap + 0] = [ap + -114], ap++; +[ap + 0] = [ap + -101], ap++; +[ap + 0] = [ap + -103], ap++; +call rel 62; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [ap + -137], ap++; +[ap + 0] = [fp + -5], ap++; +[ap + 0] = [ap + -126], ap++; +[ap + 0] = [ap + -128], ap++; +call rel 55; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [ap + -160], ap++; +[ap + 0] = [fp + -6], ap++; +[ap + 0] = [ap + -151], ap++; +[ap + 0] = [ap + -153], ap++; +call rel 48; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [ap + -184], ap++; +[ap + 0] = [fp + -5], ap++; +[ap + 0] = [ap + -176], ap++; +[ap + 0] = [ap + -178], ap++; +call rel 41; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [ap + -207], ap++; +[ap + 0] = [fp + -6], ap++; +[ap + 0] = [ap + -201], ap++; +[ap + 0] = [ap + -203], ap++; +call rel 34; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = 0, ap++; +[ap + 0] = [ap + -231], ap++; +[ap + 0] = [ap + -231], ap++; +jmp rel 26; +ap += 171; +[ap + 0] = [fp + -7] + 7, ap++; +[ap + 0] = [ap + -186], ap++; +[ap + 0] = [ap + -183], ap++; +[ap + 0] = [ap + -180], ap++; +[ap + 0] = [fp + -4], ap++; +call rel 17; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [ap + -209], ap++; +[ap + 0] = [ap + -208], ap++; +[ap + 0] = [ap + -204], ap++; +[ap + 0] = [fp + -6], ap++; +call rel 10; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = 1, ap++; +[ap + 0] = 0, ap++; +[ap + 0] = 0, ap++; +ret; +%{ (memory[ap + 1], memory[ap + 0]) = divmod(memory[fp + -6], 18446744073709551616) %} +[ap + 2] = [ap + 0] + 340282366920938463444927863358058659840, ap++; +[ap + 1] = [[fp + -7] + 0], ap++; +[ap + -2] = [[fp + -7] + 1], ap++; +[ap + -2] = [[fp + -7] + 2]; +[ap + 0] = [ap + -2] * 18446744073709551616, ap++; +[fp + -6] = [ap + -1] + [ap + -4]; +[ap + 0] = [ap + -4] * [fp + -5], ap++; +[ap + 0] = [ap + -4] * [fp + -5], ap++; +%{ (memory[ap + 0], memory[ap + 1]) = divmod(memory[ap + -1], 18446744073709551616) %} +[ap + 2] = [ap + 1] + 340282366920938463444927863358058659840, ap++; +[ap + 1] = [[fp + -7] + 3], ap++; +[ap + -1] = [[fp + -7] + 4], ap++; +[ap + -3] = [[fp + -7] + 5]; +[ap + 0] = [ap + -3] * 18446744073709551616, ap++; +[ap + -5] = [ap + -1] + [ap + -3]; +[ap + 0] = [ap + -3] * 18446744073709551616, ap++; +[ap + 3] = [ap + -7] + [ap + -1], ap++; +%{ (memory[ap + -1], memory[fp + -3]) = divmod(memory[ap + 2], 340282366920938463463374607431768211456) %} +[ap + 0] = [ap + -1] + 340282366920938463426481119284349108224, ap++; +[ap + -1] = [[fp + -7] + 6], ap++; +[ap + -3] = [[fp + -7] + 7], ap++; +[fp + -3] = [[fp + -7] + 8]; +[ap + -2] = [ap + -4] * 340282366920938463463374607431768211456; +[ap + -1] = [ap + -2] + [fp + -3]; +[fp + -4] = [ap + -9] + [ap + -4]; +[ap + 0] = [fp + -7] + 9, ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 35070}) +core::integer::U128MulGuaranteeDestruct::destruct: OrderedHashMap({Const: 3030}) + +//! > sierra_code +type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false]; +type u128 = u128 [storable: true, drop: true, dup: true, zero_sized: false]; +type core::integer::u256 = Struct [storable: true, drop: true, dup: true, zero_sized: false]; +type NonZero = NonZero [storable: true, drop: true, dup: true, zero_sized: false]; +type Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type core::option::Option::> = Enum>, NonZero, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; +type U128MulGuarantee = U128MulGuarantee [storable: true, drop: false, dup: false, zero_sized: false]; + +libfunc u256_guarantee_inv_mod_n = u256_guarantee_inv_mod_n; +libfunc branch_align = branch_align; +libfunc store_temp = store_temp; +libfunc store_temp = store_temp; +libfunc function_call = function_call; +libfunc drop = drop; +libfunc enum_init>, 0> = enum_init>, 0>; +libfunc store_temp>> = store_temp>>; +libfunc jump = jump; +libfunc struct_construct = struct_construct; +libfunc enum_init>, 1> = enum_init>, 1>; +libfunc rename = rename; +libfunc rename>> = rename>>; +libfunc u128_mul_guarantee_verify = u128_mul_guarantee_verify; +libfunc store_temp = store_temp; + +u256_guarantee_inv_mod_n([0], [1], [2]) { fallthrough([3], [4], [5], [6], [7], [8], [9], [10], [11], [12]) 38([13], [14], [15]) }; // 0 +branch_align() -> (); // 1 +store_temp([3]) -> ([18]); // 2 +store_temp([12]) -> ([19]); // 3 +function_call([18], [19]) -> ([16], [17]); // 4 +drop([17]) -> (); // 5 +store_temp([16]) -> ([22]); // 6 +store_temp([11]) -> ([23]); // 7 +function_call([22], [23]) -> ([20], [21]); // 8 +drop([21]) -> (); // 9 +store_temp([20]) -> ([26]); // 10 +store_temp([10]) -> ([27]); // 11 +function_call([26], [27]) -> ([24], [25]); // 12 +drop([25]) -> (); // 13 +store_temp([24]) -> ([30]); // 14 +store_temp([9]) -> ([31]); // 15 +function_call([30], [31]) -> ([28], [29]); // 16 +drop([29]) -> (); // 17 +store_temp([28]) -> ([34]); // 18 +store_temp([8]) -> ([35]); // 19 +function_call([34], [35]) -> ([32], [33]); // 20 +drop([33]) -> (); // 21 +store_temp([32]) -> ([38]); // 22 +store_temp([7]) -> ([39]); // 23 +function_call([38], [39]) -> ([36], [37]); // 24 +drop([37]) -> (); // 25 +store_temp([36]) -> ([42]); // 26 +store_temp([6]) -> ([43]); // 27 +function_call([42], [43]) -> ([40], [41]); // 28 +drop([41]) -> (); // 29 +store_temp([40]) -> ([46]); // 30 +store_temp([5]) -> ([47]); // 31 +function_call([46], [47]) -> ([44], [45]); // 32 +drop([45]) -> (); // 33 +enum_init>, 0>([4]) -> ([48]); // 34 +store_temp([44]) -> ([49]); // 35 +store_temp>>([48]) -> ([50]); // 36 +jump() { 51() }; // 37 +branch_align() -> (); // 38 +store_temp([13]) -> ([53]); // 39 +store_temp([15]) -> ([54]); // 40 +function_call([53], [54]) -> ([51], [52]); // 41 +drop([52]) -> (); // 42 +store_temp([51]) -> ([57]); // 43 +store_temp([14]) -> ([58]); // 44 +function_call([57], [58]) -> ([55], [56]); // 45 +drop([56]) -> (); // 46 +struct_construct() -> ([59]); // 47 +enum_init>, 1>([59]) -> ([60]); // 48 +store_temp([55]) -> ([49]); // 49 +store_temp>>([60]) -> ([50]); // 50 +rename([49]) -> ([61]); // 51 +rename>>([50]) -> ([62]); // 52 +return([61], [62]); // 53 +u128_mul_guarantee_verify([0], [1]) -> ([2]); // 54 +struct_construct() -> ([3]); // 55 +store_temp([2]) -> ([4]); // 56 +store_temp([3]) -> ([5]); // 57 +return([4], [5]); // 58 + +test::foo@0([0]: RangeCheck, [1]: NonZero, [2]: NonZero) -> (RangeCheck, core::option::Option::>); +core::integer::U128MulGuaranteeDestruct::destruct@54([0]: RangeCheck, [1]: U128MulGuarantee) -> (RangeCheck, Unit);