Circom Circuit Integration Guide for MoPro
This guide explains how to integrate Circom circuits into the MoPro (Mobile Proving) framework for mobile zero-knowledge proof benchmarking.
Prerequisites
- Completed circuit development using the Adding Hash Functions guide
- MoPro CLI installed (
cargo install --path mopro/cli) - Android Studio (for Android development) or a USB cable to test with actual mobile
- Working Circom circuit with
.zkeyand.wasmfiles
Integration Overview
MoPro enables mobile zero-knowledge proof generation through:
- Rust Core: Circuit integration and FFI exports
- UniFFI Bindings: Type-safe mobile bindings generation
- Mobile Apps: Flutter/Android/iOS applications with benchmarking UI
- Asset Management: Circuit files (.zkey, .wasm) bundled with apps
Step-by-Step Integration
The integration process for adding new Circom circuits to MoPro is much simpler than creating new projects. You work within the existing mopro-example-app structure.
Step 1: Setup Rust Witness and Circuits
1a. Setup Rust Witness
Add your circuit's witness generation to the existing lib.rs:
// Add your circuit witness (example: Blake2s256) rust_witness::witness!(multiplier2); rust_witness::witness!(keccak); rust_witness::witness!(sha256); rust_witness::witness!(blake2s256); // <- Add your new circuit
1b. Setup Rust Circuits
Register your circuit in the set_circom_circuits! macro:
set_circom_circuits! {
("multiplier2_final.zkey", circom_prover::witness::WitnessFn::RustWitness(multiplier2_witness)),
("keccak.zkey", circom_prover::witness::WitnessFn::RustWitness(keccak_witness)),
("sha256.zkey", circom_prover::witness::WitnessFn::RustWitness(sha256_witness)),
("blake2s256.zkey", circom_prover::witness::WitnessFn::RustWitness(blake2s256_witness)), // <- Add your circuit
}Step 2: Build and Update MoPro Bindings
# Navigate to the mopro-example-app directory
cd mopro-example-app
# Build the Rust library and update bindings
mopro build
mopro updateThis regenerates the Flutter/Android bindings with your new circuit support.
Step 3: Add .zkey File to Flutter Assets
Copy your circuit's proving key to the Flutter assets directory:
# Copy your .zkey file from the Circom build
cp ../frameworks/circom/circuits/blake2s256/blake2s256_0000.zkey flutter/assets/blake2s256.zkeyStep 4: Update pubspec.yaml
Add your new asset to the Flutter configuration:
# flutter/pubspec.yaml
flutter:
assets:
# Existing assets
- assets/multiplier2_final.zkey
- assets/circom.zkey
- assets/mimc256.zkey
- assets/pedersen.zkey
- assets/poseidon.zkey
# Add your new circuit asset
- assets/blake2s256.zkeyStep 5: Use MoPro Plugin in Flutter
Use the existing MoPro plugin instance to generate and verify proofs with your circuit:
// In your Flutter app (flutter/lib/main.dart)
Future<void> _proveBlake2s() async {
try {
setState(() => _blake2sProofResult = 'Generating proof...');
final input = {
'in': [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 32, 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 116, 101, 115, 116, 32, 109, 115, 103, 46]
};
// Use the correct .zkey path and input format
final result = await generateCircomProof(
'blake2s256.zkey', // <- Your asset path
input,
ProofLib.arkworks,
);
setState(() => _blake2sProofResult = result.toString());
} catch (e) {
setState(() => _blake2sProofResult = 'Error: $e');
}
}Flutter UI Integration
Complete Flutter Integration Example
Based on our successful multi-circuit integration, here's how to add your new circuit to the existing Flutter UI:
// lib/main.dart - Add to existing _MyAppState class
class _MyAppState extends State<MyApp> {
// Add state variables for your new circuit
String _blake2sProofResult = '';
bool _blake2sValid = false;
Widget _buildCircuitSection({
required String title,
required Color color,
required String proofResult,
required bool isValid,
required VoidCallback onProve,
required VoidCallback onVerify,
}) {
return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
border: Border.all(color: color, width: 2),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
title,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: color,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: onProve,
style: ElevatedButton.styleFrom(backgroundColor: color),
child: Text('Prove $title'),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: onVerify,
style: ElevatedButton.styleFrom(backgroundColor: color),
child: Text('Verify $title'),
),
),
],
),
],
),
);
}
}Key Points for Flutter Integration
No separate plugin dependencies needed! The existing MoPro plugin in mopro-example-app handles all circuits through a single interface:
// Use the existing MoPro functions directly import 'package:mopro_example_app/mopro_example_app.dart'; // These functions work with any circuit registered in lib.rs: await generateCircomProof(zkeyPath, input, ProofLib.arkworks); await verifyCircomProof(zkeyPath, proof, ProofLib.arkworks);
Asset paths are relative to the Flutter assets directory:
'blake2s256.zkey'(not full paths)'keccak.zkey''multiplier2_final.zkey'
Advanced Integration Patterns
Multi-Circuit Architecture
// All circuit witnesses
rust_witness::witness!(multiplier2);
rust_witness::witness!(keccak);
rust_witness::witness!(sha256);
rust_witness::witness!(blake2s256);
rust_witness::witness!(mimc256);
rust_witness::witness!(pedersen);
rust_witness::witness!(poseidon);
// Register all circuits with their .zkey files
set_circom_circuits! {
("multiplier2_final.zkey", circom_prover::witness::WitnessFn::RustWitness(multiplier2_witness)),
("keccak.zkey", circom_prover::witness::WitnessFn::RustWitness(keccak_witness)),
("sha256.zkey", circom_prover::witness::WitnessFn::RustWitness(sha256_witness)),
("blake2s256.zkey", circom_prover::witness::WitnessFn::RustWitness(blake2s256_witness)),
("mimc256.zkey", circom_prover::witness::WitnessFn::RustWitness(mimc256_witness)),
("pedersen.zkey", circom_prover::witness::WitnessFn::RustWitness(pedersen_witness)),
("poseidon.zkey", circom_prover::witness::WitnessFn::RustWitness(poseidon_witness)),
}Key Points:
- Single MoPro instance handles all circuits
- Circuit selection happens via the
.zkeyfilename parameter - No separate projects needed for each circuit
Input Format Handling
Different circuits require different input formats:
// Byte-based circuits (Keccak, SHA256, Blake2s)
final byteInput = {
'in': [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 32, 84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 116, 101, 115, 116, 32, 109, 115, 103, 46]
};
// Field-based circuits (MiMC)
final fieldInput = {
'in': '123456789'
};
// Array field-based circuits (Pedersen, Poseidon)
final arrayFieldInput = {
'inputs': ['123456789', '987654321']
};Troubleshooting Common Issues
Issue 1: UniFFI Binding Generation Fails
Cause: Version mismatch or missing dependencies.
Fix:
# Ensure UniFFI version is pinned
cargo update uniffi --precise 0.29.0
# Clean and rebuild
cargo clean
mopro buildIssue 2: Asset Loading Fails on Mobile
Cause: Incorrect asset paths or missing files.
Fix:
# Verify assets are in correct location
ls test-vectors/
# Should show: circuit.zkey, circuit.wasm
# Check Flutter asset configuration
grep -A 10 "assets:" pubspec.yamlIssue 3: Proof Generation Fails
Cause: Input format mismatch or circuit constraints.
Fix: Add detailed error logging to debug the issue. Verify input format matches circuit expectations.
Issue 4: Memory Issues on Mobile
Cause: Large circuit files or insufficient memory.
Fix:
- Use smaller pot files when possible
- Implement proof generation in background threads
- Add memory monitoring and cleanup
Next Steps
After successful integration:
- Performance Analysis: Run comprehensive benchmarks across devices
- Optimization: Profile and optimize bottlenecks
- Multi-Platform: Extend to iOS if needed
- Documentation: Update project README with specific instructions
- Testing: Add comprehensive test coverage
- Deployment: Prepare for production deployment
Key Learnings from Our Integrations
Based on our successful integration of Keccak256, SHA256, Blake2s256, MiMC256, Pedersen, and Poseidon circuits:
Architecture Patterns
- Separate State Management: Each circuit needs independent state variables
- Color-Coded UI: Visual distinction helps with multi-circuit interfaces
- Consistent Input Formats: Standardize on byte arrays vs field elements
- Asset Naming: Use descriptive names for .zkey files (e.g.,
mimc256.zkey)
Technical Insights
- Batch Size: Refers to benchmark iterations, not input processing
- Memory Management: Large .zkey files require careful mobile optimization
- Error Handling: Robust error handling prevents app crashes
- Performance: Proof generation times vary significantly between circuits
Integration Success Factors
- Incremental Development: Start with one circuit, then add others
- Comprehensive Testing: Test both proof generation and verification
- Visual Feedback: Clear UI indicators for success/failure states
- Documentation: Maintain detailed integration notes for future reference
Related Resources
- MoPro Documentation: https://zkmopro.org
- UniFFI Guide: https://mozilla.github.io/uniffi-rs
- Flutter Plugin Development: https://docs.flutter.dev/packages-and-plugins