diff --git a/cli/src/create/utils.rs b/cli/src/create/utils.rs index 5f1eb6b05..1f41babd6 100644 --- a/cli/src/create/utils.rs +++ b/cli/src/create/utils.rs @@ -136,6 +136,7 @@ pub fn copy_keys(target_dir: std::path::PathBuf) -> Result<()> { include_dir!("$CARGO_MANIFEST_DIR/src/template/init/test-vectors/circom/witnesscalc"), include_dir!("$CARGO_MANIFEST_DIR/src/template/init/test-vectors/halo2"), include_dir!("$CARGO_MANIFEST_DIR/src/template/init/test-vectors/noir"), + include_dir!("$CARGO_MANIFEST_DIR/src/template/init/test-vectors/gnark"), ]; key_dirs .iter() diff --git a/cli/src/template/android/app/src/androidTest/java/com/example/moproapp/ExampleInstrumentedTest.kt b/cli/src/template/android/app/src/androidTest/java/com/example/moproapp/ExampleInstrumentedTest.kt index 16703b201..9c9a9db03 100644 --- a/cli/src/template/android/app/src/androidTest/java/com/example/moproapp/ExampleInstrumentedTest.kt +++ b/cli/src/template/android/app/src/androidTest/java/com/example/moproapp/ExampleInstrumentedTest.kt @@ -89,6 +89,19 @@ class ExampleInstrumentedTest { composeTestRule.onNodeWithTag("rapidsnarkVerifyProofButton").assertIsDisplayed() } + @Test + fun gnarkButtonClick() { + composeTestRule.setContent { GnarkComponent() } + + composeTestRule.onNodeWithTag("gnarkGenerateProofButton").performClick() + composeTestRule.onNodeWithTag("gnarkGenerateProofButton").assertIsDisplayed() + + waitForProofCompletion("gnarkVerifyProofButton", maxWaitSeconds = 10) + + composeTestRule.onNodeWithTag("gnarkVerifyProofButton").performClick() + composeTestRule.onNodeWithTag("gnarkVerifyProofButton").assertIsDisplayed() + } + @Test fun halo2ButtonClick() { composeTestRule.setContent { FibonacciComponent() } diff --git a/cli/src/template/android/app/src/main/java/com/example/moproapp/FibonacciComponent.kt b/cli/src/template/android/app/src/main/java/com/example/moproapp/FibonacciComponent.kt index 4511cd482..6df823954 100644 --- a/cli/src/template/android/app/src/main/java/com/example/moproapp/FibonacciComponent.kt +++ b/cli/src/template/android/app/src/main/java/com/example/moproapp/FibonacciComponent.kt @@ -1,10 +1,20 @@ package com.example.moproapp -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -15,16 +25,18 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import uniffi.mopro.Halo2ProofResult import uniffi.mopro.generateHalo2Proof import uniffi.mopro.verifyHalo2Proof @Composable fun FibonacciComponent() { - var provingTime by remember { mutableStateOf("proving time:") } - var verifyingTime by remember { mutableStateOf("verifying time: ") } - var valid by remember { mutableStateOf("valid:") } + var provingTime by remember { mutableStateOf(null) } + var verifyingTime by remember { mutableStateOf(null) } + var valid by remember { mutableStateOf(null) } var isGeneratingProof by remember { mutableStateOf(false) } var isVerifyingProof by remember { mutableStateOf(false) } var res by remember { @@ -37,57 +49,114 @@ fun FibonacciComponent() { val provingKeyPath = getFilePathFromAssets("plonk_fibonacci_pk.bin") val verifyingKeyPath = getFilePathFromAssets("plonk_fibonacci_vk.bin") - val inputs = mutableMapOf>() inputs["out"] = listOf("55") - Box(modifier = Modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) { - Button( - onClick = { - isGeneratingProof = true - Thread { - try { - val startTime = System.currentTimeMillis() - res = generateHalo2Proof(srsPath, provingKeyPath, inputs) - val endTime = System.currentTimeMillis() - provingTime = - "proving time: " + - (endTime - startTime).toString() + - " ms" - } finally { - isGeneratingProof = false - } - } - .start() - }, - modifier = Modifier.padding(top = 20.dp).testTag("halo2GenerateProofButton"), - enabled = !isGeneratingProof && !isVerifyingProof - ) { Text(text = "generate proof") } - Button( - onClick = { - isVerifyingProof = true - Thread { - try { - val startTime = System.currentTimeMillis() - valid = "valid: " + verifyHalo2Proof(srsPath, verifyingKeyPath, res.proof, res.inputs).toString() - val endTime = System.currentTimeMillis() - verifyingTime = "verifying time: " + (endTime - startTime).toString() + " ms" - } finally { - isVerifyingProof = false - } - }.start() - }, - modifier = Modifier.padding(top = 120.dp).testTag("halo2VerifyProofButton"), - enabled = !isGeneratingProof && !isVerifyingProof && res.proof.isNotEmpty() - ) { Text(text = "verify proof") } + val scrollState = rememberScrollState() + val isBusy = isGeneratingProof || isVerifyingProof + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { Text( - text = "Halo2 Fibonacci proof", - modifier = Modifier.padding(bottom = 180.dp), - fontWeight = FontWeight.Bold + text = "Halo2 Fibonacci", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + fontSize = 22.sp ) + Text( + text = "Proves the 10th Fibonacci number is 55 using Plonk.", + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 8.dp) + ) + + OutlinedCard( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.outlinedCardColors() + ) { + Column( + modifier = Modifier.padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + if (isBusy) { + CircularProgressIndicator(modifier = Modifier.padding(8.dp)) + Text( + text = if (isGeneratingProof) "Generating proof…" else "Verifying…", + style = MaterialTheme.typography.bodyMedium + ) + } + + Button( + onClick = { + isGeneratingProof = true + provingTime = null + valid = null + verifyingTime = null + Thread { + try { + val startTime = System.currentTimeMillis() + res = generateHalo2Proof(srsPath, provingKeyPath, inputs) + val endTime = System.currentTimeMillis() + provingTime = "${endTime - startTime} ms" + } finally { + isGeneratingProof = false + } + }.start() + }, + modifier = Modifier.fillMaxWidth().testTag("halo2GenerateProofButton"), + enabled = !isBusy + ) { + Text("Generate proof") + } + + Button( + onClick = { + isVerifyingProof = true + verifyingTime = null + Thread { + try { + val startTime = System.currentTimeMillis() + val result = verifyHalo2Proof(srsPath, verifyingKeyPath, res.proof, res.inputs) + val endTime = System.currentTimeMillis() + verifyingTime = "${endTime - startTime} ms" + valid = result.toString() + } finally { + isVerifyingProof = false + } + }.start() + }, + modifier = Modifier.fillMaxWidth().testTag("halo2VerifyProofButton"), + enabled = !isBusy && res.proof.isNotEmpty() + ) { + Text("Verify proof") + } + } + } + + if (provingTime != null || verifyingTime != null || valid != null) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text("Results", fontWeight = FontWeight.SemiBold, fontSize = 16.sp) + provingTime?.let { Text("Proving: $it", style = MaterialTheme.typography.bodyMedium) } + verifyingTime?.let { Text("Verifying: $it", style = MaterialTheme.typography.bodyMedium) } + valid?.let { Text("Valid: $it", style = MaterialTheme.typography.bodyMedium, fontWeight = if (it == "true") FontWeight.Bold else FontWeight.Normal) } + } + } + } - Text(text = provingTime, modifier = Modifier.padding(top = 250.dp).width(200.dp)) - Text(text = valid, modifier = Modifier.padding(top = 300.dp).width(200.dp)) - Text(text = verifyingTime, modifier = Modifier.padding(top = 350.dp).width(200.dp)) + Spacer(modifier = Modifier.height(24.dp)) } -} \ No newline at end of file +} diff --git a/cli/src/template/android/app/src/main/java/com/example/moproapp/GnarkComponent.kt b/cli/src/template/android/app/src/main/java/com/example/moproapp/GnarkComponent.kt new file mode 100644 index 000000000..ac6965c32 --- /dev/null +++ b/cli/src/template/android/app/src/main/java/com/example/moproapp/GnarkComponent.kt @@ -0,0 +1,183 @@ +package com.example.moproapp + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import uniffi.mopro.generateGnarkProof +import uniffi.mopro.verifyGnarkProof +import uniffi.mopro.GnarkProofResult + +@Composable +fun GnarkComponent() { + var provingTime by remember { mutableStateOf(null) } + var verifyingTime by remember { mutableStateOf(null) } + var valid by remember { mutableStateOf(null) } + var errorMessage by remember { mutableStateOf(null) } + var isGeneratingProof by remember { mutableStateOf(false) } + var isVerifyingProof by remember { mutableStateOf(false) } + var proofResult by remember { mutableStateOf(null) } + + val r1csPath = getFilePathFromAssets("cubic_circuit.r1cs") + val pkPath = getFilePathFromAssets("cubic_circuit.pk") + val vkPath = getFilePathFromAssets("cubic_circuit.vk") + // Cubic circuit: x^3 + x + 5 = Y. For X=3: 27+3+5=35 + val witnessJson = """{"X": "3", "Y": "35"}""" + + val isBusy = isGeneratingProof || isVerifyingProof + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Gnark Cubic", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + fontSize = 22.sp + ) + Text( + text = "Proves x³ + x + 5 = Y (e.g. X=3 → Y=35) using Groth16 BN254.", + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 8.dp) + ) + + OutlinedCard( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.outlinedCardColors() + ) { + Column( + modifier = Modifier.padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + if (isBusy) { + CircularProgressIndicator(modifier = Modifier.padding(8.dp)) + Text( + text = if (isGeneratingProof) "Generating proof…" else "Verifying…", + style = MaterialTheme.typography.bodyMedium + ) + } + + Button( + onClick = { + isGeneratingProof = true + provingTime = null + valid = null + verifyingTime = null + errorMessage = null + Thread { + try { + val startTime = System.currentTimeMillis() + proofResult = generateGnarkProof(r1csPath, pkPath, witnessJson) + val endTime = System.currentTimeMillis() + provingTime = "${endTime - startTime} ms" + } catch (e: Exception) { + provingTime = "Failed" + errorMessage = e.message ?: "Proof generation failed" + proofResult = null + e.printStackTrace() + } finally { + isGeneratingProof = false + } + }.start() + }, + modifier = Modifier.fillMaxWidth().testTag("gnarkGenerateProofButton"), + enabled = !isBusy + ) { + Text("Generate proof") + } + + Button( + onClick = { + isVerifyingProof = true + verifyingTime = null + valid = null + errorMessage = null + val currentProof = proofResult + if (currentProof == null) { + valid = "false" + isVerifyingProof = false + return@Button + } + Thread { + try { + val startTime = System.currentTimeMillis() + val isValid = verifyGnarkProof(r1csPath, vkPath, currentProof) + val endTime = System.currentTimeMillis() + verifyingTime = "${endTime - startTime} ms" + valid = isValid.toString() + } catch (e: Exception) { + verifyingTime = "Failed" + errorMessage = e.message ?: "Verification failed" + e.printStackTrace() + } finally { + isVerifyingProof = false + } + }.start() + }, + modifier = Modifier.fillMaxWidth().testTag("gnarkVerifyProofButton"), + enabled = !isBusy && proofResult != null + ) { + Text("Verify proof") + } + } + } + + if (provingTime != null || verifyingTime != null || valid != null || errorMessage != null) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text("Results", fontWeight = FontWeight.SemiBold, fontSize = 16.sp) + provingTime?.let { Text("Proving: $it", style = MaterialTheme.typography.bodyMedium) } + verifyingTime?.let { Text("Verifying: $it", style = MaterialTheme.typography.bodyMedium) } + valid?.let { + Text( + "Valid: $it", + style = MaterialTheme.typography.bodyMedium, + fontWeight = if (it == "true") FontWeight.Bold else FontWeight.Normal + ) + } + errorMessage?.let { Text("Error: $it", style = MaterialTheme.typography.bodyMedium) } + } + } + } + + Spacer(modifier = Modifier.height(24.dp)) + } +} diff --git a/cli/src/template/android/app/src/main/java/com/example/moproapp/MainActivity.kt b/cli/src/template/android/app/src/main/java/com/example/moproapp/MainActivity.kt index 0490e8015..d12976ef8 100644 --- a/cli/src/template/android/app/src/main/java/com/example/moproapp/MainActivity.kt +++ b/cli/src/template/android/app/src/main/java/com/example/moproapp/MainActivity.kt @@ -51,7 +51,7 @@ class MainActivity : ComponentActivity() { @Composable fun MainScreen() { var selectedTab by remember { mutableIntStateOf(0) } - val tabs = listOf("Circom", "Halo2", "Noir") + val tabs = listOf("Circom", "Halo2", "Noir", "Gnark") Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Column(modifier = Modifier @@ -72,6 +72,8 @@ fun MainScreen() { 0 -> MultiplierComponent() 1 -> FibonacciComponent() 2 -> NoirComponent() + 3 -> GnarkComponent() + else -> MultiplierComponent() } } } diff --git a/cli/src/template/android/app/src/main/java/com/example/moproapp/MultiplierComponent.kt b/cli/src/template/android/app/src/main/java/com/example/moproapp/MultiplierComponent.kt index 702fbb1a1..068761ba2 100644 --- a/cli/src/template/android/app/src/main/java/com/example/moproapp/MultiplierComponent.kt +++ b/cli/src/template/android/app/src/main/java/com/example/moproapp/MultiplierComponent.kt @@ -1,19 +1,35 @@ package com.example.moproapp -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import uniffi.mopro.CircomProofResult +import androidx.compose.ui.unit.sp import uniffi.mopro.CircomProof +import uniffi.mopro.CircomProofResult import uniffi.mopro.G1 import uniffi.mopro.G2 import uniffi.mopro.generateCircomProof @@ -22,10 +38,10 @@ import uniffi.mopro.ProofLib @Composable fun MultiplierComponent() { - var provingTime by remember { mutableStateOf("proving time:") } - var verifyingTime by remember { mutableStateOf("verifying time: ") } - var valid by remember { mutableStateOf("valid:") } - var output by remember { mutableStateOf("output:") } + var provingTime by remember { mutableStateOf(null) } + var verifyingTime by remember { mutableStateOf(null) } + var valid by remember { mutableStateOf(null) } + var output by remember { mutableStateOf(null) } var isGeneratingCircomProof by remember { mutableStateOf(false) } var isGeneratingRapidsnarkProof by remember { mutableStateOf(false) } var isVerifyingProof by remember { mutableStateOf(false) } @@ -44,94 +60,168 @@ fun MultiplierComponent() { ) } - val input_str: String = "{\"b\":[\"5\"],\"a\":[\"3\"]}" - + val inputStr = "{\"b\":[\"5\"],\"a\":[\"3\"]}" val zkeyPath = getFilePathFromAssets("multiplier2_final.zkey") - Box(modifier = Modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) { - Button( - onClick = { - isGeneratingCircomProof = true - Thread( - Runnable { - try { - val startTime = System.currentTimeMillis() - res = generateCircomProof(zkeyPath, input_str, ProofLib.ARKWORKS) - val endTime = System.currentTimeMillis() - provingTime = "proving time: " + (endTime - startTime).toString() + " ms" - } finally { - isGeneratingCircomProof = false - } - } - ).start() - }, - modifier = Modifier.padding(top = 20.dp).testTag("circomGenerateProofButton"), - enabled = !isGeneratingCircomProof && !isGeneratingRapidsnarkProof && !isVerifyingProof - ) { Text(text = "generate proof") } - Button( - onClick = { - isVerifyingProof = true - Thread { - try { - val startTime = System.currentTimeMillis() - valid = "valid: " + verifyCircomProof(zkeyPath, res, ProofLib.ARKWORKS).toString() - val endTime = System.currentTimeMillis() - verifyingTime = "verifying time: " + (endTime - startTime).toString() + " ms" - output = "output: " + res.inputs - } finally { - isVerifyingProof = false - } - }.start() - }, - modifier = Modifier.padding(top = 120.dp).testTag("circomVerifyProofButton"), - enabled = !isGeneratingCircomProof && !isGeneratingRapidsnarkProof && !isVerifyingProof && res.proof.a.x.isNotEmpty() - ) { Text(text = "verify proof") } - Button( - onClick = { - isGeneratingRapidsnarkProof = true - Thread( - Runnable { - try { - val startTime = System.currentTimeMillis() - res = generateCircomProof(zkeyPath, input_str, ProofLib.RAPIDSNARK) - val endTime = System.currentTimeMillis() - provingTime = "proving time: " + (endTime - startTime).toString() + " ms" - } finally { - isGeneratingRapidsnarkProof = false - } - } - ).start() - }, - modifier = Modifier.padding(top = 220.dp).testTag("rapidsnarkGenerateProofButton"), - enabled = !isGeneratingCircomProof && !isGeneratingRapidsnarkProof && !isVerifyingProof - ) { Text(text = "generate proof (rapidsnark)") } - Button( - onClick = { - isVerifyingProof = true - Thread { - try { - val startTime = System.currentTimeMillis() - valid = "valid: " + verifyCircomProof(zkeyPath, res, ProofLib.RAPIDSNARK).toString() - val endTime = System.currentTimeMillis() - verifyingTime = "verifying time: " + (endTime - startTime).toString() + " ms" - output = "output: " + res.inputs - } finally { - isVerifyingProof = false - } - }.start() - }, - modifier = Modifier.padding(top = 320.dp).testTag("rapidsnarkVerifyProofButton"), - enabled = !isGeneratingCircomProof && !isGeneratingRapidsnarkProof && !isVerifyingProof && res.proof.a.x.isNotEmpty() - ) { Text(text = "verify proof (rapidsnark)") } + val isBusy = isGeneratingCircomProof || isGeneratingRapidsnarkProof || isVerifyingProof + + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { Text( - text = "Multiplier proof", - modifier = Modifier.padding(bottom = 180.dp), - fontWeight = FontWeight.Bold + text = "Circom Multiplier", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + fontSize = 22.sp ) + Text( + text = "Proves a × b = c (e.g. 3 × 5) with Arkworks or Rapidsnark.", + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 8.dp) + ) + + // Arkworks + OutlinedCard( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.outlinedCardColors() + ) { + Column( + modifier = Modifier.padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text("Arkworks", fontWeight = FontWeight.SemiBold, fontSize = 16.sp) + if (isGeneratingCircomProof || isVerifyingProof) { + CircularProgressIndicator(modifier = Modifier.padding(8.dp)) + } + Button( + onClick = { + isGeneratingCircomProof = true + provingTime = null + valid = null + verifyingTime = null + output = null + Thread { + try { + val startTime = System.currentTimeMillis() + res = generateCircomProof(zkeyPath, inputStr, ProofLib.ARKWORKS) + val endTime = System.currentTimeMillis() + provingTime = "${endTime - startTime} ms" + } finally { + isGeneratingCircomProof = false + } + }.start() + }, + modifier = Modifier.fillMaxWidth().testTag("circomGenerateProofButton"), + enabled = !isBusy + ) { Text("Generate proof") } + Button( + onClick = { + isVerifyingProof = true + verifyingTime = null + Thread { + try { + val startTime = System.currentTimeMillis() + val v = verifyCircomProof(zkeyPath, res, ProofLib.ARKWORKS) + val endTime = System.currentTimeMillis() + verifyingTime = "${endTime - startTime} ms" + valid = v.toString() + output = res.inputs.toString() + } finally { + isVerifyingProof = false + } + }.start() + }, + modifier = Modifier.fillMaxWidth().testTag("circomVerifyProofButton"), + enabled = !isBusy && res.proof.a.x.isNotEmpty() + ) { Text("Verify proof") } + } + } + + // Rapidsnark + OutlinedCard( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.outlinedCardColors() + ) { + Column( + modifier = Modifier.padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text("RapidSnark", fontWeight = FontWeight.SemiBold, fontSize = 16.sp) + if (isGeneratingRapidsnarkProof || isVerifyingProof) { + CircularProgressIndicator(modifier = Modifier.padding(8.dp)) + } + Button( + onClick = { + isGeneratingRapidsnarkProof = true + provingTime = null + valid = null + verifyingTime = null + output = null + Thread { + try { + val startTime = System.currentTimeMillis() + res = generateCircomProof(zkeyPath, inputStr, ProofLib.RAPIDSNARK) + val endTime = System.currentTimeMillis() + provingTime = "${endTime - startTime} ms" + } finally { + isGeneratingRapidsnarkProof = false + } + }.start() + }, + modifier = Modifier.fillMaxWidth().testTag("rapidsnarkGenerateProofButton"), + enabled = !isBusy + ) { Text("Generate proof (RapidSnark)") } + Button( + onClick = { + isVerifyingProof = true + verifyingTime = null + Thread { + try { + val startTime = System.currentTimeMillis() + val v = verifyCircomProof(zkeyPath, res, ProofLib.RAPIDSNARK) + val endTime = System.currentTimeMillis() + verifyingTime = "${endTime - startTime} ms" + valid = v.toString() + output = res.inputs.toString() + } finally { + isVerifyingProof = false + } + }.start() + }, + modifier = Modifier.fillMaxWidth().testTag("rapidsnarkVerifyProofButton"), + enabled = !isBusy && res.proof.a.x.isNotEmpty() + ) { Text("Verify proof (RapidSnark)") } + } + } + + if (provingTime != null || verifyingTime != null || valid != null || output != null) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text("Results", fontWeight = FontWeight.SemiBold, fontSize = 16.sp) + provingTime?.let { Text("Proving: $it", style = MaterialTheme.typography.bodyMedium) } + verifyingTime?.let { Text("Verifying: $it", style = MaterialTheme.typography.bodyMedium) } + valid?.let { Text("Valid: $it", style = MaterialTheme.typography.bodyMedium, fontWeight = if (it == "true") FontWeight.Bold else FontWeight.Normal) } + output?.let { Text("Output: $it", style = MaterialTheme.typography.bodyMedium) } + } + } + } - Text(text = provingTime, modifier = Modifier.padding(top = 420.dp).width(200.dp)) - Text(text = valid, modifier = Modifier.padding(top = 450.dp).width(200.dp)) - Text(text = verifyingTime, modifier = Modifier.padding(top = 480.dp).width(200.dp)) - Text(text = output, modifier = Modifier.padding(top = 510.dp).width(200.dp)) + Spacer(modifier = Modifier.height(24.dp)) } -} \ No newline at end of file +} diff --git a/cli/src/template/android/app/src/main/java/com/example/moproapp/NoirComponent.kt b/cli/src/template/android/app/src/main/java/com/example/moproapp/NoirComponent.kt index 6a9dfe785..8cedd1874 100644 --- a/cli/src/template/android/app/src/main/java/com/example/moproapp/NoirComponent.kt +++ b/cli/src/template/android/app/src/main/java/com/example/moproapp/NoirComponent.kt @@ -1,16 +1,26 @@ package com.example.moproapp -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -19,32 +29,26 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import org.json.JSONObject import uniffi.mopro.generateNoirProof -import uniffi.mopro.verifyNoirProof import uniffi.mopro.getNoirVerificationKey -import java.io.File -import java.io.InputStream +import uniffi.mopro.verifyNoirProof @Composable fun NoirComponent() { val context = LocalContext.current - var provingTime by remember { mutableStateOf("") } - var proofResult by remember { mutableStateOf("") } - var verificationTime by remember { mutableStateOf("") } - var verificationResult by remember { mutableStateOf("") } + var provingTime by remember { mutableStateOf(null) } + var proofResult by remember { mutableStateOf(null) } + var verificationTime by remember { mutableStateOf(null) } + var verificationResult by remember { mutableStateOf(null) } var proofBytes by remember { mutableStateOf(null) } var verificationKey by remember { mutableStateOf(null) } - - // Status states var isGeneratingProof by remember { mutableStateOf(false) } var isVerifyingProof by remember { mutableStateOf(false) } var statusMessage by remember { mutableStateOf("Ready to generate proof") } val circuitFile = getFilePathFromAssets("noir_multiplier2.json") val srsFile = getFilePathFromAssets("noir_multiplier2.srs") - - // Load existing verification key from assets + val existingVk = remember { try { context.assets.open("noir_multiplier2.vk").readBytes() @@ -53,54 +57,66 @@ fun NoirComponent() { } } - - Box(modifier = Modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text( - text = "Noir Multiplier2", - modifier = Modifier.padding(bottom = 20.dp), - fontWeight = FontWeight.Bold, - fontSize = 22.sp - ) - - // Status message with prominent styling - Text( - text = statusMessage, - modifier = Modifier.padding(bottom = 24.dp), - textAlign = TextAlign.Center, - fontSize = 16.sp, - fontWeight = if (isGeneratingProof || isVerifyingProof) FontWeight.Bold else FontWeight.Normal - ) - - // Progress indicator when operations are running - if (isGeneratingProof || isVerifyingProof) { - CircularProgressIndicator( - modifier = Modifier.padding(bottom = 16.dp) + val isBusy = isGeneratingProof || isVerifyingProof + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Noir Multiplier", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + fontSize = 22.sp + ) + Text( + text = "Proves a × b = c using Noir (e.g. 3 × 5) with Keccak for Solidity.", + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 8.dp) + ) + + OutlinedCard( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.outlinedCardColors() + ) { + Column( + modifier = Modifier.padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = statusMessage, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + fontWeight = if (isBusy) FontWeight.SemiBold else FontWeight.Normal ) - } - - Button( - onClick = { - isGeneratingProof = true - provingTime = "" - proofResult = "" - statusMessage = "Generating proof... This may take some time" + if (isBusy) { + CircularProgressIndicator(modifier = Modifier.padding(8.dp)) + } - Thread( - Runnable { + Button( + onClick = { + isGeneratingProof = true + provingTime = null + proofResult = null + statusMessage = "Generating proof…" + Thread { try { val inputs = listOf("3", "5") - val onChain = true // Use Keccak for Solidity compatibility + val onChain = true val lowMemoryMode = false - - // First, get or use existing verification key - val vk = existingVk ?: run { - statusMessage = "Generating verification key..." + val vk: ByteArray = existingVk ?: run { + statusMessage = "Generating verification key…" getNoirVerificationKey(circuitFile, srsFile, onChain, lowMemoryMode) } verificationKey = vk - - statusMessage = "Generating proof with verification key..." + statusMessage = "Generating proof…" val startTime = System.currentTimeMillis() proofBytes = generateNoirProof( circuitFile, @@ -111,45 +127,35 @@ fun NoirComponent() { lowMemoryMode ) val endTime = System.currentTimeMillis() - val duration = endTime - startTime - - provingTime = "Proving time: $duration ms" - proofResult = "Proof generated: ${proofBytes?.size ?: 0} bytes" - statusMessage = "Proof generation completed" + provingTime = "${endTime - startTime} ms" + proofResult = "Proof: ${proofBytes?.size ?: 0} bytes" + statusMessage = "Proof generated" } catch (e: Exception) { - provingTime = "Proving failed" + provingTime = "Failed" proofResult = "Error: ${e.message}" - statusMessage = "Proof generation failed" + statusMessage = "Proof failed" e.printStackTrace() } finally { isGeneratingProof = false } - } - ).start() - }, - modifier = Modifier.padding(top = 20.dp).testTag("noirGenerateProofButton"), - enabled = !isGeneratingProof && !isVerifyingProof - ) { - Text(text = "Generate Proof") - } - - Spacer(modifier = Modifier.height(16.dp)) - - Button( - onClick = { - isVerifyingProof = true - verificationTime = "" - verificationResult = "" - statusMessage = "Verifying proof..." - - Thread( - Runnable { + }.start() + }, + modifier = Modifier.fillMaxWidth().testTag("noirGenerateProofButton"), + enabled = !isBusy + ) { Text("Generate proof") } + + Button( + onClick = { + isVerifyingProof = true + verificationTime = null + verificationResult = null + statusMessage = "Verifying proof…" + Thread { try { proofBytes?.let { proof -> verificationKey?.let { vk -> - val onChain = true // Use Keccak for Solidity compatibility + val onChain = true val lowMemoryMode = false - val startTime = System.currentTimeMillis() val result = verifyNoirProof( circuitFile, @@ -159,85 +165,57 @@ fun NoirComponent() { lowMemoryMode ) val endTime = System.currentTimeMillis() - val duration = endTime - startTime - - verificationTime = "Verification time: $duration ms" - verificationResult = "Verification result: $result" - if (result) - statusMessage = "Proof verified successfully!" - else - statusMessage = "Proof verification failed!" + verificationTime = "${endTime - startTime} ms" + verificationResult = result.toString() + statusMessage = if (result) "Verified successfully" else "Verification failed" } ?: run { - verificationResult = "No verification key available" - statusMessage = "Please generate a proof first to get verification key" + verificationResult = "No verification key" + statusMessage = "Generate a proof first" } } ?: run { - verificationResult = "No proof available" - statusMessage = "Please generate a proof first" + verificationResult = "No proof" + statusMessage = "Generate a proof first" } } catch (e: Exception) { - verificationTime = "Verification failed" + verificationTime = "Failed" verificationResult = "Error: ${e.message}" - statusMessage = "Proof verification error" + statusMessage = "Verification error" e.printStackTrace() } finally { isVerifyingProof = false } - } - ).start() - }, - modifier = Modifier.padding(top = 20.dp).testTag("noirVerifyProofButton"), - enabled = !isGeneratingProof && !isVerifyingProof && proofBytes != null - ) { - Text(text = "Verify Proof") + }.start() + }, + modifier = Modifier.fillMaxWidth().testTag("noirVerifyProofButton"), + enabled = !isBusy && proofBytes != null + ) { Text("Verify proof") } } + } - Spacer(modifier = Modifier.height(40.dp)) - - // Results displayed in a more organized way - if (provingTime.isNotEmpty() || proofResult.isNotEmpty() || - verificationTime.isNotEmpty() || verificationResult.isNotEmpty()) { - - Text( - text = "Results", - fontWeight = FontWeight.Bold, - fontSize = 18.sp, - modifier = Modifier.padding(bottom = 8.dp) - ) - - if (provingTime.isNotEmpty()) { - Text( - text = provingTime, - modifier = Modifier.padding(top = 4.dp).width(280.dp), - textAlign = TextAlign.Center - ) - } - - if (proofResult.isNotEmpty()) { - Text( - text = proofResult, - modifier = Modifier.padding(top = 4.dp).width(280.dp), - textAlign = TextAlign.Center - ) - } - - if (verificationTime.isNotEmpty()) { - Text( - text = verificationTime, - modifier = Modifier.padding(top = 4.dp).width(280.dp), - textAlign = TextAlign.Center - ) - } - - if (verificationResult.isNotEmpty()) { - Text( - text = verificationResult, - modifier = Modifier.padding(top = 4.dp).width(280.dp), - textAlign = TextAlign.Center, - fontWeight = if (verificationResult.contains("true")) FontWeight.Bold else FontWeight.Normal - ) + if (provingTime != null || proofResult != null || verificationTime != null || verificationResult != null) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text("Results", fontWeight = FontWeight.SemiBold, fontSize = 16.sp) + provingTime?.let { Text("Proving: $it", style = MaterialTheme.typography.bodyMedium) } + proofResult?.let { Text(it, style = MaterialTheme.typography.bodyMedium) } + verificationTime?.let { Text("Verifying: $it", style = MaterialTheme.typography.bodyMedium) } + verificationResult?.let { + Text( + "Valid: $it", + style = MaterialTheme.typography.bodyMedium, + fontWeight = if (it == "true") FontWeight.Bold else FontWeight.Normal + ) + } } } } + + Spacer(modifier = Modifier.height(24.dp)) } -} \ No newline at end of file +} diff --git a/cli/src/template/ios/MoproApp.xcodeproj/project.pbxproj b/cli/src/template/ios/MoproApp.xcodeproj/project.pbxproj index 7bad10f95..a45776bf7 100644 --- a/cli/src/template/ios/MoproApp.xcodeproj/project.pbxproj +++ b/cli/src/template/ios/MoproApp.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 2A2376232F553BFF00D4B3B4 /* cubic_circuit.pk in Resources */ = {isa = PBXBuildFile; fileRef = 2A2376202F553BFF00D4B3B4 /* cubic_circuit.pk */; }; + 2A2376242F553BFF00D4B3B4 /* cubic_circuit.vk in Resources */ = {isa = PBXBuildFile; fileRef = 2A2376222F553BFF00D4B3B4 /* cubic_circuit.vk */; }; + 2A2376252F553BFF00D4B3B4 /* cubic_circuit.r1cs in Resources */ = {isa = PBXBuildFile; fileRef = 2A2376212F553BFF00D4B3B4 /* cubic_circuit.r1cs */; }; 2A4190692DF1CCE6004F1A4F /* noir_multiplier2.json in Resources */ = {isa = PBXBuildFile; fileRef = 2A4190682DF1CCE6004F1A4F /* noir_multiplier2.json */; }; 2A41906B2DF1CCED004F1A4F /* noir_multiplier2.srs in Resources */ = {isa = PBXBuildFile; fileRef = 2A41906A2DF1CCED004F1A4F /* noir_multiplier2.srs */; }; 2A59922F2CC7750200135402 /* multiplier2_final.zkey in Resources */ = {isa = PBXBuildFile; fileRef = 2A59922E2CC7750200135402 /* multiplier2_final.zkey */; }; @@ -39,6 +42,9 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 2A2376202F553BFF00D4B3B4 /* cubic_circuit.pk */ = {isa = PBXFileReference; lastKnownFileType = file; path = cubic_circuit.pk; sourceTree = ""; }; + 2A2376212F553BFF00D4B3B4 /* cubic_circuit.r1cs */ = {isa = PBXFileReference; lastKnownFileType = file; path = cubic_circuit.r1cs; sourceTree = ""; }; + 2A2376222F553BFF00D4B3B4 /* cubic_circuit.vk */ = {isa = PBXFileReference; lastKnownFileType = file; path = cubic_circuit.vk; sourceTree = ""; }; 2A4190682DF1CCE6004F1A4F /* noir_multiplier2.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = noir_multiplier2.json; sourceTree = ""; }; 2A41906A2DF1CCED004F1A4F /* noir_multiplier2.srs */ = {isa = PBXFileReference; lastKnownFileType = file; path = noir_multiplier2.srs; sourceTree = ""; }; 2A5991EC2CC7722900135402 /* MoproApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MoproApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -114,6 +120,9 @@ 2A41906A2DF1CCED004F1A4F /* noir_multiplier2.srs */, E11808952E5CBDB000B8B284 /* noir_multiplier2.vk */, 2A6489502E8BA1F1004450F0 /* multiplier2_wc_final.zkey */, + 2A2376202F553BFF00D4B3B4 /* cubic_circuit.pk */, + 2A2376212F553BFF00D4B3B4 /* cubic_circuit.r1cs */, + 2A2376222F553BFF00D4B3B4 /* cubic_circuit.vk */, ); sourceTree = ""; }; @@ -264,6 +273,9 @@ 2A59922F2CC7750200135402 /* multiplier2_final.zkey in Resources */, 2A6489512E8BA1F1004450F0 /* multiplier2_wc_final.zkey in Resources */, 2A4190692DF1CCE6004F1A4F /* noir_multiplier2.json in Resources */, + 2A2376232F553BFF00D4B3B4 /* cubic_circuit.pk in Resources */, + 2A2376242F553BFF00D4B3B4 /* cubic_circuit.vk in Resources */, + 2A2376252F553BFF00D4B3B4 /* cubic_circuit.r1cs in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/cli/src/template/ios/MoproApp/ContentView.swift b/cli/src/template/ios/MoproApp/ContentView.swift index ab510c367..2cac79f0f 100644 --- a/cli/src/template/ios/MoproApp/ContentView.swift +++ b/cli/src/template/ios/MoproApp/ContentView.swift @@ -5,360 +5,453 @@ import SwiftUI struct ContentView: View { - @State private var textViewText = "" - @State private var isCircomProveButtonEnabled = true - @State private var isCircomVerifyButtonEnabled = false - @State private var isRapidsnarkProveButtonEnabled = true - @State private var isRapidsnarkVerifyButtonEnabled = false - @State private var isHalo2ProveButtonEnabled = true - @State private var isHalo2VerifyButtonEnabled = false - @State private var isNoirProveButtonEnabled = true - @State private var isNoirVerifyButtonEnabled = false - @State private var generatedCircomProof: CircomProof? - @State private var circomPublicInputs: [String]? - @State private var generatedRapidsnarkProof: CircomProof? - @State private var rapidsnarkPublicInputs: [String]? - @State private var generatedHalo2Proof: Data? - @State private var halo2PublicInputs: Data? - @State private var generatedNoirProof: Data? - @State private var noirVerificationKey: Data? - private let zkeyPath = Bundle.main.path(forResource: "multiplier2_final", ofType: "zkey")! - private let witnesscalc_zkeyPath = Bundle.main.path(forResource: "multiplier2_wc_final", ofType: "zkey")! - private let srsPath = Bundle.main.path(forResource: "plonk_fibonacci_srs.bin", ofType: "")! - private let vkPath = Bundle.main.path(forResource: "plonk_fibonacci_vk.bin", ofType: "")! - private let pkPath = Bundle.main.path(forResource: "plonk_fibonacci_pk.bin", ofType: "")! - private let noirSrsPath = Bundle.main.path(forResource: "noir_multiplier2", ofType: "srs")! - private let noirCircuitPath = Bundle.main.path(forResource: "noir_multiplier2", ofType: "json")! - private let noirVkPath = Bundle.main.path(forResource: "noir_multiplier2", ofType: "vk")! - - var body: some View { - VStack(spacing: 10) { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Button("Prove Circom", action: runCircomProveAction).disabled(!isCircomProveButtonEnabled).accessibilityIdentifier("proveCircom") - Button("Verify Circom", action: runCircomVerifyAction).disabled(!isCircomVerifyButtonEnabled).accessibilityIdentifier("verifyCircom") - Button("Prove Circom (Rapidsnark)", action: runRapidsnarkProveAction).disabled(!isRapidsnarkProveButtonEnabled).accessibilityIdentifier("proveRapidsnark") - Button("Verify Circom (Rapidsnark)", action: runRapidsnarkVerifyAction).disabled(!isRapidsnarkVerifyButtonEnabled).accessibilityIdentifier("verifyRapidsnark") - Button("Prove Halo2", action: runHalo2ProveAction).disabled(!isHalo2ProveButtonEnabled).accessibilityIdentifier("proveHalo2") - Button("Verify Halo2", action: runHalo2VerifyAction).disabled(!isHalo2VerifyButtonEnabled).accessibilityIdentifier("verifyHalo2") - Button("Prove Noir", action: runNoirProveAction).disabled(!isNoirProveButtonEnabled).accessibilityIdentifier("proveNoir") - Button("Verify Noir", action: runNoirVerifyAction).disabled(!isNoirVerifyButtonEnabled).accessibilityIdentifier("verifyNoir") - - - ScrollView { - Text(textViewText) - .padding() - .accessibilityIdentifier("proof_log") - } - .frame(height: 200) - } - .padding() + @State private var textViewText = "" + @State private var isCircomProveButtonEnabled = true + @State private var isCircomVerifyButtonEnabled = false + @State private var isRapidsnarkProveButtonEnabled = true + @State private var isRapidsnarkVerifyButtonEnabled = false + @State private var isGnarkProveButtonEnabled = true + @State private var isGnarkVerifyButtonEnabled = false + @State private var isHalo2ProveButtonEnabled = true + @State private var isHalo2VerifyButtonEnabled = false + @State private var isNoirProveButtonEnabled = true + @State private var isNoirVerifyButtonEnabled = false + @State private var generatedCircomProof: CircomProof? + @State private var circomPublicInputs: [String]? + @State private var generatedRapidsnarkProof: CircomProof? + @State private var rapidsnarkPublicInputs: [String]? + @State private var generatedGnarkProof: String? + @State private var gnarkPublicInputs: String? + @State private var generatedHalo2Proof: Data? + @State private var halo2PublicInputs: Data? + @State private var generatedNoirProof: Data? + @State private var noirVerificationKey: Data? + private let zkeyPath = Bundle.main.path(forResource: "multiplier2_final", ofType: "zkey")! + private let witnesscalc_zkeyPath = Bundle.main.path( + forResource: "multiplier2_wc_final", ofType: "zkey")! + private let srsPath = Bundle.main.path(forResource: "plonk_fibonacci_srs.bin", ofType: "")! + private let vkPath = Bundle.main.path(forResource: "plonk_fibonacci_vk.bin", ofType: "")! + private let pkPath = Bundle.main.path(forResource: "plonk_fibonacci_pk.bin", ofType: "")! + private let noirSrsPath = Bundle.main.path(forResource: "noir_multiplier2", ofType: "srs")! + private let noirCircuitPath = Bundle.main.path(forResource: "noir_multiplier2", ofType: "json")! + private let noirVkPath = Bundle.main.path(forResource: "noir_multiplier2", ofType: "vk")! + private let gnarkR1csPath = Bundle.main.path(forResource: "cubic_circuit", ofType: "r1cs")! + private let gnarkPkPath = Bundle.main.path(forResource: "cubic_circuit", ofType: "pk")! + private let gnarkVkPath = Bundle.main.path(forResource: "cubic_circuit", ofType: "vk")! + + var body: some View { + VStack(spacing: 10) { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Button("Prove Circom", action: runCircomProveAction).disabled(!isCircomProveButtonEnabled) + .accessibilityIdentifier("proveCircom") + Button("Verify Circom", action: runCircomVerifyAction).disabled(!isCircomVerifyButtonEnabled) + .accessibilityIdentifier("verifyCircom") + Button("Prove Circom (Rapidsnark)", action: runRapidsnarkProveAction).disabled( + !isRapidsnarkProveButtonEnabled + ).accessibilityIdentifier("proveRapidsnark") + Button("Verify Circom (Rapidsnark)", action: runRapidsnarkVerifyAction).disabled( + !isRapidsnarkVerifyButtonEnabled + ).accessibilityIdentifier("verifyRapidsnark") + Button("Prove Gnark", action: runGnarkProveAction).disabled(!isGnarkProveButtonEnabled) + .accessibilityIdentifier("proveGnark") + Button("Verify Gnark", action: runGnarkVerifyAction).disabled(!isGnarkVerifyButtonEnabled) + .accessibilityIdentifier("verifyGnark") + Button("Prove Halo2", action: runHalo2ProveAction).disabled(!isHalo2ProveButtonEnabled) + .accessibilityIdentifier("proveHalo2") + Button("Verify Halo2", action: runHalo2VerifyAction).disabled(!isHalo2VerifyButtonEnabled) + .accessibilityIdentifier("verifyHalo2") + Button("Prove Noir", action: runNoirProveAction).disabled(!isNoirProveButtonEnabled) + .accessibilityIdentifier("proveNoir") + Button("Verify Noir", action: runNoirVerifyAction).disabled(!isNoirVerifyButtonEnabled) + .accessibilityIdentifier("verifyNoir") + + ScrollView { + Text(textViewText) + .padding() + .accessibilityIdentifier("proof_log") + } + .frame(height: 200) } + .padding() + } } extension ContentView { - func runCircomProveAction() { - textViewText += "Generating Circom proof... " - do { - // Prepare inputs - let a = 3 - let b = 5 - let c = a*b - let input_str: String = "{\"b\":[\"5\"],\"a\":[\"3\"]}" - - // Expected outputs - let outputs: [String] = [String(c), String(a)] - - let start = CFAbsoluteTimeGetCurrent() - - // Generate Proof - let generateProofResult = try generateCircomProof(zkeyPath: zkeyPath, circuitInputs: input_str, proofLib: ProofLib.arkworks) - assert(!generateProofResult.proof.a.x.isEmpty, "Proof should not be empty") - assert(outputs == generateProofResult.inputs, "Circuit outputs mismatch the expected outputs") - - let end = CFAbsoluteTimeGetCurrent() - let timeTaken = end - start - - // Store the generated proof and public inputs for later verification - generatedCircomProof = generateProofResult.proof - circomPublicInputs = generateProofResult.inputs - - textViewText += "\(String(format: "%.3f", timeTaken))s 1️⃣\n" - - isCircomVerifyButtonEnabled = true - } catch { - textViewText += "\nProof generation failed: \(error.localizedDescription)\n" - } + func runCircomProveAction() { + textViewText += "Generating Circom proof... " + do { + // Prepare inputs + let a = 3 + let b = 5 + let c = a * b + let input_str: String = "{\"b\":[\"5\"],\"a\":[\"3\"]}" + + // Expected outputs + let outputs: [String] = [String(c), String(a)] + + let start = CFAbsoluteTimeGetCurrent() + + // Generate Proof + let generateProofResult = try generateCircomProof( + zkeyPath: zkeyPath, circuitInputs: input_str, proofLib: ProofLib.arkworks) + assert(!generateProofResult.proof.a.x.isEmpty, "Proof should not be empty") + assert(outputs == generateProofResult.inputs, "Circuit outputs mismatch the expected outputs") + + let end = CFAbsoluteTimeGetCurrent() + let timeTaken = end - start + + // Store the generated proof and public inputs for later verification + generatedCircomProof = generateProofResult.proof + circomPublicInputs = generateProofResult.inputs + + textViewText += "\(String(format: "%.3f", timeTaken))s 1️⃣\n" + + isCircomVerifyButtonEnabled = true + } catch { + textViewText += "\nProof generation failed: \(error.localizedDescription)\n" } - - func runCircomVerifyAction() { - guard let proof = generatedCircomProof, - let inputs = circomPublicInputs else { - textViewText += "Proof has not been generated yet.\n" - return - } - - textViewText += "Verifying Circom proof... " - do { - let start = CFAbsoluteTimeGetCurrent() - - let isValid = try verifyCircomProof(zkeyPath: zkeyPath, proofResult: CircomProofResult(proof: proof, inputs: inputs), proofLib: ProofLib.arkworks) - let end = CFAbsoluteTimeGetCurrent() - let timeTaken = end - start - - assert(proof.a.x.count > 0, "Proof should not be empty") - assert(inputs.count > 0, "Inputs should not be empty") - - print("Ethereum Proof: \(proof)\n") - print("Ethereum Inputs: \(inputs)\n") - - if isValid { - textViewText += "\(String(format: "%.3f", timeTaken))s 2️⃣\n" - } else { - textViewText += "\nProof verification failed.\n" - } - isCircomVerifyButtonEnabled = false - } catch let error as MoproError { - print("\nMoproError: \(error)") - } catch { - print("\nUnexpected error: \(error)") - } + } + + func runCircomVerifyAction() { + guard let proof = generatedCircomProof, + let inputs = circomPublicInputs + else { + textViewText += "Proof has not been generated yet.\n" + return } - - func runRapidsnarkProveAction() { - textViewText += "Generating Circom Rapidsnark proof... " - do { - // Prepare inputs - let a = 3 - let b = 5 - let c = a*b - let input_str: String = "{\"b\":[\"5\"],\"a\":[\"3\"]}" - - // Expected outputs - let outputs: [String] = [String(c), String(a)] - - let start = CFAbsoluteTimeGetCurrent() - - // Generate Proof - let generateProofResult = try generateCircomProof(zkeyPath: witnesscalc_zkeyPath, circuitInputs: input_str, proofLib: ProofLib.rapidsnark) - assert(!generateProofResult.proof.a.x.isEmpty, "Proof should not be empty") - assert(outputs == generateProofResult.inputs, "Circuit outputs mismatch the expected outputs") - - let end = CFAbsoluteTimeGetCurrent() - let timeTaken = end - start - - // Store the generated proof and public inputs for later verification - generatedCircomProof = generateProofResult.proof - circomPublicInputs = generateProofResult.inputs - - textViewText += "\(String(format: "%.3f", timeTaken))s 1️⃣\n" - - isRapidsnarkVerifyButtonEnabled = true - } catch { - textViewText += "\nProof generation failed: \(error.localizedDescription)\n" - } + + textViewText += "Verifying Circom proof... " + do { + let start = CFAbsoluteTimeGetCurrent() + + let isValid = try verifyCircomProof( + zkeyPath: zkeyPath, proofResult: CircomProofResult(proof: proof, inputs: inputs), + proofLib: ProofLib.arkworks) + let end = CFAbsoluteTimeGetCurrent() + let timeTaken = end - start + + assert(proof.a.x.count > 0, "Proof should not be empty") + assert(inputs.count > 0, "Inputs should not be empty") + + print("Ethereum Proof: \(proof)\n") + print("Ethereum Inputs: \(inputs)\n") + + if isValid { + textViewText += "\(String(format: "%.3f", timeTaken))s 2️⃣\n" + } else { + textViewText += "\nProof verification failed.\n" + } + isCircomVerifyButtonEnabled = false + } catch let error as MoproError { + print("\nMoproError: \(error)") + } catch { + print("\nUnexpected error: \(error)") } - - func runRapidsnarkVerifyAction() { - guard let proof = generatedCircomProof, - let inputs = circomPublicInputs else { - textViewText += "Proof has not been generated yet.\n" - return - } - - textViewText += "Verifying Circom Rapidsnark proof... " - do { - let start = CFAbsoluteTimeGetCurrent() - - let isValid = try verifyCircomProof(zkeyPath: witnesscalc_zkeyPath, proofResult: CircomProofResult(proof: proof, inputs: inputs), proofLib: ProofLib.rapidsnark) - let end = CFAbsoluteTimeGetCurrent() - let timeTaken = end - start - - assert(proof.a.x.count > 0, "Proof should not be empty") - assert(inputs.count > 0, "Inputs should not be empty") - - print("Ethereum Proof: \(proof)\n") - print("Ethereum Inputs: \(inputs)\n") - - if isValid { - textViewText += "\(String(format: "%.3f", timeTaken))s 2️⃣\n" - } else { - textViewText += "\nProof verification failed.\n" - } - isCircomVerifyButtonEnabled = false - } catch let error as MoproError { - print("\nMoproError: \(error)") - } catch { - print("\nUnexpected error: \(error)") - } + } + + func runRapidsnarkProveAction() { + textViewText += "Generating Circom Rapidsnark proof... " + do { + // Prepare inputs + let a = 3 + let b = 5 + let c = a * b + let input_str: String = "{\"b\":[\"5\"],\"a\":[\"3\"]}" + + // Expected outputs + let outputs: [String] = [String(c), String(a)] + + let start = CFAbsoluteTimeGetCurrent() + + // Generate Proof + let generateProofResult = try generateCircomProof( + zkeyPath: witnesscalc_zkeyPath, circuitInputs: input_str, proofLib: ProofLib.rapidsnark) + assert(!generateProofResult.proof.a.x.isEmpty, "Proof should not be empty") + assert(outputs == generateProofResult.inputs, "Circuit outputs mismatch the expected outputs") + + let end = CFAbsoluteTimeGetCurrent() + let timeTaken = end - start + + // Store the generated proof and public inputs for later verification + generatedCircomProof = generateProofResult.proof + circomPublicInputs = generateProofResult.inputs + + textViewText += "\(String(format: "%.3f", timeTaken))s 1️⃣\n" + + isRapidsnarkVerifyButtonEnabled = true + } catch { + textViewText += "\nProof generation failed: \(error.localizedDescription)\n" } - - func runHalo2ProveAction() { - textViewText += "Generating Halo2 proof... " - do { - // Prepare inputs - var inputs = [String: [String]]() - let out = 55 - inputs["out"] = [String(out)] - - let start = CFAbsoluteTimeGetCurrent() - - // Generate Proof - let generateProofResult = try generateHalo2Proof(srsPath: srsPath, pkPath: pkPath, circuitInputs: inputs) - assert(!generateProofResult.proof.isEmpty, "Proof should not be empty") - assert(!generateProofResult.inputs.isEmpty, "Inputs should not be empty") - - - let end = CFAbsoluteTimeGetCurrent() - let timeTaken = end - start - - // Store the generated proof and public inputs for later verification - generatedHalo2Proof = generateProofResult.proof - halo2PublicInputs = generateProofResult.inputs - - textViewText += "\(String(format: "%.3f", timeTaken))s 1️⃣\n" - - isHalo2VerifyButtonEnabled = true - } catch { - textViewText += "\nProof generation failed: \(error.localizedDescription)\n" - } + } + + func runRapidsnarkVerifyAction() { + guard let proof = generatedCircomProof, + let inputs = circomPublicInputs + else { + textViewText += "Proof has not been generated yet.\n" + return } - - func runHalo2VerifyAction() { - guard let proof = generatedHalo2Proof, - let inputs = halo2PublicInputs else { - textViewText += "Proof has not been generated yet.\n" - return - } - - textViewText += "Verifying Halo2 proof... " - do { - let start = CFAbsoluteTimeGetCurrent() - - let isValid = try verifyHalo2Proof( - srsPath: srsPath, vkPath: vkPath, proof: proof, publicInput: inputs) - let end = CFAbsoluteTimeGetCurrent() - let timeTaken = end - start - - - if isValid { - textViewText += "\(String(format: "%.3f", timeTaken))s 2️⃣\n" - } else { - textViewText += "\nProof verification failed.\n" - } - isHalo2VerifyButtonEnabled = false - } catch let error as MoproError { - print("\nMoproError: \(error)") - } catch { - print("\nUnexpected error: \(error)") - } + + textViewText += "Verifying Circom Rapidsnark proof... " + do { + let start = CFAbsoluteTimeGetCurrent() + + let isValid = try verifyCircomProof( + zkeyPath: witnesscalc_zkeyPath, + proofResult: CircomProofResult(proof: proof, inputs: inputs), proofLib: ProofLib.rapidsnark) + let end = CFAbsoluteTimeGetCurrent() + let timeTaken = end - start + + assert(proof.a.x.count > 0, "Proof should not be empty") + assert(inputs.count > 0, "Inputs should not be empty") + + print("Ethereum Proof: \(proof)\n") + print("Ethereum Inputs: \(inputs)\n") + + if isValid { + textViewText += "\(String(format: "%.3f", timeTaken))s 2️⃣\n" + } else { + textViewText += "\nProof verification failed.\n" + } + isCircomVerifyButtonEnabled = false + } catch let error as MoproError { + print("\nMoproError: \(error)") + } catch { + print("\nUnexpected error: \(error)") + } + } + + func runGnarkProveAction() { + textViewText += "Generating Gnark proof... " + do { + // Prepare inputs + let witnessJson = "{\"X\": \"3\", \"Y\": \"35\"}" + + let start = CFAbsoluteTimeGetCurrent() + + // Generate Proof + let generateProofResult = try generateGnarkProof( + r1csPath: gnarkR1csPath, pkPath: gnarkPkPath, witnessJson: witnessJson) + assert(!generateProofResult.proof.isEmpty, "Proof should not be empty") + assert(!generateProofResult.publicInputs.isEmpty, "Public inputs should not be empty") + + let end = CFAbsoluteTimeGetCurrent() + let timeTaken = end - start + + // Store the generated proof and public inputs for later verification + generatedGnarkProof = generateProofResult.proof + gnarkPublicInputs = generateProofResult.publicInputs + + textViewText += "\(String(format: "%.3f", timeTaken))s 1️⃣\n" + + isGnarkVerifyButtonEnabled = true + } catch { + textViewText += "\nProof generation failed: \(error.localizedDescription)\n" + } + } + + func runGnarkVerifyAction() { + guard let proof = generatedGnarkProof, + let publicInputs = gnarkPublicInputs + else { + textViewText += "Proof has not been generated yet.\n" + return + } + + textViewText += "Verifying Gnark proof... " + do { + let start = CFAbsoluteTimeGetCurrent() + + let isValid = try verifyGnarkProof( + r1csPath: gnarkR1csPath, vkPath: gnarkVkPath, + proofResult: GnarkProofResult(proof: proof, publicInputs: publicInputs)) + let end = CFAbsoluteTimeGetCurrent() + let timeTaken = end - start + + assert(isValid, "Proof verification should succeed") + + if isValid { + textViewText += "\(String(format: "%.3f", timeTaken))s 2️⃣\n" + } else { + textViewText += "\nProof verification failed.\n" + } + isGnarkVerifyButtonEnabled = false + } catch let error as MoproError { + print("\nMoproError: \(error)") + } catch { + print("\nUnexpected error: \(error)") + } + } + + func runHalo2ProveAction() { + textViewText += "Generating Halo2 proof... " + do { + // Prepare inputs + var inputs = [String: [String]]() + let out = 55 + inputs["out"] = [String(out)] + + let start = CFAbsoluteTimeGetCurrent() + + // Generate Proof + let generateProofResult = try generateHalo2Proof( + srsPath: srsPath, pkPath: pkPath, circuitInputs: inputs) + assert(!generateProofResult.proof.isEmpty, "Proof should not be empty") + assert(!generateProofResult.inputs.isEmpty, "Inputs should not be empty") + + let end = CFAbsoluteTimeGetCurrent() + let timeTaken = end - start + + // Store the generated proof and public inputs for later verification + generatedHalo2Proof = generateProofResult.proof + halo2PublicInputs = generateProofResult.inputs + + textViewText += "\(String(format: "%.3f", timeTaken))s 1️⃣\n" + + isHalo2VerifyButtonEnabled = true + } catch { + textViewText += "\nProof generation failed: \(error.localizedDescription)\n" } - - func runNoirProveAction() { - textViewText += "Generating Noir proof...\n" + } + func runHalo2VerifyAction() { + guard let proof = generatedHalo2Proof, + let inputs = halo2PublicInputs + else { + textViewText += "Proof has not been generated yet.\n" + return + } + + textViewText += "Verifying Halo2 proof... " + do { + let start = CFAbsoluteTimeGetCurrent() + + let isValid = try verifyHalo2Proof( + srsPath: srsPath, vkPath: vkPath, proof: proof, publicInput: inputs) + let end = CFAbsoluteTimeGetCurrent() + let timeTaken = end - start + + if isValid { + textViewText += "\(String(format: "%.3f", timeTaken))s 2️⃣\n" + } else { + textViewText += "\nProof verification failed.\n" + } + isHalo2VerifyButtonEnabled = false + } catch let error as MoproError { + print("\nMoproError: \(error)") + } catch { + print("\nUnexpected error: \(error)") + } + } + + func runNoirProveAction() { + textViewText += "Generating Noir proof...\n" + + do { + let inputs: [String] = ["3", "5"] + let onChain = true // Use Keccak for Solidity compatibility + let lowMemoryMode = false + + DispatchQueue.global(qos: .userInitiated).async { do { - let inputs: [String] = ["3", "5"] - let onChain = true // Use Keccak for Solidity compatibility - let lowMemoryMode = false - - DispatchQueue.global(qos: .userInitiated).async { - do { - // First, try to load existing verification key from file, or generate new one - let vk: Data - if let existingVkData = try? Data(contentsOf: URL(fileURLWithPath: noirVkPath)) { - vk = existingVkData - DispatchQueue.main.async { - textViewText += "Using existing verification key...\n" - } - } else { - DispatchQueue.main.async { - textViewText += "Generating verification key...\n" - } - vk = try getNoirVerificationKey(circuitPath: noirCircuitPath, srsPath: noirSrsPath, onChain: onChain, lowMemoryMode: lowMemoryMode) - } - noirVerificationKey = vk - - DispatchQueue.main.async { - textViewText += "Generating proof with verification key...\n" - } - let start = CFAbsoluteTimeGetCurrent() - - // Generate the proof with all required parameters - let proofData = try generateNoirProof( - circuitPath: noirCircuitPath, - srsPath: noirSrsPath, - inputs: inputs, - onChain: onChain, - vk: vk, - lowMemoryMode: lowMemoryMode - ) - - let end = CFAbsoluteTimeGetCurrent() - let timeTaken = end - start - - DispatchQueue.main.async { - generatedNoirProof = proofData - textViewText += "\(String(format: "%.3f", timeTaken))s 1️⃣\n" - isNoirVerifyButtonEnabled = true - } - } catch { - DispatchQueue.main.async { - textViewText += "Proof generation failed: \(error.localizedDescription)\n" - } - } + // First, try to load existing verification key from file, or generate new one + let vk: Data + if let existingVkData = try? Data(contentsOf: URL(fileURLWithPath: noirVkPath)) { + vk = existingVkData + DispatchQueue.main.async { + textViewText += "Using existing verification key...\n" + } + } else { + DispatchQueue.main.async { + textViewText += "Generating verification key...\n" } + vk = try getNoirVerificationKey( + circuitPath: noirCircuitPath, srsPath: noirSrsPath, onChain: onChain, + lowMemoryMode: lowMemoryMode) + } + noirVerificationKey = vk + + DispatchQueue.main.async { + textViewText += "Generating proof with verification key...\n" + } + let start = CFAbsoluteTimeGetCurrent() + + // Generate the proof with all required parameters + let proofData = try generateNoirProof( + circuitPath: noirCircuitPath, + srsPath: noirSrsPath, + inputs: inputs, + onChain: onChain, + vk: vk, + lowMemoryMode: lowMemoryMode + ) + let end = CFAbsoluteTimeGetCurrent() + let timeTaken = end - start + + DispatchQueue.main.async { + generatedNoirProof = proofData + textViewText += "\(String(format: "%.3f", timeTaken))s 1️⃣\n" + isNoirVerifyButtonEnabled = true + } } catch { - textViewText += "Error setting up proof generation: \(error.localizedDescription)\n" + DispatchQueue.main.async { + textViewText += "Proof generation failed: \(error.localizedDescription)\n" + } } + } + + } catch { + textViewText += "Error setting up proof generation: \(error.localizedDescription)\n" } - - func runNoirVerifyAction() { - guard let proofData = generatedNoirProof else { - textViewText += "Error: Proof data is not available. Generate proof first.\n" - return - } - - guard let vk = noirVerificationKey else { - textViewText += "Error: Verification key is not available. Generate proof first.\n" - return - } + } - textViewText += "Verifying Noir proof...\n" - - DispatchQueue.global(qos: .userInitiated).async { - let start = CFAbsoluteTimeGetCurrent() - do { - let onChain = true // Use Keccak for Solidity compatibility - let lowMemoryMode = false - - // Verify the proof with all required parameters - let isValid = try verifyNoirProof( - circuitPath: noirCircuitPath, - proof: proofData, - onChain: onChain, - vk: vk, - lowMemoryMode: lowMemoryMode - ) - - let end = CFAbsoluteTimeGetCurrent() - let timeTaken = end - start - - DispatchQueue.main.async { - if isValid { - textViewText += "\(String(format: "%.3f", timeTaken))s 2️⃣\n" - } else { - textViewText += "\nProof verification failed.\n" - } - isNoirVerifyButtonEnabled = false - } - } catch { - DispatchQueue.main.async { - textViewText += "Verification failed: \(error.localizedDescription)\n" - } - } + func runNoirVerifyAction() { + guard let proofData = generatedNoirProof else { + textViewText += "Error: Proof data is not available. Generate proof first.\n" + return + } + + guard let vk = noirVerificationKey else { + textViewText += "Error: Verification key is not available. Generate proof first.\n" + return + } + + textViewText += "Verifying Noir proof...\n" + + DispatchQueue.global(qos: .userInitiated).async { + let start = CFAbsoluteTimeGetCurrent() + do { + let onChain = true // Use Keccak for Solidity compatibility + let lowMemoryMode = false + + // Verify the proof with all required parameters + let isValid = try verifyNoirProof( + circuitPath: noirCircuitPath, + proof: proofData, + onChain: onChain, + vk: vk, + lowMemoryMode: lowMemoryMode + ) + + let end = CFAbsoluteTimeGetCurrent() + let timeTaken = end - start + + DispatchQueue.main.async { + if isValid { + textViewText += "\(String(format: "%.3f", timeTaken))s 2️⃣\n" + } else { + textViewText += "\nProof verification failed.\n" + } + isNoirVerifyButtonEnabled = false + } + } catch { + DispatchQueue.main.async { + textViewText += "Verification failed: \(error.localizedDescription)\n" } + } } -} \ No newline at end of file + } +} diff --git a/cli/src/template/ios/MoproAppUITests/MoproAppUITests.swift b/cli/src/template/ios/MoproAppUITests/MoproAppUITests.swift index ab91743af..ab1a4ac09 100644 --- a/cli/src/template/ios/MoproAppUITests/MoproAppUITests.swift +++ b/cli/src/template/ios/MoproAppUITests/MoproAppUITests.swift @@ -47,6 +47,20 @@ final class MoproAppUITests: XCTestCase { let verifyText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] %@", "2️⃣")).firstMatch XCTAssertTrue(verifyText.waitForExistence(timeout: 5), "The time of proof verification is over 5 secs") } + + func testGnarkProveVerify() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + app.buttons["proveGnark"].tap() + let proveText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] %@", "1️⃣")).firstMatch + XCTAssertTrue(proveText.waitForExistence(timeout: 5), "The time of proof generation is over 5 secs") + + app.buttons["verifyGnark"].tap() + let verifyText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] %@", "2️⃣")).firstMatch + XCTAssertTrue(verifyText.waitForExistence(timeout: 5), "The time of proof verification is over 5 secs") + } func testHalo2ProveVerify() throws { // UI tests must launch the application that they test.