Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,18 @@ jobs:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest]
adapter: [circom, halo2, noir, none]
adapter: [circom, halo2, noir, gnark, none]
runs-on: ${{ matrix.os }}
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- uses: actions/checkout@v6

- name: Setup Go (for gnark adapter)
if: matrix.adapter == 'gnark'
uses: actions/setup-go@v5
with:
go-version: "1.24"

- name: Prepare template project
uses: ./.github/actions/mopro-init-project
with:
Expand All @@ -146,12 +152,18 @@ jobs:
strategy:
fail-fast: false
matrix:
adapter: [circom, halo2, noir]
adapter: [circom, halo2, noir, gnark]
runs-on: macos-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- uses: actions/checkout@v6

- name: Setup Go (for gnark adapter)
if: matrix.adapter == 'gnark'
uses: actions/setup-go@v5
with:
go-version: "1.24"

- name: Prepare template project
uses: ./.github/actions/mopro-init-project
with:
Expand Down Expand Up @@ -196,7 +208,7 @@ jobs:
strategy:
fail-fast: false
matrix:
adapter: [circom, halo2, noir]
adapter: [circom, halo2, noir, gnark]
runs-on: macos-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
Expand Down Expand Up @@ -247,13 +259,19 @@ jobs:
strategy:
fail-fast: false
matrix:
adapter: [circom, halo2, noir]
adapter: [circom, halo2, noir, gnark]
runs-on: ubuntu-latest
timeout-minutes: 15
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- uses: actions/checkout@v6

- name: Setup Go (for gnark adapter)
if: matrix.adapter == 'gnark'
uses: actions/setup-go@v5
with:
go-version: "1.24"

- name: Android SDK
uses: android-actions/setup-android@v3

Expand Down Expand Up @@ -318,12 +336,18 @@ jobs:
strategy:
fail-fast: false
matrix:
adapter: [circom, halo2, noir]
adapter: [circom, halo2, noir, gnark]
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- uses: actions/checkout@v6

- name: Setup Go (for gnark adapter)
if: matrix.adapter == 'gnark'
uses: actions/setup-go@v5
with:
go-version: "1.24"

- uses: subosito/flutter-action@v2
with:
flutter-version: "3.35.4"
Expand Down Expand Up @@ -361,12 +385,18 @@ jobs:
strategy:
fail-fast: false
matrix:
adapter: [circom, halo2, noir]
adapter: [circom, halo2, noir, gnark]
runs-on: macos-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo_full_name
steps:
- uses: actions/checkout@v6

- name: Setup Go (for gnark adapter)
if: matrix.adapter == 'gnark'
uses: actions/setup-go@v5
with:
go-version: "1.24"

# Build mopro example project for iOS + Android first, and create RN frameworks
- name: Prepare template project
uses: ./.github/actions/mopro-init-project
Expand Down
1 change: 1 addition & 0 deletions cli/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::{env, fs, io::Write, path::Path};

pub mod adapter;
mod circom;
mod gnark;
mod halo2;
mod noir;
mod proving_system;
Expand Down
28 changes: 26 additions & 2 deletions cli/src/init/adapter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{circom::Circom, halo2::Halo2, noir::Noir};
use super::{circom::Circom, gnark::Gnark, halo2::Halo2, noir::Noir};
use crate::init::proving_system::{replace_test_bindings_lib_import, ProvingSystem};
use crate::{select::multi_select, style};

Expand All @@ -7,13 +7,15 @@ pub enum Adapter {
Circom,
Halo2,
Noir,
Gnark,
NoneOfTheAbove,
}

pub(super) const ADAPTERS: [Adapter; 4] = [
pub(super) const ADAPTERS: [Adapter; 5] = [
Adapter::Circom,
Adapter::Halo2,
Adapter::Noir,
Adapter::Gnark,
Adapter::NoneOfTheAbove,
];

Expand All @@ -23,6 +25,7 @@ impl Adapter {
Adapter::Circom => "circom",
Adapter::Halo2 => "halo2",
Adapter::Noir => "noir",
Adapter::Gnark => "gnark",
Adapter::NoneOfTheAbove => "none of the above",
}
}
Expand Down Expand Up @@ -82,6 +85,9 @@ impl AdapterSelector {
if self.contains(Adapter::Noir) {
Noir::dep_template(cargo_toml_path)?;
}
if self.contains(Adapter::Gnark) {
Gnark::dep_template(cargo_toml_path)?;
}
Ok(())
}

Expand All @@ -95,6 +101,9 @@ impl AdapterSelector {
if self.contains(Adapter::Noir) {
Noir::build_dep_template(cargo_toml_path)?;
}
if self.contains(Adapter::Gnark) {
Gnark::build_dep_template(cargo_toml_path)?;
}
Ok(())
}

Expand All @@ -108,6 +117,9 @@ impl AdapterSelector {
if self.contains(Adapter::Noir) {
Noir::dev_dep_template(cargo_toml_path)?;
}
if self.contains(Adapter::Gnark) {
Gnark::dev_dep_template(cargo_toml_path)?;
}
Ok(())
}

Expand All @@ -129,6 +141,12 @@ impl AdapterSelector {
} else {
Noir::lib_stub_template(lib_rs_path)?;
}

if self.contains(Adapter::Gnark) {
Gnark::lib_template(lib_rs_path)?;
} else {
Gnark::lib_stub_template(lib_rs_path)?;
}
Ok(())
}

Expand All @@ -142,6 +160,9 @@ impl AdapterSelector {
if self.contains(Adapter::Noir) {
Noir::build_template(build_rs_path)?;
}
if self.contains(Adapter::Gnark) {
Gnark::build_template(build_rs_path)?;
}
Ok(())
}

Expand All @@ -159,6 +180,9 @@ impl AdapterSelector {
if self.contains(Adapter::Noir) {
Noir::build_bindings_lib(bindings_lib_path, project_name)?;
}
if self.contains(Adapter::Gnark) {
Gnark::build_bindings_lib(bindings_lib_path, project_name)?;
}
// copy ffi bindings test
let ffi_bindings_dir_path = format!("{}/{}", bindings_lib_path, "ffi");
replace_test_bindings_lib_import(ffi_bindings_dir_path.as_str(), project_name)?;
Expand Down
16 changes: 16 additions & 0 deletions cli/src/init/gnark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::init::adapter::Adapter;
use crate::init::proving_system::ProvingSystem;
use include_dir::include_dir;
use include_dir::Dir;

pub struct Gnark;

impl ProvingSystem for Gnark {
const TEMPLATE_DIR: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/src/template/gnark");

const ADAPTER: Adapter = Adapter::Gnark;

const DEPENDENCIES: &'static str = r#"
rust-gnark = "0.0.2"
"#;
}
3 changes: 3 additions & 0 deletions cli/src/init/write_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,21 @@ anyhow = "1.0.99"
# CIRCOM_DEPENDENCIES
# HALO2_DEPENDENCIES
# NOIR_DEPENDENCIES
# GNARK_DEPENDENCIES

[build-dependencies]
# CIRCOM_BUILD_DEPENDENCIES
# HALO2_BUILD_DEPENDENCIES
# NOIR_BUILD_DEPENDENCIES
# GNARK_BUILD_DEPENDENCIES

[dev-dependencies]
mopro-ffi = { version = "0.3.4", features = ["uniffi-tests"] }

# CIRCOM_DEV_DEPENDENCIES
# HALO2_DEV_DEPENDENCIES
# NOIR_DEV_DEPENDENCIES
# GNARK_DEV_DEPENDENCIES

[target.wasm32-unknown-unknown.dependencies]
mopro-ffi = { version = "0.3.4", features = ["wasm"] }
Expand Down
42 changes: 42 additions & 0 deletions cli/src/template/gnark/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#[cfg(not(target_arch = "wasm32"))]
mod gnark;
#[cfg(not(target_arch = "wasm32"))]
pub use gnark::{generate_gnark_proof, verify_gnark_proof, GnarkProofResult};

#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod gnark_tests {
use crate::gnark::{generate_gnark_proof, verify_gnark_proof};

const R1CS_PATH: &str = "./test-vectors/gnark/cubic_circuit.r1cs";
const PK_PATH: &str = "./test-vectors/gnark/cubic_circuit.pk";
const VK_PATH: &str = "./test-vectors/gnark/cubic_circuit.vk";

#[test]
fn test_gnark_cubic_circuit() {
// x=3: x^3 + x + 5 = 27 + 3 + 5 = 35
let witness_json = r#"{"X": "3", "Y": "35"}"#.to_string();

let result = generate_gnark_proof(
R1CS_PATH.to_string(),
PK_PATH.to_string(),
witness_json,
);
assert!(result.is_ok(), "Proof generation should succeed");

let proof_result = result.unwrap();
assert!(!proof_result.proof.is_empty(), "Proof should not be empty");
assert!(
!proof_result.public_inputs.is_empty(),
"Public inputs should not be empty"
);

let valid = verify_gnark_proof(
R1CS_PATH.to_string(),
VK_PATH.to_string(),
proof_result,
);
assert!(valid.is_ok(), "Verification should not error");
assert!(valid.unwrap(), "Proof should be valid");
}
}
1 change: 1 addition & 0 deletions cli/src/template/init/build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
fn main() {
// CIRCOM_TEMPLATE
// GNARK_TEMPLATE
}
2 changes: 2 additions & 0 deletions cli/src/template/init/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ pub enum MoproError {
Halo2Error(String),
#[error("NoirError: {0}")]
NoirError(String),
#[error("GnarkError: {0}")]
GnarkError(String),
}
81 changes: 81 additions & 0 deletions cli/src/template/init/src/gnark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use crate::MoproError;
use std::sync::Once;

/// Guards one-time initialization of the gnark Go runtime.
static GNARK_INIT: Once = Once::new();

/// Result of a gnark Groth16 BN254 proof generation.
///
/// Both fields are hex-encoded gnark binary serializations:
/// - `proof`: compressed Groth16 proof
/// - `public_inputs`: public witness
#[derive(Debug, Clone)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct GnarkProofResult {
pub proof: String,
pub public_inputs: String,
}

/// Generate a Groth16 BN254 proof using gnark.
///
/// # Arguments
///
/// * `r1cs_path` - Path to the `.r1cs` file (CBOR binary)
/// * `pk_path` - Path to the `.pk` file (gnark binary)
/// * `witness_json` - JSON object mapping circuit field names to decimal string values
///
/// # Errors
///
/// Returns [`MoproError::GnarkError`] if proof generation fails.
#[cfg_attr(feature = "uniffi", uniffi::export)]
pub fn generate_gnark_proof(
r1cs_path: String,
pk_path: String,
witness_json: String,
) -> Result<GnarkProofResult, MoproError> {
GNARK_INIT.call_once(|| {
rust_gnark::init().expect("Failed to initialize gnark runtime");
});

let result = rust_gnark::groth16_prove(&r1cs_path, &pk_path, &witness_json)
.map_err(|e| MoproError::GnarkError(e.to_string()))?;

Ok(GnarkProofResult {
proof: result.proof,
public_inputs: result.public_inputs,
})
}

/// Verify a Groth16 BN254 proof using gnark.
///
/// # Arguments
///
/// * `r1cs_path` - Path to the `.r1cs` file
/// * `vk_path` - Path to the `.vk` file (gnark binary)
/// * `proof_result` - The proof result from [`generate_gnark_proof`]
///
/// # Returns
///
/// `true` if the proof is valid, `false` otherwise.
///
/// # Errors
///
/// Returns [`MoproError::GnarkError`] if verification encounters an error.
#[cfg_attr(feature = "uniffi", uniffi::export)]
pub fn verify_gnark_proof(
r1cs_path: String,
vk_path: String,
proof_result: GnarkProofResult,
) -> Result<bool, MoproError> {
GNARK_INIT.call_once(|| {
rust_gnark::init().expect("Failed to initialize gnark runtime");
});

let inner = rust_gnark::Groth16ProofResult {
proof: proof_result.proof,
public_inputs: proof_result.public_inputs,
};

rust_gnark::groth16_verify(&r1cs_path, &vk_path, &inner)
.map_err(|e| MoproError::GnarkError(e.to_string()))
}
2 changes: 2 additions & 0 deletions cli/src/template/init/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ mod uniffi_tests {
// HALO2_TEMPLATE

// NOIR_TEMPLATE

// GNARK_TEMPLATE
Loading