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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Secrets — never commit real credentials
.env
OpenACExampleApp/Secrets.swift

# Xcode
xcuserdata/
*.xccheckout
*.moved-aside
DerivedData/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
*.hmap
*.ipa

# SPM
.build/
.swiftpm/

# macOS
.DS_Store
77 changes: 77 additions & 0 deletions Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Required bundle keys -->
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleDisplayName</key>
<string>OpenACExampleApp</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>

<!-- Custom URL scheme so MOICA can redirect back to this app -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>mopro.OpenACExampleApp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>openac</string>
</array>
</dict>
</array>

<!-- Allow opening the MOICA app via its URL scheme -->
<key>LSApplicationQueriesSchemes</key>
<array>
<string>mobilemoica</string>
</array>

<!-- Scene manifest -->
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict/>
</dict>

<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>

<!-- Launch screen -->
<key>UILaunchScreen</key>
<dict/>

<!-- Supported orientations – iPhone -->
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>

<!-- Supported orientations – iPad -->
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
18 changes: 6 additions & 12 deletions OpenACExampleApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -408,13 +408,10 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = N3ESL929H7;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -440,13 +437,10 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = N3ESL929H7;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down
224 changes: 220 additions & 4 deletions OpenACExampleApp/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,31 @@
import SwiftUI

struct ContentView: View {
@State private var vm = ProofViewModel()
@Bindable var vm: ProofViewModel

private var spTicketSymbol: String {
switch vm.spTicketStatus {
case .idle, .running: return "ticket"
case .success: return "checkmark.circle.fill"
case .failure: return "xmark.circle.fill"
}
}

private var athResultSymbol: String {
switch vm.athResultStatus {
case .idle, .running: return "checkmark.shield"
case .success: return "checkmark.shield.fill"
case .failure: return "xmark.shield.fill"
}
}

private func spTicketColor(_ status: ProofViewModel.StepStatus) -> Color {
switch status {
case .idle, .running: return .secondary
case .success: return .green
case .failure: return .red
}
}

var body: some View {
NavigationStack {
Expand All @@ -22,13 +46,205 @@ struct ContentView: View {
}
}

// ── SP Ticket / MOICA ─────────────────────────────────
Section {
// Row 0 – ID number input
HStack {
Text("ID Number")
.font(.subheadline)
SecureField("e.g. A123456789", text: $vm.idNum)
.textFieldStyle(.roundedBorder)
}

// Row 1 – fetch ticket
HStack(spacing: 16) {
Image(systemName: spTicketSymbol)
.font(.title2)
.foregroundStyle(spTicketColor(vm.spTicketStatus))
.frame(width: 32)

VStack(alignment: .leading, spacing: 2) {
Text("Get SP Ticket").font(.headline)
Text("POST /fido/sp-ticket").font(.caption).foregroundStyle(.secondary)

if case .success(let detail) = vm.spTicketStatus {
Text(detail)
.font(.caption.monospacedDigit())
.foregroundStyle(.green)
.padding(.top, 2)
if let ticket = vm.spTicket {
Text(ticket)
.font(.caption.monospaced())
.foregroundStyle(.secondary)
.lineLimit(3)
.truncationMode(.middle)
.textSelection(.enabled)
.padding(.top, 1)
}
}
if case .failure(let msg) = vm.spTicketStatus {
Text(msg)
.font(.caption)
.foregroundStyle(.red)
.padding(.top, 2)
}
}

Spacer()

if case .running = vm.spTicketStatus {
ProgressView().controlSize(.small)
} else {
Button {
Task { await vm.computeSPTicket() }
} label: {
Image(systemName: "arrow.down.circle")
}
.buttonStyle(.bordered)
.controlSize(.small)
}
}
.padding(.vertical, 4)
.animation(.default, value: vm.spTicketStatus)

// Row 2 – open MOICA (visible once ticket is ready)
if vm.spTicket != nil {
HStack(spacing: 16) {
Image(systemName: "person.badge.key.fill")
.font(.title2)
.foregroundStyle(.blue)
.frame(width: 32)

VStack(alignment: .leading, spacing: 2) {
Text("Verify with MOICA").font(.headline)
Text("Opens mobilemoica:// · returns to openac://callback")
.font(.caption).foregroundStyle(.secondary)

if let val = vm.rtnVal {
Text("rtn_val: \(val)")
.font(.caption.monospacedDigit())
.foregroundStyle(.green)
.padding(.top, 2)
}
}

Spacer()

Button {
vm.openMOICA()
} label: {
Image(systemName: "arrow.up.forward.app")
}
.buttonStyle(.borderedProminent)
.controlSize(.small)
}
.padding(.vertical, 4)
}

// Row 3 – ath-result
if vm.spTicket != nil {
HStack(spacing: 16) {
Image(systemName: athResultSymbol)
.font(.title2)
.foregroundStyle(spTicketColor(vm.athResultStatus))
.frame(width: 32)

VStack(alignment: .leading, spacing: 2) {
Text("Auth Result").font(.headline)
Text("POST /fido/ath-result").font(.caption).foregroundStyle(.secondary)

if case .success(let detail) = vm.athResultStatus {
Text(detail)
.font(.caption.monospacedDigit())
.foregroundStyle(.green)
.padding(.top, 2)
}
if case .failure(let msg) = vm.athResultStatus {
Text(msg)
.font(.caption)
.foregroundStyle(.red)
.padding(.top, 2)
}
}

Spacer()

if case .running = vm.athResultStatus {
ProgressView().controlSize(.small)
} else {
Button {
Task { await vm.pollAthResult() }
} label: {
Image(systemName: "checkmark.shield")
}
.buttonStyle(.bordered)
.controlSize(.small)
}
}
.padding(.vertical, 4)
.animation(.default, value: vm.athResultStatus)
}

// Row 4 – generate input.json
HStack(spacing: 16) {
Image(systemName: {
switch vm.generateInputStatus {
case .idle, .running: return "doc.badge.gearshape"
case .success: return "checkmark.circle.fill"
case .failure: return "xmark.circle.fill"
}
}())
.font(.title2)
.foregroundStyle(spTicketColor(vm.generateInputStatus))
.frame(width: 32)

VStack(alignment: .leading, spacing: 2) {
Text("Generate Input").font(.headline)
Text("Build circuit input from MOICA response").font(.caption).foregroundStyle(.secondary)

if case .success(let detail) = vm.generateInputStatus {
Text(detail)
.font(.caption.monospacedDigit())
.foregroundStyle(.green)
.lineLimit(2)
.truncationMode(.middle)
.padding(.top, 2)
}
if case .failure(let msg) = vm.generateInputStatus {
Text(msg)
.font(.caption)
.foregroundStyle(.red)
.padding(.top, 2)
}
}

Spacer()

if case .running = vm.generateInputStatus {
ProgressView().controlSize(.small)
} else {
Button {
Task { await vm.runGenerateInput() }
} label: {
Image(systemName: "arrow.trianglehead.2.clockwise")
}
.buttonStyle(.bordered)
.controlSize(.small)
}
}
.padding(.vertical, 4)
.animation(.default, value: vm.generateInputStatus)
} header: {
Text("FIDO / MOICA")
}

// ── Pipeline Steps ─────────────────────────────────────
Section {
StepRow(index: 1, title: "Setup Keys",
subtitle: "Generate proving & verifying keys",
status: vm.setupStatus)
StepRow(index: 2, title: "Generate Proof",
subtitle: "Prove the RS256 circuit",
subtitle: "Prove the sha256rsa4096 circuit",
status: vm.proveStatus)
StepRow(index: 3, title: "Verify",
subtitle: "Check the proof is valid",
Expand Down Expand Up @@ -69,7 +285,7 @@ private struct CircuitDownloadCard: View {
.font(.headline)
.foregroundStyle(.blue)

Text("rs256.r1cs (~749 MB) must be downloaded before running the pipeline.")
Text("sha256rsa4096.r1cs (~97.43 MB) must be downloaded before running the pipeline.")
.font(.subheadline)
.foregroundStyle(.secondary)

Expand Down Expand Up @@ -190,5 +406,5 @@ private struct IndexBadge: View {
}

#Preview {
ContentView()
ContentView(vm: ProofViewModel())
}
Binary file added OpenACExampleApp/MOICA-G3.cer
Binary file not shown.
Loading