Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.0.1-beta.5 (2025-10-13)

Added SPM support for iOS.
Made plugin run proof generation on a concurrent background queue.

## 0.0.1-beta.4 (2025-06-09)

Proof generation now works in background task queue.
Expand Down
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ group 'com.rapidsnark.flutter_rapidsnark'
version '1.0-SNAPSHOT'

buildscript {
ext.kotlin_version = '1.9.0'
ext.kotlin_version = '2.1.0'
repositories {
google()
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath 'com.android.tools.build:gradle:8.9.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Expand Down
2 changes: 1 addition & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.StandardMethodCodec
import io.iden3.rapidsnark.*
import java.util.concurrent.Executors
import android.os.Handler
import android.os.Looper

/** FlutterRapidsnarkPlugin */
class FlutterRapidsnarkPlugin : FlutterPlugin, MethodCallHandler {
Expand All @@ -16,12 +19,20 @@ class FlutterRapidsnarkPlugin : FlutterPlugin, MethodCallHandler {
/// when the Flutter Engine is detached from the Activity
private lateinit var channel: MethodChannel

// Executor to allow multiple proving / verifying operations to run concurrently
private val executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors().coerceAtLeast(2)
)
private val mainHandler = Handler(Looper.getMainLooper())

override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val taskQueue = flutterPluginBinding.binaryMessenger.makeBackgroundTaskQueue()
channel = MethodChannel(flutterPluginBinding.binaryMessenger,
channel = MethodChannel(
flutterPluginBinding.binaryMessenger,
"com.rapidsnark.flutter_rapidsnark",
StandardMethodCodec.INSTANCE,
taskQueue)
taskQueue
)
channel.setMethodCallHandler(this)
}

Expand All @@ -36,78 +47,136 @@ class FlutterRapidsnarkPlugin : FlutterPlugin, MethodCallHandler {

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
executor.shutdown()
}

// Helper to finish results safely on main thread
private fun complete(result: Result, block: () -> Unit) {
if (Looper.myLooper() == Looper.getMainLooper()) {
block()
} else {
mainHandler.post { block() }
}
}

private fun callGroth16Prove(call: MethodCall, result: Result) {
try {
val arguments: Map<String, Any> = call.arguments<Map<String, Any>>()!!

val zkeyPath = arguments["zkeyPath"] as String
val witnessBytes = arguments["witness"] as ByteArray

val proofBufferSize = arguments["proofBufferSize"] as Int
val publicBufferSize = arguments["publicBufferSize"] as Int?
val errorBufferSize = arguments["errorBufferSize"] as Int

// Call the heavy computation function
val proof = groth16Prove(
zkeyPath,
witnessBytes,
proofBufferSize,
publicBufferSize,
errorBufferSize,
)

result.success(
mapOf(
"proof" to proof.proof,
"publicSignals" to proof.publicSignals
)
)
val arguments = try {
@Suppress("UNCHECKED_CAST")
call.arguments<Map<String, Any?>>() ?: run {
result.error("groth16Prove", "Arguments must be a map", null)
return
}
} catch (e: Exception) {
result.error("groth16ProveWithZKeyFilePath", e.message, null)
result.error("groth16Prove", "Invalid arguments type: ${e.message}", null)
return
}

val zkeyPath = arguments["zkeyPath"] as? String
val witnessBytes = arguments["witness"] as? ByteArray
val proofBufferSize = (arguments["proofBufferSize"] as? Int)
val publicBufferSize = (arguments["publicBufferSize"] as? Int?)
val errorBufferSize = (arguments["errorBufferSize"] as? Int)

if (zkeyPath == null) { result.error("groth16Prove", "Missing zkeyPath", null); return }
if (witnessBytes == null) { result.error("groth16Prove", "Missing witness", null); return }
if (proofBufferSize == null) { result.error("groth16Prove", "Missing proofBufferSize", null); return }
if (errorBufferSize == null) { result.error("groth16Prove", "Missing errorBufferSize", null); return }

// Dispatch heavy native call asynchronously to allow concurrency
executor.execute {
try {
val proof = groth16Prove(
zkeyPath,
witnessBytes,
proofBufferSize,
publicBufferSize,
errorBufferSize,
)
complete(result) {
result.success(
mapOf(
"proof" to proof.proof,
"publicSignals" to proof.publicSignals
)
)
}
} catch (e: RapidsnarkProverError) {
complete(result) { result.error("groth16Prove", e.message, null) }
} catch (e: Exception) {
complete(result) { result.error("groth16Prove", e.message, null) }
}
}
}

private fun callGroth16PublicBufferSize(call: MethodCall, result: Result) {
try {
val arguments: Map<String, Any> = call.arguments<Map<String, Any>>()!!

val zkeyPath = arguments["zkeyPath"] as String
val arguments = try {
@Suppress("UNCHECKED_CAST")
call.arguments<Map<String, Any?>>() ?: run {
result.error("groth16PublicBufferSize", "Arguments must be a map", null)
return
}
} catch (e: Exception) {
result.error("groth16PublicBufferSize", "Invalid arguments type: ${e.message}", null)
return
}

val errorBufferSize = arguments["errorBufferSize"] as Int
val zkeyPath = arguments["zkeyPath"] as? String
val errorBufferSize = arguments["errorBufferSize"] as? Int

val publicSize = groth16PublicBufferSize(
zkeyPath,
errorBufferSize,
)
if (zkeyPath == null) { result.error("groth16PublicBufferSize", "Missing zkeyPath", null); return }
if (errorBufferSize == null) { result.error("groth16PublicBufferSize", "Missing errorBufferSize", null); return }

result.success(publicSize)
} catch (e: Exception) {
result.error("groth16PublicSizeForZkeyFile", e.message, null)
executor.execute {
try {
val publicSize = groth16PublicBufferSize(
zkeyPath,
errorBufferSize,
)
complete(result) { result.success(publicSize) }
} catch (e: RapidsnarkProverError) {
complete(result) { result.error("groth16PublicBufferSize", e.message, null) }
} catch (e: Exception) {
complete(result) { result.error("groth16PublicBufferSize", e.message, null) }
}
}
}

private fun callGroth16Verify(call: MethodCall, result: Result) {
try {
val arguments: Map<String, Any> = call.arguments<Map<String, Any>>()!!

val proof = arguments["proof"] as String
val inputs = arguments["inputs"] as String
val verificationKey = arguments["verificationKey"] as String

val errorBufferSize = arguments["errorBufferSize"] as Int
val arguments = try {
@Suppress("UNCHECKED_CAST")
call.arguments<Map<String, Any?>>() ?: run {
result.error("groth16Verify", "Arguments must be a map", null)
return
}
} catch (e: Exception) {
result.error("groth16Verify", "Invalid arguments type: ${e.message}", null)
return
}

val isValid = groth16Verify(
proof,
inputs,
verificationKey,
errorBufferSize,
)
val proof = arguments["proof"] as? String
val inputs = arguments["inputs"] as? String
val verificationKey = arguments["verificationKey"] as? String
val errorBufferSize = arguments["errorBufferSize"] as? Int

result.success(isValid)
} catch (e: Exception) {
result.error("groth16Verify", e.message, null)
if (proof == null || inputs == null || verificationKey == null) {
result.error("groth16Verify", "Missing proof / inputs / verificationKey", null); return
}
if (errorBufferSize == null) { result.error("groth16Verify", "Missing errorBufferSize", null); return }

executor.execute {
try {
val isValid = groth16Verify(
proof,
inputs,
verificationKey,
errorBufferSize,
)
complete(result) { result.success(isValid) }
} catch (e: RapidsnarkVerifierError) {
complete(result) { result.error("groth16Verify", e.message, null) }
} catch (e: Exception) {
complete(result) { result.error("groth16Verify", e.message, null) }
}
}
}
}
2 changes: 1 addition & 1 deletion example/android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
4 changes: 2 additions & 2 deletions example/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ pluginManagement {

plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.2.1" apply false
id "org.jetbrains.kotlin.android" version "1.9.0" apply false
id "com.android.application" version "8.9.0" apply false
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
}

include ":app"
2 changes: 1 addition & 1 deletion example/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
<string>13.0</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '12.0'
platform :ios, '13.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand Down
6 changes: 3 additions & 3 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,15 @@ SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_rapidsnark: 842c3ddde387254b6f8a36e1a0f403560ee169ca
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_rapidsnark: 943da7b717fd3435ce80d3422c64c12a0dd18be4
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
rapidsnark: da1c1d74a36ba8376700286d45707574d9d1afcb
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4

PODFILE CHECKSUM: 095d9af8b13ecba9b7619a234542d1b32779cac5
PODFILE CHECKSUM: 92906b04914919e75004799b609448ddd7c0a72e

COCOAPODS: 1.16.2
Loading