From e00f9ab0ffe2578f38661639a1bae77e3e22b065 Mon Sep 17 00:00:00 2001 From: Ori Ziv Date: Wed, 5 Jul 2023 17:33:12 +0300 Subject: [PATCH] Added libfunc for u256_inv_mod_n. --- corelib/src/math.cairo | 35 +- corelib/src/test/math_test.cairo | 121 ++++--- crates/cairo-lang-casm/src/hints/mod.rs | 72 ++++ crates/cairo-lang-runner/src/casm_run/mod.rs | 55 ++- .../src/core_libfunc_ap_change.rs | 1 + .../src/core_libfunc_cost_base.rs | 4 + .../src/invocations/int/unsigned256.rs | 265 +++++++++++++++ .../src/extensions/modules/int/unsigned256.rs | 56 +++ .../src/allowed_libfuncs_lists/all.json | 3 +- tests/e2e_test_data/libfuncs/u256 | 321 ++++++++++++++++++ 10 files changed, 874 insertions(+), 59 deletions(-) diff --git a/corelib/src/math.cairo b/corelib/src/math.cairo index fd80276867c..62e7fdbbe42 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,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 +) -> Result< + ( + NonZero, + 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) -> Option> { + 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, n: NonZero) -> Option { - Option::Some(u256_mul_mod_n(a, inv_mod(b, n)?, n)) +fn u256_div_mod_n(a: u256, b: u256, n: NonZero) -> Option { + Option::Some(u256_mul_mod_n(a, u256_inv_mod(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 450e28806c3..8384b1978ba 100644 --- a/corelib/src/test/math_test.cairo +++ b/corelib/src/test/math_test.cairo @@ -1,42 +1,39 @@ +/// Helper for making a non-zero value. +fn nz>>(n: N) -> NonZero { + 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'); @@ -46,47 +43,67 @@ 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)'); - - 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, 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( + 0x2ab1f535168b19cef4bf517c5b010e089820273ac99e934bba57b1afb49856aa, + 0xea9195982bd472e30e5146ad7cb0acd954cbc75032a298ac73234b6b05e28cc1, + nz(0x4075f980fab77a3fde536dbaae600f5ea1540e01837dcec64c1f379613aa4d18) + ) + .is_none(), + 'Random no inverse 1' + ); + assert( + math::u256_div_mod_n( + 0xe3f5c3c783073fdcf77c0459634bd8111698220fb18ef110d9a7b8b39de6289a, + 0x85ef555d7a0aa34019c138defc40a1d3683dc1caa505bff286dd8069a28a2e4c, + nz(0xd71e5a5f4a4d1af45e703f9e13d1305ce149313037956247ad5edfe3e81d6353) + ) + .is_none(), + 'Random no inverse 2' + ); + let large_gcd = 544274268426609069862727999887493286337307; + assert( + math::u256_div_mod_n( + 0xa9c760e223af72dd9c1d218d352623d72124eff55633996c14cfc3687dbfb60f, + large_gcd * 405474585360891598201612396201, + nz(large_gcd * 953612161653462500754187030739) + ) + .is_none(), + 'Random no inverse 3 - large gcd' + ); + 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' + ); } diff --git a/crates/cairo-lang-casm/src/hints/mod.rs b/crates/cairo-lang-casm/src/hints/mod.rs index d5052d56098..4afeabd1e36 100644 --- a/crates/cairo-lang-casm/src/hints/mod.rs +++ b/crates/cairo-lang-casm/src/hints/mod.rs @@ -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 @@ -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 + " + ) + } } } } diff --git a/crates/cairo-lang-runner/src/casm_run/mod.rs b/crates/cairo-lang-runner/src/casm_run/mod.rs index edffa41030d..e711545de07 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; @@ -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(()) } 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..d22ac030047 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(46), 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..884a7a6f860 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: 40, 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..d93c65aa53a 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,267 @@ 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 s_or_r0; + tempvar s_or_r1; + tempvar t_or_k0; + tempvar t_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, + s_or_r0: s_or_r0, + s_or_r1: s_or_r1, + t_or_k0: t_or_k0, + t_or_k1: t_or_k1 + }; + + jump NoInverse if g0_or_no_inv != 0; + let r0 = s_or_r0; + let r1 = s_or_r1; + let k0 = t_or_k0; + let k1 = t_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 `k * n + 1 - r * b = 0`. + // Validate limb0. + tempvar part0 = n0k0_low + one; + tempvar part1 = part0 - r0b0_low; + tempvar leftover = part1 / u128_limit; + // leftover is an integer in range: + // [(0 + 1 - u128::MAX) / u128_limit, (u128::MAX + 1 - 0) / u128_limit] ==> [0, 1]. + assert leftover = leftover * leftover; + + // Validate limb1. + tempvar part0 = n0k0_high + leftover; + tempvar part1 = part0 + n0k1_low; + tempvar part2 = part1 + n1k0_low; + tempvar part3 = part2 - r0b0_high; + tempvar part4 = part3 - r0b1_low; + tempvar part5 = part4 - r1b0_low; + tempvar leftover = part5 / u128_limit; + // leftover is an integer in range: + // [(0 + 3 * 0 - 3 * u128::MAX) / u128_limit, (1 + 3 * u128::MAX - 3 * 0) / u128_limit] + // ==> [-2, 2]. + 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 = n0k1_high + leftover; + tempvar part1 = part0 + n1k0_high; + tempvar part2 = part1 + n1k1_low; + tempvar part3 = part2 - r1b0_high; + tempvar part4 = part3 - r0b1_high; + tempvar part5 = part4 - r1b1_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]. + 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 r1b1_high = n1k1_high + leftover; + jump Done; + } + casm_build_extend! {casm_builder, + NoInverse: + let g0 = g0_or_no_inv; + let g1 = g1_option; + let s0 = s_or_r0; + let s1 = s_or_r1; + let t0 = t_or_k0; + let t1 = t_or_k1; + assert g0 = *(range_check++); + assert g1 = *(range_check++); + assert s0 = *(range_check++); + assert s1 = *(range_check++); + assert t0 = *(range_check++); + assert t1 = *(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 * s = b` and `g * t = n`. + + // Only calculate the upper word, since we already know the lower word is `b0`. + let g0s0_low = b0; + tempvar g0s0_high; + hint WideMul128 { lhs: g0, rhs: s0 } into { low: g0s0_low, high: g0s0_high }; + + // Only calculate the upper word, since we already know the lower word is `n0`. + let g0t0_low = n0; + tempvar g0t0_high; + hint WideMul128 { lhs: g0, rhs: t0 } into { low: g0t0_low, high: g0t0_high }; + + // No need for Limb 0 validations - since we didn't actually calculate it. + + // Validate limb1 for `b` and `n`. + // We know that for honest prover (completeness) `limb2` and `limb3` are 0. + // Therefore, `g1` is 0 or both `s1` and `t1` are 0. + // We also know that g0*s1, g1*s0, g0*t1 and g1*t0 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 overflow in the field. + // In fact, instead of checking that two items are smaller than 2**64, we can + // check that their sum is smaller than 2**65. + tempvar gs1; + tempvar gt1; + tempvar smalls_sum; + jump S1_AND_T1_EQ_ZERO if g1 != 0; + // `g1` is 0 - no need to multiply it by `s` and `t`. + assert gs1 = s1 * g0; + assert gt1 = t1 * 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 = s1 + t1; + jump MERGE; + G0IsSmall: + assert smalls_sum = g0; + jump MERGE; + S1_AND_T1_EQ_ZERO: + // s1 and t1 are 0 - no need to multiply them by g. + assert gs1 = s0 * g1; + assert gt1 = t0 * g1; + assert s1 = zero; + assert t1 = 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 = s0 + t0; + 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 = gs1 + g0s0_high; + assert n1 = gt1 + g0t0_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, s0, g0s0_high, g0s0_low], &[g0, t0, g0t0_high, g0t0_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..a2b7e26061a 100644 --- a/crates/cairo-lang-sierra/src/extensions/modules/int/unsigned256.rs +++ b/crates/cairo-lang-sierra/src/extensions/modules/int/unsigned256.rs @@ -16,6 +16,7 @@ define_libfunc_hierarchy! { IsZero(Uint256IsZeroLibfunc), Divmod(Uint256DivmodLibfunc), SquareRoot(Uint256SquareRootLibfunc), + InvModN(Uint256InvModNLibfunc), }, Uint256Concrete } @@ -113,3 +114,58 @@ 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 u256_ty = get_u256_type(context)?; + let nz_ty = nonzero_ty(context, &u256_ty)?; + let range_check_type = context.get_concrete_type(RangeCheckType::id(), &[])?; + let rc_output = OutputVarInfo::new_builtin(range_check_type.clone(), 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 (divisor). + ParamSignature::new(u256_ty), + // n (modulus). + 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..48097d26ff5 100644 --- a/crates/cairo-lang-starknet/src/allowed_libfuncs_lists/all.json +++ b/crates/cairo-lang-starknet/src/allowed_libfuncs_lists/all.json @@ -148,9 +148,10 @@ "struct_construct", "struct_deconstruct", "struct_snapshot_deconstruct", + "u256_guarantee_inv_mod_n", + "u256_is_zero", "u256_safe_divmod", "u256_sqrt", - "u256_is_zero", "u128_const", "u128_eq", "u128_is_zero", diff --git a/tests/e2e_test_data/libfuncs/u256 b/tests/e2e_test_data/libfuncs/u256 index 224b495c586..21c307a6707 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_guarantee_inv_mod_n libfunc + +//! > test_comments + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +fn foo(b: u256, n: NonZero) -> Option> { + match math::u256_guarantee_inv_mod_n(b, n) { + Result::Ok((r, _, _, _, _, _, _, _, _)) => Option::Some(r), + Result::Err(_) => Option::None(()), + } +} + +//! > casm +%{ +from starkware.python.math_utils import igcdex + +b = memory[fp + -6] + (memory[fp + -5] << 128) +n = memory[fp + -4] + (memory[fp + -3] << 128) + +(_, r, g) = igcdex(n, b) +if g != 1: + if g % 2 == 0: + g = 2 + s = b // g + t = n // g + memory[ap + 0] = g & 0xffffffffffffffffffffffffffffffff + memory[ap + 1] = g >> 128 + memory[ap + 2] = s & 0xffffffffffffffffffffffffffffffff + memory[ap + 3] = s >> 128 + memory[ap + 4] = t & 0xffffffffffffffffffffffffffffffff + memory[ap + 5] = t >> 128 +else: + 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 55 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 + 1] = [ap + -7] + 1, ap++; +[ap + 0] = [ap + 1] + [ap + -16], ap++; +[ap + 0] = [ap + 1] * 340282366920938463463374607431768211456, ap++; +[ap + 0] = [ap + 0] * [ap + 0], ap++; +[ap + 0] = [ap + -10] + [ap + -1], ap++; +[ap + 0] = [ap + -1] + [ap + -10], ap++; +[ap + 0] = [ap + -1] + [ap + -9], ap++; +[ap + -1] = [ap + 0] + [ap + -21], ap++; +[ap + -1] = [ap + 0] + [ap + -21], ap++; +[ap + -1] = [ap + 0] + [ap + -20], 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 + -17] + [ap + -3], ap++; +[ap + 0] = [ap + -1] + [ap + -16], ap++; +[ap + 0] = [ap + -1] + [ap + -16], ap++; +[ap + -1] = [ap + 0] + [ap + -26], ap++; +[ap + -1] = [ap + 0] + [ap + -29], ap++; +[ap + -1] = [ap + 0] + [ap + -27], 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 + -30] = [ap + -22] + [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 + -43], ap++; +[ap + 0] = [ap + -25], ap++; +[ap + 0] = [ap + -27], ap++; +call rel 83; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [fp + -3], ap++; +[ap + 0] = [ap + -67], ap++; +[ap + 0] = [ap + -50], ap++; +[ap + 0] = [ap + -52], ap++; +call rel 76; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [fp + -4], ap++; +[ap + 0] = [ap + -89], ap++; +[ap + 0] = [ap + -75], ap++; +[ap + 0] = [ap + -77], ap++; +call rel 69; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [fp + -4], ap++; +[ap + 0] = [ap + -113], ap++; +[ap + 0] = [ap + -100], ap++; +[ap + 0] = [ap + -102], ap++; +call rel 62; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [ap + -136], ap++; +[ap + 0] = [fp + -5], ap++; +[ap + 0] = [ap + -125], ap++; +[ap + 0] = [ap + -127], ap++; +call rel 55; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [ap + -159], ap++; +[ap + 0] = [fp + -6], ap++; +[ap + 0] = [ap + -150], ap++; +[ap + 0] = [ap + -152], ap++; +call rel 48; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [ap + -183], ap++; +[ap + 0] = [fp + -5], ap++; +[ap + 0] = [ap + -175], ap++; +[ap + 0] = [ap + -177], ap++; +call rel 41; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [ap + -206], ap++; +[ap + 0] = [fp + -6], ap++; +[ap + 0] = [ap + -200], ap++; +[ap + 0] = [ap + -202], ap++; +call rel 34; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = 0, ap++; +[ap + 0] = [ap + -230], ap++; +[ap + 0] = [ap + -230], ap++; +jmp rel 26; +ap += 170; +[ap + 0] = [fp + -7] + 7, ap++; +[ap + 0] = [ap + -185], ap++; +[ap + 0] = [ap + -182], ap++; +[ap + 0] = [ap + -179], ap++; +[ap + 0] = [fp + -4], ap++; +call rel 17; +[ap + 0] = [ap + -1], ap++; +[ap + 0] = [ap + -208], ap++; +[ap + 0] = [ap + -207], ap++; +[ap + 0] = [ap + -203], 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: 34970}) +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]: core::integer::u256, [2]: NonZero) -> (RangeCheck, core::option::Option::>); +core::integer::U128MulGuaranteeDestruct::destruct@54([0]: RangeCheck, [1]: U128MulGuarantee) -> (RangeCheck, Unit);