--- tags: mpc, circom --- # Circom to Arithmetic Circuit Compiler Overview This document provides an overview and status report of the Circom to Arithmetic Circuit Compiler: - https://github.com/namnc/circom-2-arithc We will also discuss the challenges currently faced by the project and outline potential next steps for its development. ## Project Overview The primary aim of this project is to facilitate the creation of arithmetic circuits through the Circom DSL. The motivation behind developing such a tool comes from the desire to construct multi-party computation applications using a programming language, rather than relying on libraries. Traditionally, Circom is utilized for creating R1CS-based zk circuits. However, our project diverges from this path by targeting the generation of generic arithmetic circuits. These circuits are intended to serve as an intermediate representation, enabling the use of Circom for developing MPC or Fully Homomorphic Encryption applications. Since our objective differs from the original Circom's purpose, we won't be able to use some of the present tooling created for it, such as it's testing javascript libraries or the available Circom libraries (such as circomlib). But Circom's restrictive nature significantly accelerates the compiler implementation process. Leveraging Rust's tooling for generating the program's abstract syntax tree and types is also very useful. This approach is important for quickly benchmarking the entire pipeline. ## Current Status As of the latest update (`2b07504`), the project successfully generates arithmetic circuits from Circom programs. It supports a basic yet sufficient range of statements and expressions to create simple programs, as outlined below: | Category | Type | | --------------- | :-------------------: | | **Statements** | `InitializationBlock` | | | `Block` | | | `Substitution` | | | `Declaration` | | | `IfThenElse` | | | `While` | | | `Return` | | **Expressions** | `Call` | | | `InfixOp` | | | `Number` | | | `Variable` | | | `PrefixOp` | ## Circuit Example The Circom program below performs multiplication of two matrices: ``` pragma circom 2.0.0; // Matrix multiplication by element template matElemMul (m,n) { signal input a[m][n]; signal input b[m][n]; signal output out[m][n]; for (var i=0; i < m; i++) { for (var j=0; j < n; j++) { out[i][j] <== a[i][j] * b[i][j]; } } } component main = matElemMul(2,2); ``` This outputs the following circuit file: ``` { "vars": { "14": null, "15": null, ... "10": null, "0": null }, "nodes": [ { "id": 1, "signals": [0], "names": ["0.a[0][0]"], "is_const": false, "const_value": 0 }, { "id": 2, "signals": [1], "names": ["0.a[0][1]"], "is_const": false, "const_value": 0 }, ... { "id": 18, "signals": [14, 10], "names": ["0.random_3464591624", "0.out[1][0]"], "is_const": false, "const_value": 0 }, { "id": 20, "signals": [15, 11], "names": ["0.random_4150857917", "0.out[1][1]"], "is_const": false, "const_value": 0 } ], "gates": [ { "id": 0, "gate_type": "AMul", "lh_input": 1, "rh_input": 5, "output": 14 }, { "id": 1, "gate_type": "AMul", "lh_input": 2, "rh_input": 6, "output": 16 }, { "id": 2, "gate_type": "AMul", "lh_input": 3, "rh_input": 7, "output": 18 }, { "id": 3, "gate_type": "AMul", "lh_input": 4, "rh_input": 8, "output": 20 } ], "node_count": 20 } ``` Where: - `var`: Represents a signal variable, which may hold a value if it is a constant signal. - `node`: Represents a circuit node, it can be a single signal or a connection of multiple signals. The names of these signals correspond to their identifiers in the Circom program. - `gate`: Represents a circuit gate, characterized by a type (e.g., AMul for multiplication gate), inputs from two nodes, and an output node. In this specific circuit, four multiplication gates are utilized to execute the element-wise multiplication of a 2x2 matrix. ## Structure Overview The project is structured as a standard Rust crate. Inside the `src` directory we can find the following: ``` ├── src │ ├── assets │ │ ├── *.circom │ ├── circom │ │ ├── *.rs │ ├── circuit.rs │ ├── lib.rs │ ├── main.rs │ ├── process.rs │ ├── program.rs └───└── runtime.rs ``` - `assets`: holds the circuits file to be compiled. - `circom`: contains modified code from the main [circom](https://github.com/iden3/circom/tree/master/circom) compiler crate. We use this to parse the circom files and generate the abstract syntax tree. - `lib.rs`: exports the crate modules. - `main.rs`: loads the input file, runs the compile function and writes the outputs. - `process.rs`: traverses each statement and handles them along with expressions. - `program.rs`: has the main `build_circuit` function that loads each statement and process them with the `process.rs` functions. - `runtime.rs`: handles the application runtime memory, a runtime and a stack of contexts to track the program scopes and variables. ## Challenges And Next Steps ### MPC CircomLib We're in the need of a circom library made for pure execution rather than constraint-based operations. Developing a library that includes basic circuit-building tools, such as comparators, is a crucial next step. But in order to have a stable library, we need some way to test our circuits, this is why the **circuits evaluation** is necessary to achieve this goal. Once that is solved, we could leverage on the existing circomlib functions and tests to cover basic language tooling. ### Circuits Evaluation We can't rely on the existing circom tooling to evaluate (simulate) a circuit or a program for the reasons detailed above. An option is to run the evaluation as a separate process from the compilation, so taking the program as an input, but this could present some stack integrity issues, since we won't be running the actual circuit output but a "possible" different interpretation of it. Another possible path is to leverage the current circuit evaluator present in the [MPZ](https://github.com/privacy-scaling-explorations/mpz) library. In order to do so, we should first make the `mpz` circuit model generic and able to handle arithmetic circuits, something that @tkmc and @andrew are currently working on. Once this is finished we can then import the evaluator into our crate and run it from there. > It's important to note that while this could be seen as some kind of integration testing for our project, we should still implement **unit testing** for the different project modules to reduce the possibilities of compilation errors. ### Stack Integration So far this project has been used as an isolated tool, but at this stage we should start to work for our possible "backends", such as [MP-SPDZ](https://github.com/data61/MP-SPDZ) or [MPZ](https://github.com/privacy-scaling-explorations/mpz). This includes generating our circuits in a standardize format such as the [Bristol](https://nigelsmart.github.io/MPC-Circuits/) format or another defined circuit structure. ### Project Usability There could be some improvements regarding the developer experience of the compiler. We should give a more detailed set of instructions on how to build the project and run the compilation of a sample project file, perhaps exposing the compiler as a rust crate that people can use, or think any other option in terms of how to make this available. ### Feature Completeness Expanding the compiler to support the full range of Circom features is necessary to maximize its utility. Continuing to develop and refine the compiler will ensure it meets the needs of a broader range of applications. ### Compilation Errors The current error design present a limited utility, we should add more verbose error description to target each specific compilation error case. This will be useful to have better feedback and speed up debugging.