Circom Adapter
Mopro supports the integration of Circom circuits. For this, you need to have pre-built zkey
and wasm
files for your circuits. You can find more information on how to generate these files in the Circom documentation.
Samples
Explore how the Circom adapter is implemented by checking out the test-e2e where we maintain (and test) each adapter.
Setup the rust project
You can follow the instructions in the Rust Setup guide to create a new Rust project that builds this library with Circom proofs.
In your Cargo.toml
file, ensure the circom
feature is activated for mopro-ffi
:
[features]
default = ["mopro-ffi/circom"]
[dependencies]
mopro-ffi = { version = "0.2" }
# ...
Witness Generation Functions
In order for the Mopro to be able to generate proofs for your chosen circom circuits, you need to provide a witness generation function for each of the circuits you plan to use to generate proofs for. This function handles the witness generation for your circuit. You can read more about witnesses for circom circuits here.
Mopro provides three different witness generators. By default, the Mopro CLI uses rust-witness
, as it offers better stability and faster build times.
If you're interested in exploring the differences between witness generators, check out this blog post.
rust-witness
The function signature should be:
type RustWitnessWtnsFn = fn(HashMap<String, Vec<BigInt>>) -> Vec<BigInt>;
- Implementing the Witness Function
For simplicity, you can use the witness!
macro provided by the rust-witness
crate. This macro generates a witness function for you given the circuit name. You can read more about the witness!
macro here.
- Adding the
rust-witness
Crate Dependency
To use it, you must first add the rust-witness
crate to your Cargo.toml
regular and build dependencies:
[dependencies]
# ...
rust-witness = "0.1"
[build-dependencies]
# ...
rust-witness = "0.1"
- Configuring the path to the
.wasm
circuit files in thebuild.rs
Then you need to add to the build.rs
the call to rust_witness::transpile::transpile_wasm
macro and pass it the path to the folder containing the .wasm
files for the circom circuits. The path can be absolute or a relative to the location of the build.rs
file. Note that the .wasm
files can be recursively in subfolders of the specified folder, as in the example below.
For example, for the following project structure:
your-rust-project
├── build.rs
...
test-vectors
├── circom
│ ├── multiplier
│ │ ├── multiplier2.wasm
│ │ └── multiplier3.wasm
│ └── keccak256_256_test.wasm
...
You will need to add the following to the build.rs
file:
fn main() {
// ...
rust_witness::transpile::transpile_wasm("../test-vectors/circom".to_string());
// ...
}
- Automatically Generating Witness Functions
Then you can automatically generate the witness functions for all the circuits in the specified folder.
To do so, in the lib.rs
file, you can add the following:
rust_witness::witness!(multiplier2);
rust_witness::witness!(multiplier3);
rust_witness::witness!(keccak256256test);
This will generate the witness function for the specified circuit following the naming convention here.
witnesscalc_adapter
Ensure the following requirements before using witnesscalc_adapter
.
The witnesscalc-adapter is based on the original witnesscalc implementation in C++. To use it, compile your Circom circuit with the --c
flag to generate C++ code. Then, retrieve the .dat
file from the output C++ folder for use in the adapter. For example, compile the circuit using:
circom multiplier2.circom --c
- The function signature should be:
type WitnesscalcWtnsFn = fn(&str) -> anyhow::Result<Vec<u8>>;
- Adding the
witnesscalc-adapter
Crate Dependency
To use it, you must first add the witnesscalc-adapter
crate to your Cargo.toml
regular and build dependencies:
[dependencies]
# ...
mopro-ffi = { version="0.2", features = ["witnesscalc"] } # activate witnesscalc feature
witnesscalc-adapter = "0.1"
[build-dependencies]
# ...
witnesscalc-adapter = "0.1"
- Configuring the path to the
.dat
circuit files in thebuild.rs
Then you need to add to the build.rs
the call to witnesscalc_adapter::build_and_link
macro and pass it the path to the folder containing the .dat
files for the circom circuits. The path can be absolute or a relative to the location of the build.rs
file. Note that the .dat
files can be recursively in subfolders of the specified folder.
You will need to add the following to the build.rs
file:
fn main() {
// ...
witnesscalc_adapter::build_and_link("../test-vectors/circom");
// ...
}
- Automatically Generating Witness Functions
Then you can automatically generate the witness functions for all the circuits in the specified folder.
To do so, in the lib.rs
file, you can add the following:
witnesscalc_adapter::witness!(multiplier2);
witnesscalc_adapter::witness!(multiplier3);
witnesscalc_adapter::witness!(keccak_256_256_test);
Currently, circuit names can include underscores (_
) but not dashes (-
).
Make sure your circuit name does not contain dashes, and avoid using main
as the circuit name to prevent conflicts.
circom-witnesscalc
It doesn't support for some circuits: see Unimplemented features
-
Compile a circuit graph before using the
circom-witnesscalc
. Please checkout: Compile a circuit and build the witness graph. -
The function signature should be:
type CircomWitnessCalcWtnsFn = fn(&str) -> anyhow::Result<Vec<u8>>;
- Activate
circom-witnesscalc
Feature
To use it, you only need to activate the circom-witnesscalc
feature in mopro-ffi
in your Cargo.toml
.
[dependencies]
# ...
mopro-ffi = { version= "0.2", features = ["circom-witnesscalc"] } # activate circom-witnesscalc feature
- Use the Mopro helper to enable the graph functionality in your project.
const GRAPH_PATH: &str = "./test-vectors/circom/multiplier2.bin";
mopro_ffi::graph!(multiplier2, &GRAPH_PATH)
// The witness name (multiplier2) does not need to match the file name, and there are no restrictions on its format.
// Then you will have a witness function called multiplier2_witness
Setting the Circom Circuits
To set Circom circuits you want to use on other platforms, you need to use the set_circom_circuits!
macro provided by the mopro-ffi
crate. This macro should be called in the lib.rs
file of your project, after the mopro_ffi::app()
macro call. You should pass it a list of tuples (pairs), where the first element is the name of the zkey
file and the second element is the witness generation function.
For example:
mopro_ffi::set_circom_circuits! {
// using rust-witness
("multiplier2_final.zkey", mopro_ffi::witness::WitnessFn::RustWitness(multiplier2_witness)),
// using witnesscalc
("multiplier3_final.zkey", mopro_ffi::witness::WitnessFn::Witnesscalc(multiplier3_witness)),
// using circom-witnesscalc
("keccak256_256_test_final.zkey", mopro_ffi::witness::WitnessFn::CircomWitnesscalc(keccak256256test_witness)),
}
Under the hood, the set_circom_circuits!
macro will generate a get_circom_wtns_fn
function that will be used to get the witness generation function for a given circuit zkey
file.
Manual Configuration
For advanced users, you can manually define the get_circom_wtns_fn
function in the lib.rs
file:
fn get_circom_wtns_fn(circuit: &str) -> Result<mopro_ffi::WtnsFn, mopro_ffi::MoproError> {
match circuit {
"your_circuit.zkey" => Ok(your_circuit_wtns_gen_fn),
_ => Err(mopro_ffi::MoproError::CircomError(format!("Unknown ZKEY: {}", circuit).to_string()))
}
}
This might be useful if you want to have more control over the proving functions for each circuit.
Proof Generation Functions
Mopro now supports 2 Circom provers. You can find more information in the blog post.
ark-works
- By default, Arkworks is enabled as it offers greater stability.
rust-rapidsnark
rust-rapidsnark
is based on the original C++ implementation of rapidsnark, with the binary wrapped and integrated in Rust.- Activate
rapidsnark
Feature for both[dependencies]
and[build-dependencies]
[dependencies]
# ...
mopro-ffi = { version= "0.2", features = ["rapidsnark"] } # activate rapidsnark feature
[build-dependencies]
# ...
mopro-ffi = { version= "0.2", features = ["rapidsnark"] } # activate rapidsnark featurer
Generate Proofs
By integrating the witness generator with the prover, you can run generate_circom_proof
using
let zkey_path = "./test-vectors/circom/multiplier2_final.zkey".to_string();
let circuit_inputs = "{\"a\": 2, \"b\": 3}".to_string();
let result = generate_circom_proof(
zkey_path.clone(),
circuit_inputs,
ProofLib::Arkworks
// To use rapidsnark
// ProofLib::Rapidsnark
);
Using the Library
After you have specified the circuits you want to use, you can follow the usual steps to build the library and use it in your project.
iOS API
The Circom adapter exposes the following functions to be used in the iOS project:
generateCircomProof
// Generate a proof for a given circuit zkey, as well as the circuit inputs
// Make sure that the name of the zkey file matches the one you set in the `set_circom_circuits!` macro
public func generateCircomProof(zkeyPath: String, circuitInputs: String, proofLib: ProofLib)throws -> CircomProofResult
verifyCircomProof
// Verify a proof for a given circuit zkey
// This works for arbitrary circuits, as long as the zkey file is valid
public func verifyCircomProof(zkeyPath: String, proofResult: CircomProofResult, proofLib: ProofLib)throws -> Bool
G1
public struct G1 {
public var x: String
public var y: String
public var z: String
}
G2
public struct G2 {
public var x: [String]
public var y: [String]
public var z: [String]
}
CircomProof
public struct CircomProof {
public var a: G1
public var b: G2
public var c: G1
public var `protocol`: String
public var curve: String
}
CircomProofResult
public struct CircomProofResult {
public var proof: CircomProof
public var inputs: [String]
}
ProofLib
public enum ProofLib {
case arkworks
case rapidsnark
}
Android API
The Circom adapter exposes the equivalent functions and types to be used in the Android project.
generateCircomProof
// Generate a proof for a given circuit zkey, as well as the circuit inputs
// Make sure that the name of the zkey file matches the one you set in the `set_circom_circuits!` macro
fun `generateCircomProof`(
`zkeyPath`: kotlin.String,
`circuitInputs`: kotlin.String,
`proofLib`: ProofLib,
): CircomProofResult
verifyCircomProof
fun `verifyCircomProof`(
`zkeyPath`: kotlin.String,
`proofResult`: CircomProofResult,
`proofLib`: ProofLib,
): kotlin.Boolean
G1
data class G1(
var `x`: kotlin.String,
var `y`: kotlin.String,
var `z`: kotlin.String,
)
G2
data class G2(
var `x`: List<kotlin.String>,
var `y`: List<kotlin.String>,
var `z`: List<kotlin.String>,
)
CircomProof
data class CircomProof(
var `a`: G1,
var `b`: G2,
var `c`: G1,
var `protocol`: kotlin.String,
var `curve`: kotlin.String,
)
CircomProofResult
data class CircomProofResult(
var `proof`: CircomProof,
var `inputs`: List<kotlin.String>,
)
ProofLib
enum class ProofLib {
ARKWORKS,
RAPIDSNARK,
}