diff --git a/Cargo.toml b/Cargo.toml index 97a85fa..d4460f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -cairo-lang-sierra = "2.7.1" -cairo-lang-utils = "2.7.1" +cairo-lang-sierra = "=2.7.1" +cairo-lang-utils = "=2.7.1" clap = { version = "4.5.16", features = ["derive"] } k256 = "0.13.3" keccak = "0.1.5" @@ -25,8 +25,8 @@ tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } [dev-dependencies] -cairo-lang-compiler = "2.7.0" -cairo-lang-starknet = "2.7.0" +cairo-lang-compiler = "=2.7.0" +cairo-lang-starknet = "=2.7.0" # On dev optimize dependencies a bit so it's not as slow. [profile.dev.package."*"] diff --git a/src/main.rs b/src/main.rs index 37bbec5..265d1d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ use self::args::CmdArgs; use cairo_lang_sierra::{ - extensions::{core::CoreTypeConcrete, starknet::StarkNetTypeConcrete}, + extensions::{ + circuit::CircuitTypeConcrete, core::CoreTypeConcrete, starknet::StarkNetTypeConcrete, + }, ProgramParser, }; use clap::Parser; @@ -65,10 +67,14 @@ fn main() -> Result<(), Box> { CoreTypeConcrete::Felt252(_) => Value::parse_felt(&iter.next().unwrap()), CoreTypeConcrete::GasBuiltin(_) => Value::U128(args.available_gas.unwrap()), CoreTypeConcrete::RangeCheck(_) + | CoreTypeConcrete::RangeCheck96(_) | CoreTypeConcrete::Bitwise(_) | CoreTypeConcrete::Pedersen(_) | CoreTypeConcrete::Poseidon(_) - | CoreTypeConcrete::SegmentArena(_) => Value::Unit, + | CoreTypeConcrete::SegmentArena(_) + | CoreTypeConcrete::Circuit( + CircuitTypeConcrete::AddMod(_) | CircuitTypeConcrete::MulMod(_), + ) => Value::Unit, CoreTypeConcrete::StarkNet(inner) => match inner { StarkNetTypeConcrete::System(_) => Value::Unit, _ => todo!(), diff --git a/src/value.rs b/src/value.rs index f72ac05..898444a 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,5 +1,6 @@ use cairo_lang_sierra::{ extensions::{ + circuit::CircuitTypeConcrete, core::{CoreLibfunc, CoreType, CoreTypeConcrete}, starknet::StarkNetTypeConcrete, ConcreteType, @@ -7,7 +8,7 @@ use cairo_lang_sierra::{ ids::ConcreteTypeId, program_registry::ProgramRegistry, }; -use num_bigint::BigInt; +use num_bigint::{BigInt, BigUint}; use serde::Serialize; use starknet_types_core::felt::Felt; use std::{collections::HashMap, fmt::Debug, ops::Range}; @@ -22,6 +23,9 @@ pub enum Value { range: Range, value: BigInt, }, + Circuit(Vec), + CircuitModulus(BigUint), + CircuitOutputs(Vec), Enum { self_ty: ConcreteTypeId, index: usize, @@ -50,6 +54,7 @@ pub enum Value { }, I8(i8), Struct(Vec), + U256(u128, u128), U128(u128), U16(u16), U32(u32), @@ -82,6 +87,9 @@ impl Value { CoreTypeConcrete::Array(info) => { matches!(self, Self::Array { ty, .. } if *ty == info.ty) } + CoreTypeConcrete::BoundedInt(info) => { + matches!(self, Self::BoundedInt { range, .. } if range.start == info.range.lower && range.end == info.range.upper) + } CoreTypeConcrete::Enum(_) => { matches!(self, Self::Enum { self_ty, .. } if self_ty == type_id) } @@ -94,6 +102,12 @@ impl Value { CoreTypeConcrete::NonZero(info) => self.is(registry, &info.ty), CoreTypeConcrete::Sint8(_) => matches!(self, Self::I8(_)), CoreTypeConcrete::Snapshot(info) => self.is(registry, &info.ty), + CoreTypeConcrete::StarkNet( + StarkNetTypeConcrete::ClassHash(_) + | StarkNetTypeConcrete::ContractAddress(_) + | StarkNetTypeConcrete::StorageBaseAddress(_) + | StarkNetTypeConcrete::StorageAddress(_), + ) => matches!(self, Self::Felt(_)), CoreTypeConcrete::Struct(info) => { matches!(self, Self::Struct(members) if members.len() == info.members.len() @@ -105,9 +119,19 @@ impl Value { } CoreTypeConcrete::Uint8(_) => matches!(self, Self::U8(_)), CoreTypeConcrete::Uint32(_) => matches!(self, Self::U32(_)), + CoreTypeConcrete::Uint128(_) + | CoreTypeConcrete::Circuit(CircuitTypeConcrete::U96Guarantee(_)) => { + matches!(self, Self::U128(_)) + } // Unused builtins (mapped to `Value::Unit`). - CoreTypeConcrete::RangeCheck(_) | CoreTypeConcrete::SegmentArena(_) => { + CoreTypeConcrete::RangeCheck(_) + | CoreTypeConcrete::SegmentArena(_) + | CoreTypeConcrete::RangeCheck96(_) + | CoreTypeConcrete::Circuit( + CircuitTypeConcrete::AddMod(_) | CircuitTypeConcrete::MulMod(_), + ) + | CoreTypeConcrete::StarkNet(StarkNetTypeConcrete::System(_)) => { matches!(self, Self::Unit) } diff --git a/src/vm.rs b/src/vm.rs index 3f51a0d..7c4a5d7 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -27,6 +27,7 @@ mod r#box; mod branch_align; mod bytes31; mod cast; +mod circuit; mod r#const; mod drop; mod dup; @@ -290,7 +291,7 @@ fn eval<'a>( CoreConcreteLibfunc::BranchAlign(info) => self::branch_align::eval(registry, info, args), CoreConcreteLibfunc::Bytes31(selector) => self::bytes31::eval(registry, selector, args), CoreConcreteLibfunc::Cast(selector) => self::cast::eval(registry, selector, args), - CoreConcreteLibfunc::Circuit(_) => todo!(), + CoreConcreteLibfunc::Circuit(selector) => self::circuit::eval(registry, selector, args), CoreConcreteLibfunc::Const(selector) => self::r#const::eval(registry, selector, args), CoreConcreteLibfunc::Coupon(_) => todo!(), CoreConcreteLibfunc::CouponCall(_) => todo!(), diff --git a/src/vm/circuit.rs b/src/vm/circuit.rs new file mode 100644 index 0000000..b9593a4 --- /dev/null +++ b/src/vm/circuit.rs @@ -0,0 +1,355 @@ +use std::u8; + +use super::EvalAction; +use crate::Value; +use cairo_lang_sierra::{ + extensions::{ + circuit::{ + CircuitConcreteLibfunc, CircuitTypeConcrete, ConcreteGetOutputLibFunc, + ConcreteU96LimbsLessThanGuaranteeVerifyLibfunc, + }, + core::{CoreLibfunc, CoreType, CoreTypeConcrete}, + lib_func::{SignatureAndTypeConcreteLibfunc, SignatureOnlyConcreteLibfunc}, + }, + program_registry::ProgramRegistry, +}; +use num_bigint::{BigInt, BigUint, Sign, ToBigInt}; +use smallvec::smallvec; +use starknet_types_core::felt::Felt; + +pub fn eval( + registry: &ProgramRegistry, + selector: &CircuitConcreteLibfunc, + args: Vec, +) -> EvalAction { + match selector { + CircuitConcreteLibfunc::AddInput(info) => eval_add_input(registry, info, args), + CircuitConcreteLibfunc::Eval(info) => eval_eval(registry, info, args), + CircuitConcreteLibfunc::GetDescriptor(info) => eval_get_descriptor(registry, info, args), + CircuitConcreteLibfunc::InitCircuitData(info) => { + eval_init_circuit_data(registry, info, args) + } + CircuitConcreteLibfunc::GetOutput(info) => eval_get_output(registry, info, args), + CircuitConcreteLibfunc::TryIntoCircuitModulus(info) => { + eval_try_into_circuit_modulus(registry, info, args) + } + CircuitConcreteLibfunc::FailureGuaranteeVerify(info) => { + eval_failure_guarantee_verify(registry, info, args) + } + CircuitConcreteLibfunc::IntoU96Guarantee(info) => { + eval_into_u96_guarantee(registry, info, args) + } + CircuitConcreteLibfunc::U96GuaranteeVerify(info) => { + eval_u96_guarantee_verify(registry, info, args) + } + CircuitConcreteLibfunc::U96LimbsLessThanGuaranteeVerify(info) => { + eval_u96_limbs_less_than_guarantee_verify(registry, info, args) + } + CircuitConcreteLibfunc::U96SingleLimbLessThanGuaranteeVerify(info) => { + eval_u96_single_limb_less_than_guarantee_verify(registry, info, args) + } + } +} + +pub fn eval_add_input( + _registry: &ProgramRegistry, + _info: &SignatureAndTypeConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Circuit(mut values), Value::Struct(members)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + assert_ne!(values.len(), values.capacity()); + + let [Value::U128(l0), Value::U128(l1), Value::U128(l2), Value::U128(l3)]: [Value; 4] = + members.try_into().unwrap() + else { + panic!() + }; + + let l0 = l0.to_le_bytes(); + let l1 = l1.to_le_bytes(); + let l2 = l2.to_le_bytes(); + let l3 = l3.to_le_bytes(); + values.push(BigUint::from_bytes_le(&[ + l0[0], l0[1], l0[2], l0[3], l0[4], l0[5], l0[6], l0[7], l0[8], l0[9], l0[10], + l0[11], // + l1[0], l1[1], l1[2], l1[3], l1[4], l1[5], l1[6], l1[7], l1[8], l1[9], l1[10], + l1[11], // + l2[0], l2[1], l2[2], l2[3], l2[4], l2[5], l2[6], l2[7], l2[8], l2[9], l2[10], + l2[11], // + l3[0], l3[1], l3[2], l3[3], l3[4], l3[5], l3[6], l3[7], l3[8], l3[9], l3[10], + l3[11], // + ])); + + EvalAction::NormalBranch( + (values.len() != values.capacity()) as usize, + smallvec![Value::Circuit(values)], + ) +} + +pub fn eval_eval( + _registry: &ProgramRegistry, + _info: &SignatureAndTypeConcreteLibfunc, + _args: Vec, +) -> EvalAction { + let [add_mod @ Value::Unit, mul_mod @ Value::Unit, _descripctor @ Value::Unit, Value::Circuit(inputs), Value::CircuitModulus(modulus), _, _]: [Value; 7] = _args.try_into().unwrap() + else { + panic!() + }; + let circ_info = match _registry.get_type(&_info.ty).unwrap() { + CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(info)) => &info.circuit_info, + _ => todo!(), + }; + let mut outputs = vec![None; 1 + circ_info.n_inputs + circ_info.values.len()]; + let mut add_gates = circ_info.add_offsets.iter().peekable(); + let mut mul_gates = circ_info.mul_offsets.iter(); + + outputs[0] = Some(BigUint::from(1_u8)); + + for (i, input) in inputs.iter().enumerate() { + outputs[i + 1] = Some(input.to_owned()); + } + + let success = loop { + while let Some(add_gate) = add_gates.peek() { + let lhs = outputs[add_gate.lhs].to_owned(); + let rhs = outputs[add_gate.rhs].to_owned(); + + match (lhs, rhs) { + (Some(l), Some(r)) => { + outputs[add_gate.output] = Some((l + r) % &modulus); + } + (None, Some(r)) => { + let res = match outputs[add_gate.output].to_owned() { + Some(res) => res, + None => break, + }; + // if it is a sub_gate the output index is store in lhs + outputs[add_gate.lhs] = Some((res + &modulus - r) % &modulus); + } + // there aren't enough gates computed for add_gate to compute + // the next gate so we need to compute a mul_gate + _ => break, + }; + + add_gates.next(); + } + + match mul_gates.next() { + Some(mul_gate) => { + let lhs = outputs[mul_gate.lhs].to_owned(); + let rhs = outputs[mul_gate.rhs].to_owned(); + + match (lhs, rhs) { + (Some(l), Some(r)) => { + outputs[mul_gate.output] = Some((l * r) % &modulus); + } + (None, Some(r)) => { + let res = match r.modinv(&modulus) { + Some(inv) => inv, + None => { + panic!("attempt to divide by 0"); + } + }; + // if it is a inv_gate the output index is store in lhs + outputs[mul_gate.lhs] = Some(res); + } + // this state should not be reached since it would mean that + // not all the circuit's inputs where filled + _ => unreachable!(), + } + } + None => break true, + } + }; + + let values = outputs + .into_iter() + .skip(1 + circ_info.n_inputs) + .collect::>>() + .expect("The circuit cannot be calculated"); + + if success { + EvalAction::NormalBranch( + 0, + smallvec![add_mod, mul_mod, Value::CircuitOutputs(values)], + ) + } else { + EvalAction::NormalBranch(1, smallvec![add_mod, mul_mod, Value::Unit, Value::Unit]) + } +} + +pub fn eval_get_output( + _registry: &ProgramRegistry, + _info: &ConcreteGetOutputLibFunc, + args: Vec, +) -> EvalAction { + let [Value::CircuitOutputs(outputs)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + let circuit_info = match _registry.get_type(&_info.circuit_ty).unwrap() { + CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(info)) => &info.circuit_info, + _ => todo!(), + }; + let gate_offset = circuit_info.values.get(&_info.output_ty).unwrap().clone(); + let output_idx = gate_offset - 1 - circuit_info.n_inputs; + let output = outputs[output_idx].to_owned(); + let output_big = output.to_bigint().unwrap(); + + let mask: BigInt = BigInt::from_bytes_be(Sign::Plus, &[255; 12]); + + let l0: BigInt = &output_big & &mask; + let l1: BigInt = (&output_big >> 96) & &mask; + let l2: BigInt = (&output_big >> 192) & &mask; + let l3: BigInt = (output_big >> 288) & &mask; + + let range = BigInt::ZERO..(BigInt::from(1) << 96); + let vec_values = vec![ + Value::BoundedInt { + range: range.clone(), + value: l0, + }, + Value::BoundedInt { + range: range.clone(), + value: l1, + }, + Value::BoundedInt { + range: range.clone(), + value: l2, + }, + Value::BoundedInt { range, value: l3 }, + ]; + + EvalAction::NormalBranch(0, smallvec![Value::Struct(vec_values), Value::Unit]) +} + +pub fn eval_u96_limbs_less_than_guarantee_verify( + _registry: &ProgramRegistry, + _info: &ConcreteU96LimbsLessThanGuaranteeVerifyLibfunc, + _args: Vec, +) -> EvalAction { + EvalAction::NormalBranch(0, smallvec![Value::Unit]) +} + +pub fn eval_u96_single_limb_less_than_guarantee_verify( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + _args: Vec, +) -> EvalAction { + EvalAction::NormalBranch(0, smallvec![Value::Unit]) +} + +pub fn eval_u96_guarantee_verify( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + _args: Vec, +) -> EvalAction { + let [range_check_96 @ Value::Unit, _]: [Value; 2] = _args.try_into().unwrap() else { + panic!() + }; + EvalAction::NormalBranch(0, smallvec![range_check_96]) +} + +pub fn eval_failure_guarantee_verify( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + _args: Vec, +) -> EvalAction { + let [rc96 @ Value::Unit, mul_mod @ Value::Unit, _, _, _]: [Value; 5] = + _args.try_into().unwrap() + else { + panic!() + }; + + EvalAction::NormalBranch(0, smallvec![rc96, mul_mod, Value::Unit]) +} + +pub fn eval_get_descriptor( + _registry: &ProgramRegistry, + _info: &SignatureAndTypeConcreteLibfunc, + _args: Vec, +) -> EvalAction { + EvalAction::NormalBranch(0, smallvec![Value::Unit]) +} + +pub fn eval_init_circuit_data( + _registry: &ProgramRegistry, + info: &SignatureAndTypeConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check_96 @ Value::Unit]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + let num_inputs = match _registry.get_type(&info.ty).unwrap() { + CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(info)) => info.circuit_info.n_inputs, + _ => todo!("{}", info.ty), + }; + + EvalAction::NormalBranch( + 0, + smallvec![ + range_check_96, + Value::Circuit(Vec::with_capacity(num_inputs)), + ], + ) +} + +pub fn eval_try_into_circuit_modulus( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Struct(members)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + let [Value::BoundedInt { + range: r0, + value: l0, + }, Value::BoundedInt { + range: r1, + value: l1, + }, Value::BoundedInt { + range: r2, + value: l2, + }, Value::BoundedInt { + range: r3, + value: l3, + }]: [Value; 4] = members.try_into().unwrap() + else { + panic!() + }; + assert_eq!(r0, BigInt::ZERO..(BigInt::from(1) << 96)); + assert_eq!(r1, BigInt::ZERO..(BigInt::from(1) << 96)); + assert_eq!(r2, BigInt::ZERO..(BigInt::from(1) << 96)); + assert_eq!(r3, BigInt::ZERO..(BigInt::from(1) << 96)); + + let l0 = l0.to_biguint().unwrap(); + let l1 = l1.to_biguint().unwrap(); + let l2 = l2.to_biguint().unwrap(); + let l3 = l3.to_biguint().unwrap(); + + let value = l0 | (l1 << 96) | (l2 << 192) | (l3 << 288); + + // a CircuitModulus must not be neither 0 nor 1 + assert_ne!(value, 0_u8.into()); + assert_ne!(value, 1_u8.into()); + + EvalAction::NormalBranch(0, smallvec![Value::CircuitModulus(value)]) +} + +pub fn eval_into_u96_guarantee( + _registry: &ProgramRegistry, + _info: &SignatureAndTypeConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::BoundedInt { range, value }]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + assert_eq!(range, BigInt::ZERO..(BigInt::from(1) << 96)); + + EvalAction::NormalBranch(0, smallvec![Value::U128(value.try_into().unwrap())]) +} diff --git a/tests/libfuncs.rs b/tests/libfuncs.rs index 5fcf124..69df8ef 100644 --- a/tests/libfuncs.rs +++ b/tests/libfuncs.rs @@ -2,9 +2,12 @@ use std::{path::Path, sync::Arc}; use cairo_lang_compiler::{compile_cairo_project_at_path, CompilerConfig}; use cairo_lang_sierra::{ - extensions::{core::CoreTypeConcrete, starknet::StarkNetTypeConcrete}, + extensions::{ + circuit::CircuitTypeConcrete, core::CoreTypeConcrete, starknet::StarkNetTypeConcrete, + }, program::{GenFunction, Program, StatementIdx}, }; +use num_bigint::BigInt; use sierra_emu::{ProgramTrace, StateDump, Value, VirtualMachine}; fn run_program(path: &str, func_name: &str, args: &[Value]) -> Vec { @@ -40,11 +43,15 @@ fn run_program(path: &str, func_name: &str, args: &[Value]) -> Vec { CoreTypeConcrete::GasBuiltin(_) => Value::U128(initial_gas), CoreTypeConcrete::StarkNet(StarkNetTypeConcrete::System(_)) => Value::Unit, CoreTypeConcrete::RangeCheck(_) + | CoreTypeConcrete::RangeCheck96(_) | CoreTypeConcrete::Pedersen(_) | CoreTypeConcrete::Poseidon(_) | CoreTypeConcrete::Bitwise(_) | CoreTypeConcrete::BuiltinCosts(_) - | CoreTypeConcrete::SegmentArena(_) => Value::Unit, + | CoreTypeConcrete::SegmentArena(_) + | CoreTypeConcrete::Circuit( + CircuitTypeConcrete::AddMod(_) | CircuitTypeConcrete::MulMod(_), + ) => Value::Unit, _ => args.next().unwrap(), } }) @@ -131,3 +138,53 @@ pub fn find_entry_point_by_name<'a>( .iter() .find(|x| x.id.debug_name.as_ref().map(|x| x.as_str()) == Some(name)) } + +// CIRCUITS + +#[test] +fn test_run_full_circuit() { + let range96 = BigInt::ZERO..(BigInt::from(1) << 96); + let limb0 = Value::BoundedInt { + range: range96.clone(), + value: 36699840570117848377038274035_u128.into(), + }; + let limb1 = Value::BoundedInt { + range: range96.clone(), + value: 72042528776886984408017100026_u128.into(), + }; + let limb2 = Value::BoundedInt { + range: range96.clone(), + value: 54251667697617050795983757117_u128.into(), + }; + let limb3 = Value::BoundedInt { + range: range96, + value: 7.into(), + }; + + let output = run_program( + "tests/tests/circuits.cairo", + "circuits::circuits::main", + &[], + ); + let expected_output = Value::Struct(vec![Value::Struct(vec![limb0, limb1, limb2, limb3])]); + let Value::Enum { + self_ty: _, + index: _, + payload, + } = output.last().unwrap() + else { + panic!("No output"); + }; + + assert_eq!(**payload, expected_output); +} + +#[test] +#[should_panic(expected = "attempt to divide by 0")] +fn test_circuit_failure() { + run_program( + "tests/tests/circuits_failure.cairo", + "circuits_failure::circuits_failure::main", + &[], + ); +} diff --git a/tests/tests/circuits.cairo b/tests/tests/circuits.cairo new file mode 100644 index 0000000..5e9acd1 --- /dev/null +++ b/tests/tests/circuits.cairo @@ -0,0 +1,32 @@ +use core::circuit::{ + RangeCheck96, AddMod, MulMod, u96, CircuitElement, CircuitInput, circuit_add, + circuit_sub, circuit_mul, circuit_inverse, EvalCircuitTrait, u384, + CircuitOutputsTrait, CircuitModulus, AddInputResultTrait, CircuitInputs, +}; + +fn main() -> u384 { + let in1 = CircuitElement::> {}; + let in2 = CircuitElement::> {}; + let add1 = circuit_add(in1, in2); + let mul1 = circuit_mul(add1, in1); + let mul2 = circuit_mul(mul1, add1); + let inv1 = circuit_inverse(mul2); + let sub1 = circuit_sub(inv1, in2); + let sub2 = circuit_sub(sub1, mul2); + let inv2 = circuit_inverse(sub2); + let add2 = circuit_add(inv2, inv2); + + let modulus = TryInto::<_, CircuitModulus>::try_into([17, 14, 14, 14]).unwrap(); + + let outputs = (add2,) + .new_inputs() + .next([9, 2, 9, 3]) + .next([5, 7, 0, 8]) + .done() + .eval(modulus) + .unwrap(); + + outputs.get_output(add2) +} + + diff --git a/tests/tests/circuits_failure.cairo b/tests/tests/circuits_failure.cairo new file mode 100644 index 0000000..fcf2684 --- /dev/null +++ b/tests/tests/circuits_failure.cairo @@ -0,0 +1,20 @@ +use core::circuit::{ + RangeCheck96, AddMod, MulMod, u96, CircuitElement, CircuitInput, circuit_add, circuit_sub, + circuit_mul, circuit_inverse, EvalCircuitTrait, u384, CircuitOutputsTrait, CircuitModulus, + AddInputResultTrait, CircuitInputs, +}; + +fn main() { + let in1 = CircuitElement::> {}; + let inv = circuit_inverse(in1); + + let modulus = TryInto::<_, CircuitModulus>::try_into([7, 0, 0, 0]).unwrap(); + let outputs = (inv,) + .new_inputs() + .next([0, 0, 0, 0]) + .done() + .eval(modulus) + .unwrap(); + + outputs.get_output(inv); +}