diff --git a/CHANGELOG.md b/CHANGELOG.md index db42a08..7ae79e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/android/build.gradle b/android/build.gradle index 8da8e2f..42fe9d6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -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" } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 42defcc..18362b7 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/android/src/main/kotlin/com/rapidsnark/flutter_rapidsnark/FlutterRapidsnarkPlugin.kt b/android/src/main/kotlin/com/rapidsnark/flutter_rapidsnark/FlutterRapidsnarkPlugin.kt index 0d6c50e..3243724 100644 --- a/android/src/main/kotlin/com/rapidsnark/flutter_rapidsnark/FlutterRapidsnarkPlugin.kt +++ b/android/src/main/kotlin/com/rapidsnark/flutter_rapidsnark/FlutterRapidsnarkPlugin.kt @@ -7,21 +7,26 @@ 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 { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// 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.newCachedThreadPool() + 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) } @@ -36,6 +41,16 @@ 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) { @@ -49,23 +64,33 @@ class FlutterRapidsnarkPlugin : FlutterPlugin, MethodCallHandler { 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 - ) - ) + // 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) } + } + } } catch (e: Exception) { - result.error("groth16ProveWithZKeyFilePath", e.message, null) + result.error("groth16Prove", "Failed to prove: ${e.message}", null) + return } } @@ -74,17 +99,28 @@ class FlutterRapidsnarkPlugin : FlutterPlugin, MethodCallHandler { val arguments: Map = call.arguments>()!! val zkeyPath = arguments["zkeyPath"] as String - val errorBufferSize = arguments["errorBufferSize"] as Int - val publicSize = groth16PublicBufferSize( - zkeyPath, - errorBufferSize, - ) - - result.success(publicSize) + 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) } + } + } } catch (e: Exception) { - result.error("groth16PublicSizeForZkeyFile", e.message, null) + result.error( + "groth16PublicBufferSize", + "Failed to calculate public buffer size: ${e.message}", + null + ) + return } } @@ -98,16 +134,24 @@ class FlutterRapidsnarkPlugin : FlutterPlugin, MethodCallHandler { val errorBufferSize = arguments["errorBufferSize"] as Int - val isValid = groth16Verify( - proof, - inputs, - verificationKey, - errorBufferSize, - ) - - result.success(isValid) + 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) } + } + } } catch (e: Exception) { - result.error("groth16Verify", e.message, null) + result.error("groth16Verify", "Invalid arguments type: ${e.message}", null) + return } } } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index dc159ec..b836b84 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 4fb6206..3ae3ac3 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -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" diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 7c56964..1dc6cf7 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 13.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index 529e1a9..8ca20d3 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -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' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index c612428..83ddd0a 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -102,8 +102,8 @@ 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 @@ -111,6 +111,6 @@ SPEC CHECKSUMS: share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 -PODFILE CHECKSUM: 095d9af8b13ecba9b7619a234542d1b32779cac5 +PODFILE CHECKSUM: 92906b04914919e75004799b609448ddd7c0a72e COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 4ca1961..8e70adb 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -12,6 +12,7 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -52,6 +53,7 @@ 70249D20E73433154197C873 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -72,6 +74,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 20BFE1D4D33226DD6BB98B99 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -107,6 +110,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -161,7 +165,6 @@ EEB12A9733F147839230EF7E /* Pods-RunnerTests.release.xcconfig */, C8AA96D495EDCBFEDA685C7D /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -205,6 +208,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -238,6 +244,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -331,10 +340,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; @@ -455,7 +468,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -471,7 +484,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 2U6UZ9UZP9; + DEVELOPMENT_TEAM = 6K7H9274Q6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -585,7 +598,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -636,7 +649,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -654,7 +667,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 2U6UZ9UZP9; + DEVELOPMENT_TEAM = 6K7H9274Q6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -677,7 +690,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 2U6UZ9UZP9; + DEVELOPMENT_TEAM = 6K7H9274Q6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -726,6 +739,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 15cada4..c3fedb2 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + { child: const Text('Run prover'), ), const SizedBox(height: 8), + ElevatedButton( + onPressed: () => _runProver(concurrent: true), + child: const Text('Run prover concurrently (5x)'), + ), + const SizedBox(height: 8), Text(_verificationResult ?? ''), ElevatedButton( onPressed: _runVerifier, @@ -256,7 +261,7 @@ class _MyAppState extends State { return verificationKey; } - Future _runProver() async { + Future _runProver({bool concurrent = false}) async { setState(() { _error = null; _proof = null; @@ -292,10 +297,35 @@ class _MyAppState extends State { final stopwatch = Stopwatch()..start(); - final proof = await _flutterRapidsnarkPlugin.groth16Prove( - zkeyPath: _zkeyPath, - witness: witness, - ); + ProveResult proof; + if (!concurrent) { + proof = await _flutterRapidsnarkPlugin.groth16Prove( + zkeyPath: _zkeyPath, + witness: witness, + ); + } else { + final futures = [ + for (int i = 0; i < 5; i++) + Future(() async { + print( + 'Starting proof generation $i at ${stopwatch.elapsedMilliseconds}'); + + final proof = await _flutterRapidsnarkPlugin.groth16Prove( + zkeyPath: _zkeyPath, + witness: witness, + ); + print( + 'Proof generation $i completed at ${stopwatch.elapsedMilliseconds}'); + return proof; + }).catchError((error) { + print('Error in proof generation $i: $error'); + throw error; + }), + ]; + + final results = await Future.wait(futures); + proof = results[0]; + } stopwatch.stop(); diff --git a/example/pubspec.lock b/example/pubspec.lock index d84c0f1..2a78ddc 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -145,7 +145,7 @@ packages: path: ".." relative: true source: path - version: "0.0.1-beta.3" + version: "0.0.1-beta.5" flutter_test: dependency: "direct dev" description: flutter @@ -170,26 +170,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -391,10 +391,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" typed_data: dependency: transitive description: @@ -447,10 +447,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -492,5 +492,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.24.0" diff --git a/ios/Classes/FlutterRapidsnarkPlugin.swift b/ios/Classes/FlutterRapidsnarkPlugin.swift deleted file mode 100644 index 874a972..0000000 --- a/ios/Classes/FlutterRapidsnarkPlugin.swift +++ /dev/null @@ -1,104 +0,0 @@ -import Flutter -import UIKit - -import rapidsnark - -public class FlutterRapidsnarkPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let taskQueue = registrar.messenger().makeBackgroundTaskQueue?() - let channel = FlutterMethodChannel( - name: "com.rapidsnark.flutter_rapidsnark", - binaryMessenger: registrar.messenger(), - codec: FlutterStandardMethodCodec.sharedInstance(), - taskQueue: taskQueue - ) - let instance = FlutterRapidsnarkPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "groth16Prove": - callGroth16Prove(call: call, result: result) - case "groth16PublicBufferSize": - callGroth16PublicBufferSize(call: call, result: result) - case "groth16Verify": - callGroth16Verify(call: call, result: result) - default: - result(FlutterMethodNotImplemented) - } - } - - private func callGroth16Prove(call: FlutterMethodCall, result: @escaping FlutterResult) { - let args = call.arguments as! Dictionary - - let zkeyPath = args["zkeyPath"] as! String - let witness = (args["witness"] as! FlutterStandardTypedData).data - - let proofBufferSize = (args["proofBufferSize"] as! NSNumber).intValue - let publicBufferSize = (args["publicBufferSize"] as? NSNumber)?.intValue - let errorBufferSize = (args["errorBufferSize"] as! NSNumber).intValue - - do { - let proof = try groth16Prove( - zkeyPath: zkeyPath, - witness: witness, - proofBufferSize: proofBufferSize, - publicBufferSize: publicBufferSize, - errorBufferSize: errorBufferSize - ) - - result([ - "proof": proof.proof, - "publicSignals": proof.publicSignals - ]) - } catch let error as RapidsnarkProverError { - result(FlutterError(code: "groth16Prove", message: error.message, details: nil)) - } catch let error { - result(FlutterError(code: "groth16Prove", message: "Unknown error", details: error.localizedDescription)) - } - } - - private func callGroth16PublicBufferSize(call: FlutterMethodCall, result: FlutterResult) { - let args = call.arguments as! Dictionary - - let zkeyPath = args["zkeyPath"] as! String - - let errorBufferSize = (args["errorBufferSize"] as! NSNumber).intValue - - do { - let publicSize = try groth16PublicBufferSize( - zkeyPath: zkeyPath, - errorBufferSize: errorBufferSize - ) - - result(publicSize) - } catch let error as RapidsnarkProverError { - result(FlutterError(code: "groth16PublicSizeForZkeyFilePath", message: error.message, details: nil)) - } catch { - result(FlutterError(code: "groth16PublicSizeForZkeyFilePath", message: "Unknown error", details: nil)) - } - } - - private func callGroth16Verify(call: FlutterMethodCall, result: FlutterResult) { - let args = call.arguments as! Dictionary - - let proof = args["proof"] as! String - let inputs = args["inputs"] as! String - let verificationKey = args["verificationKey"] as! String - - do { - let isValid = try groth16Verify( - proof: proof.data(using: .utf8)!, - inputs: inputs.data(using: .utf8)!, - verificationKey: verificationKey.data(using: .utf8)! - ) - - result(isValid) - } catch let error as RapidsnarkVerifierError { - result(FlutterError(code: "groth16Verify", message: error.message, details: nil)) - } catch { - result(FlutterError(code: "groth16Verify", message: "Unknown error", details: nil)) - } - } -} diff --git a/ios/flutter_rapidsnark.podspec b/ios/flutter_rapidsnark.podspec index 11e1341..074e313 100644 --- a/ios/flutter_rapidsnark.podspec +++ b/ios/flutter_rapidsnark.podspec @@ -17,12 +17,12 @@ Rapidsnark Flutter pod for plugin. 'Dmytro Sukhyi' => 'dmytro.sukhiy@gmail.com' } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + s.source_files = 'flutter_rapidsnark/Sources/flutter_rapidsnark/**/*' s.dependency 'Flutter' s.platform = :ios, '11.0' # Flutter.framework does not contain a i386 slice. - s.pod_target_xcconfig = { + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', } s.swift_version = '5.0' diff --git a/ios/flutter_rapidsnark/Package.resolved b/ios/flutter_rapidsnark/Package.resolved new file mode 100644 index 0000000..62f0b82 --- /dev/null +++ b/ios/flutter_rapidsnark/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "ios-rapidsnark", + "kind" : "remoteSourceControl", + "location" : "https://github.com/iden3/ios-rapidsnark.git", + "state" : { + "revision" : "120a06abca61e3afabe78779447698cba4910af9", + "version" : "0.0.1-beta.2" + } + } + ], + "version" : 2 +} diff --git a/ios/flutter_rapidsnark/Package.swift b/ios/flutter_rapidsnark/Package.swift new file mode 100644 index 0000000..c4477dd --- /dev/null +++ b/ios/flutter_rapidsnark/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "flutter_rapidsnark", + platforms: [ + .iOS("12.0"), + .macOS("10.14") + ], + products: [ + .library(name: "flutter-rapidsnark", targets: ["flutter_rapidsnark"]) + ], + dependencies: [ + .package(url: "https://github.com/iden3/ios-rapidsnark.git", from: "0.0.1-beta.2") + ], + targets: [ + .target( + name: "flutter_rapidsnark", + dependencies: [ + .product(name: "rapidsnark", package: "ios-rapidsnark") + ], + resources: [] + ) + ] +) diff --git a/ios/flutter_rapidsnark/Sources/flutter_rapidsnark/FlutterRapidsnarkPlugin.swift b/ios/flutter_rapidsnark/Sources/flutter_rapidsnark/FlutterRapidsnarkPlugin.swift new file mode 100644 index 0000000..a242e4d --- /dev/null +++ b/ios/flutter_rapidsnark/Sources/flutter_rapidsnark/FlutterRapidsnarkPlugin.swift @@ -0,0 +1,117 @@ +import Flutter +import UIKit + +import rapidsnark + +let channelName = "com.rapidsnark.flutter_rapidsnark" + +public class FlutterRapidsnarkPlugin: NSObject, FlutterPlugin { + // Concurrent queue to allow multiple native proving operations to run in parallel + private let workQueue = DispatchQueue(label: "com.rapidsnark.flutter_rapidsnark.proving", qos: .userInitiated, attributes: .concurrent) + + public static func register(with registrar: FlutterPluginRegistrar) { + let messenger = registrar.messenger() + let codec = FlutterStandardMethodCodec.sharedInstance() + // We still provide a background task queue so initial message handling is off the main thread, + // but heavy work is further dispatched onto our own concurrent queue to enable parallelism. + let taskQueue = messenger.makeBackgroundTaskQueue?() + let channel = FlutterMethodChannel(name: channelName, binaryMessenger: messenger, codec: codec, taskQueue: taskQueue) + let instance = FlutterRapidsnarkPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "groth16Prove": + callGroth16Prove(call: call, result: result) + case "groth16PublicBufferSize": + callGroth16PublicBufferSize(call: call, result: result) + case "groth16Verify": + callGroth16Verify(call: call, result: result) + default: + result(FlutterMethodNotImplemented) + } + } + + private func extractArguments(_ call: FlutterMethodCall) -> [String: Any]? { + return call.arguments as? [String: Any] + } + + private func callGroth16Prove(call: FlutterMethodCall, result: @escaping FlutterResult) { + let args = call.arguments as! Dictionary + + let zkeyPath = args["zkeyPath"] as! String + let witnessData = (args["witness"] as! FlutterStandardTypedData).data + + let proofBufferSize = (args["proofBufferSize"] as! NSNumber).intValue + let publicBufferSize = (args["publicBufferSize"] as? NSNumber)?.intValue + let errorBufferSize = (args["errorBufferSize"] as! NSNumber).intValue + + // Dispatch onto concurrent queue so multiple calls can run in parallel when invoked from Flutter (e.g. Future.wait) + workQueue.async { + do { + let proof = try groth16Prove( + zkeyPath: zkeyPath, + witness: witnessData, + proofBufferSize: proofBufferSize, + publicBufferSize: publicBufferSize, + errorBufferSize: errorBufferSize + ) + + // Return result (MethodChannel is thread-safe for calling the result closure from background threads) + result([ + "proof": proof.proof, + "publicSignals": proof.publicSignals + ]) + } catch let error as RapidsnarkProverError { + result(FlutterError(code: "groth16Prove", message: error.message, details: nil)) + } catch let error { + result(FlutterError(code: "groth16Prove", message: "Unknown error", details: error.localizedDescription)) + } + } + } + + private func callGroth16PublicBufferSize(call: FlutterMethodCall, result: @escaping FlutterResult) { + let args = call.arguments as! Dictionary + + let zkeyPath = args["zkeyPath"] as! String + let errorBufferSize = (args["errorBufferSize"] as! NSNumber).intValue + + workQueue.async { + do { + let publicSize = try groth16PublicBufferSize( + zkeyPath: zkeyPath, + errorBufferSize: errorBufferSize + ) + result(publicSize) + } catch let error as RapidsnarkProverError { + result(FlutterError(code: "groth16PublicSizeForZkeyFilePath", message: error.message, details: nil)) + } catch { + result(FlutterError(code: "groth16PublicSizeForZkeyFilePath", message: "Unknown error", details: nil)) + } + } + } + + private func callGroth16Verify(call: FlutterMethodCall, result: @escaping FlutterResult) { + let args = call.arguments as! Dictionary + + let proof = args["proof"] as! String + let inputs = args["inputs"] as! String + let verificationKey = args["verificationKey"] as! String + + workQueue.async { + do { + let isValid = try groth16Verify( + proof: proof.data(using: .utf8)!, + inputs: inputs.data(using: .utf8)!, + verificationKey: verificationKey.data(using: .utf8)! + ) + result(isValid) + } catch let error as RapidsnarkVerifierError { + result(FlutterError(code: "groth16Verify", message: error.message, details: nil)) + } catch { + result(FlutterError(code: "groth16Verify", message: "Unknown error", details: nil)) + } + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index bbad4f9..8453d42 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_rapidsnark description: "Rapidsnark Flutter Plugin" -version: 0.0.1-beta.4 +version: 0.0.1-beta.5 homepage: https://github.com/iden3/flutter-rapidsnark environment: