Skip to main content
Version: 0.2.0

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 the build.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

warning

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 the build.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);
warning

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

danger

It doesn't support for some circuits: see Unimplemented features

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,
}