@baerae/zkap-zkp-react-native 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # `@baerae/zkap-zkp-react-native`
2
+
3
+ React Native SDK for zkap-zkp with Expo modules integration.
4
+
5
+ Install:
6
+
7
+ ```bash
8
+ npm install @baerae/zkap-zkp-react-native
9
+ ```
10
+
11
+ Requires Expo New Architecture and the following peer dependencies:
12
+
13
+ - `expo`
14
+ - `expo-crypto`
15
+ - `expo-file-system`
16
+ - `expo-modules-core`
17
+ - `react-native`
18
+
19
+ ```ts
20
+ import { generateHash, generateAnchor } from '@baerae/zkap-zkp-react-native'
21
+
22
+ const hash = await generateHash(['0x1', '0x2'])
23
+ const anchor = await generateAnchor(config, secrets)
24
+ ```
25
+
26
+ The npm package bundles the iOS XCFramework and Android `.so` libraries produced by CI.
@@ -0,0 +1,30 @@
1
+ apply plugin: 'com.android.library'
2
+ apply plugin: 'kotlin-android'
3
+
4
+ android {
5
+ compileSdkVersion 34
6
+ namespace "expo.modules.zkap"
7
+
8
+ defaultConfig {
9
+ minSdkVersion 24
10
+ targetSdkVersion 34
11
+ }
12
+
13
+ sourceSets {
14
+ main {
15
+ java.srcDirs += ['src/main/java']
16
+ jniLibs.srcDirs = ['libs']
17
+ }
18
+ }
19
+ }
20
+
21
+ repositories {
22
+ mavenCentral()
23
+ }
24
+
25
+ dependencies {
26
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0"
27
+ // expo-modules-core provides Module, ModuleDefinition, AsyncFunction, etc.
28
+ // compileOnly: provided by the consumer app at runtime
29
+ compileOnly project(':expo-modules-core')
30
+ }
@@ -0,0 +1,64 @@
1
+ package expo.modules.zkap
2
+
3
+ import expo.modules.kotlin.modules.Module
4
+ import expo.modules.kotlin.modules.ModuleDefinition
5
+
6
+ class ZkapSdkModule : Module() {
7
+
8
+ // Load the native Rust library (built as libzkap_zkp_rn.so)
9
+ companion object {
10
+ init {
11
+ System.loadLibrary("zkap_zkp_rn")
12
+ }
13
+
14
+ // JNI-compatible native methods (no underscores → no name-mangling issues).
15
+ // Corresponding Rust symbol: Java_expo_modules_zkap_ZkapSdkModule_native<X>
16
+ // Each JNI wrapper in Rust calls the C-ABI function, copies the result into
17
+ // a Java String, and frees the Rust allocation — no memory leak.
18
+ @JvmStatic external fun nativeGenerateHash(inputJson: String): String
19
+ @JvmStatic external fun nativeGenerateAnchor(inputJson: String): String
20
+ @JvmStatic external fun nativeGenerateAudHash(inputJson: String): String
21
+ @JvmStatic external fun nativeGenerateLeafHash(inputJson: String): String
22
+ @JvmStatic external fun nativeProve(inputJson: String): String
23
+ }
24
+
25
+ override fun definition() = ModuleDefinition {
26
+ Name("ZkapSdk")
27
+
28
+ AsyncFunction("generateHash") { inputJson: String ->
29
+ unwrapResult(nativeGenerateHash(inputJson))
30
+ }
31
+
32
+ AsyncFunction("generateAnchor") { inputJson: String ->
33
+ unwrapResult(nativeGenerateAnchor(inputJson))
34
+ }
35
+
36
+ AsyncFunction("generateAudHash") { inputJson: String ->
37
+ unwrapResult(nativeGenerateAudHash(inputJson))
38
+ }
39
+
40
+ AsyncFunction("generateLeafHash") { inputJson: String ->
41
+ unwrapResult(nativeGenerateLeafHash(inputJson))
42
+ }
43
+
44
+ AsyncFunction("prove") { inputJson: String ->
45
+ unwrapResult(nativeProve(inputJson))
46
+ }
47
+ }
48
+
49
+ /** Parse JSON result from Rust FFI. Throws if the result contains an "error" field. */
50
+ @Suppress("UNCHECKED_CAST")
51
+ private fun unwrapResult(json: String): String {
52
+ try {
53
+ val obj = org.json.JSONObject(json)
54
+ if (obj.has("error")) {
55
+ throw ZkapException(obj.getString("error"))
56
+ }
57
+ } catch (e: org.json.JSONException) {
58
+ // Not a JSON object — return as-is
59
+ }
60
+ return json
61
+ }
62
+ }
63
+
64
+ class ZkapException(message: String) : Exception("[zkap] $message")
@@ -0,0 +1,9 @@
1
+ {
2
+ "platforms": ["ios", "android"],
3
+ "ios": {
4
+ "modules": ["ZkapSdkModule"]
5
+ },
6
+ "android": {
7
+ "modules": ["expo.modules.zkap.ZkapSdkModule"]
8
+ }
9
+ }
@@ -0,0 +1,34 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'ZkapSdkModule'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.authors = package['author']
11
+ s.homepage = package['homepage']
12
+ s.platform = :ios, '15.0'
13
+ s.swift_version = '5.9'
14
+ s.source = { git: package['repository']['url'] }
15
+
16
+ s.source_files = 'ios/**/*.{swift,h,m}'
17
+
18
+ # Pre-built Rust static libraries (generated by CI build-react-native.yml)
19
+ s.vendored_frameworks = 'ios/ZkapZkp.xcframework'
20
+
21
+ # Expose C headers for the Rust FFI symbols
22
+ s.preserve_paths = 'ios/include/*.h'
23
+ s.xcconfig = {
24
+ 'HEADER_SEARCH_PATHS' => '$(PODS_TARGET_SRCROOT)/ios/include',
25
+ 'OTHER_LDFLAGS' => '-lc++'
26
+ }
27
+
28
+ s.dependency 'ExpoModulesCore'
29
+
30
+ s.pod_target_xcconfig = {
31
+ 'DEFINES_MODULE' => 'YES',
32
+ 'SWIFT_COMPILATION_MODE' => 'wholemodule'
33
+ }
34
+ end
@@ -0,0 +1,105 @@
1
+ import ExpoModulesCore
2
+
3
+ public class ZkapSdkModule: Module {
4
+ public func definition() -> ModuleDefinition {
5
+ Name("ZkapSdk")
6
+
7
+ // ──────────────────────────────────────────
8
+ // generateHash
9
+ // ──────────────────────────────────────────
10
+ AsyncFunction("generateHash") { (inputJson: String) -> String in
11
+ guard let cInput = inputJson.cString(using: .utf8) else {
12
+ throw ZkapError.invalidInput("Failed to encode inputJson as UTF-8")
13
+ }
14
+ let result = zkap_generate_hash(cInput)
15
+ defer { zkap_free_string(result) }
16
+ return try ZkapSdkModule.unwrapResult(result)
17
+ }
18
+
19
+ // ──────────────────────────────────────────
20
+ // generateAnchor
21
+ // ──────────────────────────────────────────
22
+ AsyncFunction("generateAnchor") { (inputJson: String) -> String in
23
+ guard let cInput = inputJson.cString(using: .utf8) else {
24
+ throw ZkapError.invalidInput("Failed to encode inputJson as UTF-8")
25
+ }
26
+ let result = zkap_generate_anchor(cInput)
27
+ defer { zkap_free_string(result) }
28
+ return try ZkapSdkModule.unwrapResult(result)
29
+ }
30
+
31
+ // ──────────────────────────────────────────
32
+ // generateAudHash
33
+ // ──────────────────────────────────────────
34
+ AsyncFunction("generateAudHash") { (inputJson: String) -> String in
35
+ guard let cInput = inputJson.cString(using: .utf8) else {
36
+ throw ZkapError.invalidInput("Failed to encode inputJson as UTF-8")
37
+ }
38
+ let result = zkap_generate_aud_hash(cInput)
39
+ defer { zkap_free_string(result) }
40
+ return try ZkapSdkModule.unwrapResult(result)
41
+ }
42
+
43
+ // ──────────────────────────────────────────
44
+ // generateLeafHash
45
+ // ──────────────────────────────────────────
46
+ AsyncFunction("generateLeafHash") { (inputJson: String) -> String in
47
+ guard let cInput = inputJson.cString(using: .utf8) else {
48
+ throw ZkapError.invalidInput("Failed to encode inputJson as UTF-8")
49
+ }
50
+ let result = zkap_generate_leaf_hash(cInput)
51
+ defer { zkap_free_string(result) }
52
+ return try ZkapSdkModule.unwrapResult(result)
53
+ }
54
+
55
+ // ──────────────────────────────────────────
56
+ // prove
57
+ // ──────────────────────────────────────────
58
+ AsyncFunction("prove") { (inputJson: String) -> String in
59
+ guard let cInput = inputJson.cString(using: .utf8) else {
60
+ throw ZkapError.invalidInput("Failed to encode inputJson as UTF-8")
61
+ }
62
+ let result = zkap_prove(cInput)
63
+ defer { zkap_free_string(result) }
64
+ return try ZkapSdkModule.unwrapResult(result)
65
+ }
66
+ }
67
+
68
+ // ──────────────────────────────────────────────────────────────
69
+ // Helpers
70
+ // ──────────────────────────────────────────────────────────────
71
+
72
+ /// Convert a raw C string pointer returned by the Rust FFI into a Swift String.
73
+ /// Throws ZkapError.rustError if the JSON contains an "error" field.
74
+ private static func unwrapResult(_ ptr: UnsafeMutablePointer<CChar>?) throws -> String {
75
+ guard let ptr = ptr else {
76
+ throw ZkapError.rustError("Rust FFI returned null pointer")
77
+ }
78
+ let json = String(cString: ptr)
79
+
80
+ // Check for error response: {"error": "..."}
81
+ if let data = json.data(using: .utf8),
82
+ let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
83
+ let errMsg = obj["error"] as? String {
84
+ throw ZkapError.rustError(errMsg)
85
+ }
86
+
87
+ return json
88
+ }
89
+ }
90
+
91
+ // ──────────────────────────────────────────────────────────────────
92
+ // Error types
93
+ // ──────────────────────────────────────────────────────────────────
94
+
95
+ enum ZkapError: Error, CustomStringConvertible {
96
+ case invalidInput(String)
97
+ case rustError(String)
98
+
99
+ var description: String {
100
+ switch self {
101
+ case .invalidInput(let msg): return "[zkap] Invalid input: \(msg)"
102
+ case .rustError(let msg): return "[zkap] Rust error: \(msg)"
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,48 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>AvailableLibraries</key>
6
+ <array>
7
+ <dict>
8
+ <key>BinaryPath</key>
9
+ <string>libzkap_zkp_rn_sim.a</string>
10
+ <key>HeadersPath</key>
11
+ <string>Headers</string>
12
+ <key>LibraryIdentifier</key>
13
+ <string>ios-arm64_x86_64-simulator</string>
14
+ <key>LibraryPath</key>
15
+ <string>libzkap_zkp_rn_sim.a</string>
16
+ <key>SupportedArchitectures</key>
17
+ <array>
18
+ <string>arm64</string>
19
+ <string>x86_64</string>
20
+ </array>
21
+ <key>SupportedPlatform</key>
22
+ <string>ios</string>
23
+ <key>SupportedPlatformVariant</key>
24
+ <string>simulator</string>
25
+ </dict>
26
+ <dict>
27
+ <key>BinaryPath</key>
28
+ <string>libzkap_zkp_rn.a</string>
29
+ <key>HeadersPath</key>
30
+ <string>Headers</string>
31
+ <key>LibraryIdentifier</key>
32
+ <string>ios-arm64</string>
33
+ <key>LibraryPath</key>
34
+ <string>libzkap_zkp_rn.a</string>
35
+ <key>SupportedArchitectures</key>
36
+ <array>
37
+ <string>arm64</string>
38
+ </array>
39
+ <key>SupportedPlatform</key>
40
+ <string>ios</string>
41
+ </dict>
42
+ </array>
43
+ <key>CFBundlePackageType</key>
44
+ <string>XFWK</string>
45
+ <key>XCFrameworkFormatVersion</key>
46
+ <string>1.0</string>
47
+ </dict>
48
+ </plist>
@@ -0,0 +1,15 @@
1
+ #pragma once
2
+ #ifdef __cplusplus
3
+ extern "C" {
4
+ #endif
5
+
6
+ char* zkap_generate_hash(const char* input_json);
7
+ char* zkap_generate_anchor(const char* input_json);
8
+ char* zkap_generate_aud_hash(const char* input_json);
9
+ char* zkap_generate_leaf_hash(const char* input_json);
10
+ char* zkap_prove(const char* input_json);
11
+ void zkap_free_string(char* ptr);
12
+
13
+ #ifdef __cplusplus
14
+ }
15
+ #endif
@@ -0,0 +1,15 @@
1
+ #pragma once
2
+ #ifdef __cplusplus
3
+ extern "C" {
4
+ #endif
5
+
6
+ char* zkap_generate_hash(const char* input_json);
7
+ char* zkap_generate_anchor(const char* input_json);
8
+ char* zkap_generate_aud_hash(const char* input_json);
9
+ char* zkap_generate_leaf_hash(const char* input_json);
10
+ char* zkap_prove(const char* input_json);
11
+ void zkap_free_string(char* ptr);
12
+
13
+ #ifdef __cplusplus
14
+ }
15
+ #endif
@@ -0,0 +1,15 @@
1
+ #pragma once
2
+ #ifdef __cplusplus
3
+ extern "C" {
4
+ #endif
5
+
6
+ char* zkap_generate_hash(const char* input_json);
7
+ char* zkap_generate_anchor(const char* input_json);
8
+ char* zkap_generate_aud_hash(const char* input_json);
9
+ char* zkap_generate_leaf_hash(const char* input_json);
10
+ char* zkap_prove(const char* input_json);
11
+ void zkap_free_string(char* ptr);
12
+
13
+ #ifdef __cplusplus
14
+ }
15
+ #endif
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@baerae/zkap-zkp-react-native",
3
+ "version": "0.1.0",
4
+ "description": "React Native SDK for zkap-zkp — on-device ZK proving with Groth16 (BN254) and Poseidon hash. Requires Expo New Architecture.",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "source": "./src/index.ts",
8
+ "react-native": "./src/index.ts",
9
+ "files": [
10
+ "README.md",
11
+ "src/",
12
+ "ios/ZkapSdkModule.podspec",
13
+ "ios/ZkapSdkModule.swift",
14
+ "ios/include/",
15
+ "ios/ZkapZkp.xcframework/",
16
+ "android/src/",
17
+ "android/build.gradle",
18
+ "android/libs/",
19
+ "expo-module.config.json"
20
+ ],
21
+ "scripts": {
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "keywords": [
28
+ "zk",
29
+ "groth16",
30
+ "poseidon",
31
+ "zkap",
32
+ "react-native",
33
+ "expo",
34
+ "zero-knowledge"
35
+ ],
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/baerae-zkap/zkap-zkp.git",
39
+ "directory": "packages/sdk-react-native"
40
+ },
41
+ "homepage": "https://github.com/baerae-zkap/zkap-zkp",
42
+ "bugs": "https://github.com/baerae-zkap/zkap-zkp/issues",
43
+ "license": "MIT OR Apache-2.0",
44
+ "peerDependencies": {
45
+ "expo": ">=50.0.0",
46
+ "expo-crypto": ">=13.0.0",
47
+ "expo-file-system": ">=17.0.0",
48
+ "expo-modules-core": ">=1.12.0",
49
+ "react-native": ">=0.73.0"
50
+ },
51
+ "devDependencies": {
52
+ "typescript": "^5.4.5"
53
+ },
54
+ "publishConfig": {
55
+ "registry": "https://registry.npmjs.org/",
56
+ "access": "public"
57
+ }
58
+ }
@@ -0,0 +1,231 @@
1
+ /**
2
+ * artifact-manager — MVP implementation
3
+ *
4
+ * Included: download, cache path management, SHA256 verification,
5
+ * atomic rename, max 3 retries, progress callback
6
+ * Excluded: HTTP Range resume, background download, advanced cache cleanup
7
+ */
8
+
9
+ import * as FileSystem from 'expo-file-system';
10
+ import * as Crypto from 'expo-crypto';
11
+
12
+ // ──────────────────────────────────────────────────────────────────
13
+ // Types
14
+ // ──────────────────────────────────────────────────────────────────
15
+
16
+ export interface ArtifactManifest {
17
+ version: string;
18
+ circuit: string;
19
+ artifacts: {
20
+ proving_key: {
21
+ url: string;
22
+ sha256: string;
23
+ size_bytes: number;
24
+ compressed_size_bytes?: number;
25
+ };
26
+ };
27
+ min_sdk_version: string;
28
+ max_sdk_version: string;
29
+ }
30
+
31
+ export interface InitProveArtifactsOptions {
32
+ /** URL of the manifest.json that describes the proving key location */
33
+ manifestUrl: string;
34
+ /**
35
+ * Local directory for caching the proving key.
36
+ * Defaults to `<FileSystem.cacheDirectory>/zkap/`
37
+ */
38
+ cacheDir?: string;
39
+ /**
40
+ * Called periodically during download.
41
+ * `downloaded` and `total` are in bytes.
42
+ */
43
+ onProgress?: (downloaded: number, total: number) => void;
44
+ }
45
+
46
+ // ──────────────────────────────────────────────────────────────────
47
+ // Constants
48
+ // ──────────────────────────────────────────────────────────────────
49
+
50
+ const MAX_RETRIES = 3;
51
+ const RETRY_BASE_DELAY_MS = 1000;
52
+
53
+ // ──────────────────────────────────────────────────────────────────
54
+ // Public API
55
+ // ──────────────────────────────────────────────────────────────────
56
+
57
+ /**
58
+ * Download (if not cached) and verify the Groth16 proving key.
59
+ *
60
+ * Returns the local file path to the cached proving key,
61
+ * which should be passed to `prove()` as `request.pk_path`.
62
+ *
63
+ * @throws if download fails after 3 retries, or SHA256 verification fails
64
+ */
65
+ export async function initProveArtifacts(
66
+ options: InitProveArtifactsOptions
67
+ ): Promise<string> {
68
+ const cacheDir =
69
+ (options.cacheDir ?? `${FileSystem.cacheDirectory}zkap/`)
70
+ .replace(/^file:\/\//, '');
71
+
72
+ // Ensure cache directory exists
73
+ await ensureDir(cacheDir);
74
+
75
+ // 1. Download manifest
76
+ const manifest = await fetchManifest(options.manifestUrl);
77
+
78
+ // 2. Determine cache file path (version-namespaced to avoid stale cache)
79
+ const pkInfo = manifest.artifacts.proving_key;
80
+ const fileName = `zkap-${manifest.version}-pk.bin`;
81
+ const pkCachePath = `${cacheDir}${fileName}`;
82
+ const pkTmpPath = `${pkCachePath}.tmp`;
83
+
84
+ // 3. Check if already cached and valid
85
+ const fileInfo = await FileSystem.getInfoAsync(pkCachePath);
86
+ if (fileInfo.exists) {
87
+ const valid = await verifySha256(pkCachePath, pkInfo.sha256);
88
+ if (valid) {
89
+ return pkCachePath;
90
+ }
91
+ // Cache is corrupted — delete and re-download
92
+ await FileSystem.deleteAsync(pkCachePath, { idempotent: true });
93
+ }
94
+
95
+ // 4. Download with retries
96
+ await downloadWithRetry({
97
+ url: pkInfo.url,
98
+ tmpPath: pkTmpPath,
99
+ totalBytes: pkInfo.size_bytes,
100
+ onProgress: options.onProgress,
101
+ maxRetries: MAX_RETRIES,
102
+ });
103
+
104
+ // 5. Verify SHA256
105
+ const valid = await verifySha256(pkTmpPath, pkInfo.sha256);
106
+ if (!valid) {
107
+ await FileSystem.deleteAsync(pkTmpPath, { idempotent: true });
108
+ throw new Error(
109
+ '[zkap/artifact-manager] SHA256 verification failed. ' +
110
+ 'The downloaded file may be corrupted. Please try again.'
111
+ );
112
+ }
113
+
114
+ // 6. Atomic rename: tmp → final cache path
115
+ await FileSystem.moveAsync({ from: pkTmpPath, to: pkCachePath });
116
+
117
+ return pkCachePath;
118
+ }
119
+
120
+ // ──────────────────────────────────────────────────────────────────
121
+ // Internal helpers
122
+ // ──────────────────────────────────────────────────────────────────
123
+
124
+ async function ensureDir(dir: string): Promise<void> {
125
+ const info = await FileSystem.getInfoAsync(dir);
126
+ if (!info.exists) {
127
+ await FileSystem.makeDirectoryAsync(dir, { intermediates: true });
128
+ }
129
+ }
130
+
131
+ async function fetchManifest(url: string): Promise<ArtifactManifest> {
132
+ const response = await fetch(url);
133
+ if (!response.ok) {
134
+ throw new Error(
135
+ `[zkap/artifact-manager] Failed to fetch manifest: HTTP ${response.status}`
136
+ );
137
+ }
138
+ return response.json() as Promise<ArtifactManifest>;
139
+ }
140
+
141
+ interface DownloadOptions {
142
+ url: string;
143
+ tmpPath: string;
144
+ totalBytes: number;
145
+ onProgress?: (downloaded: number, total: number) => void;
146
+ maxRetries: number;
147
+ }
148
+
149
+ async function downloadWithRetry(opts: DownloadOptions): Promise<void> {
150
+ let lastError: Error | null = null;
151
+
152
+ for (let attempt = 1; attempt <= opts.maxRetries; attempt++) {
153
+ try {
154
+ // Clean up any partial download from a previous attempt
155
+ await FileSystem.deleteAsync(opts.tmpPath, { idempotent: true });
156
+
157
+ const downloadResumable = FileSystem.createDownloadResumable(
158
+ opts.url,
159
+ opts.tmpPath,
160
+ {},
161
+ (progress) => {
162
+ if (opts.onProgress) {
163
+ opts.onProgress(
164
+ progress.totalBytesWritten,
165
+ progress.totalBytesExpectedToWrite > 0
166
+ ? progress.totalBytesExpectedToWrite
167
+ : opts.totalBytes
168
+ );
169
+ }
170
+ }
171
+ );
172
+
173
+ const result = await downloadResumable.downloadAsync();
174
+ if (!result || result.status !== 200) {
175
+ throw new Error(
176
+ `[zkap/artifact-manager] Download failed with status ${result?.status ?? 'unknown'}`
177
+ );
178
+ }
179
+
180
+ return; // Success
181
+ } catch (err) {
182
+ lastError = err instanceof Error ? err : new Error(String(err));
183
+ if (attempt < opts.maxRetries) {
184
+ const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
185
+ await sleep(delay);
186
+ }
187
+ }
188
+ }
189
+
190
+ throw new Error(
191
+ `[zkap/artifact-manager] Download failed after ${opts.maxRetries} attempts. ` +
192
+ `Last error: ${lastError?.message ?? 'unknown'}`
193
+ );
194
+ }
195
+
196
+ async function verifySha256(filePath: string, expectedHex: string): Promise<boolean> {
197
+ try {
198
+ // Read file as base64, convert to raw binary Uint8Array, then SHA256 the binary.
199
+ // NOTE: digestStringAsync hashes the string's UTF-8 bytes, not the file's binary
200
+ // content. We must use Crypto.digest(algorithm, Uint8Array) for correct results.
201
+ const base64 = await FileSystem.readAsStringAsync(filePath, {
202
+ encoding: FileSystem.EncodingType.Base64,
203
+ });
204
+
205
+ // base64 → raw binary bytes
206
+ const binaryStr = atob(base64);
207
+ const bytes = new Uint8Array(binaryStr.length);
208
+ for (let i = 0; i < binaryStr.length; i++) {
209
+ bytes[i] = binaryStr.charCodeAt(i);
210
+ }
211
+
212
+ // Hash the raw binary
213
+ const digestBuffer = await Crypto.digest(
214
+ Crypto.CryptoDigestAlgorithm.SHA256,
215
+ bytes
216
+ );
217
+
218
+ // ArrayBuffer → hex string
219
+ const digestHex = Array.from(new Uint8Array(digestBuffer))
220
+ .map((b) => b.toString(16).padStart(2, '0'))
221
+ .join('');
222
+
223
+ return digestHex.toLowerCase() === expectedHex.toLowerCase();
224
+ } catch {
225
+ return false;
226
+ }
227
+ }
228
+
229
+ function sleep(ms: number): Promise<void> {
230
+ return new Promise((resolve) => setTimeout(resolve, ms));
231
+ }
package/src/index.ts ADDED
@@ -0,0 +1,147 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+
3
+ const ZkapSdk = requireNativeModule('ZkapSdk');
4
+
5
+ // ──────────────────────────────────────────────────────────────────
6
+ // Input / Output types
7
+ // ──────────────────────────────────────────────────────────────────
8
+
9
+ export interface CircuitConfig {
10
+ max_jwt_b64_len: number;
11
+ max_payload_b64_len: number;
12
+ max_aud_len: number;
13
+ max_exp_len: number;
14
+ max_iss_len: number;
15
+ max_nonce_len: number;
16
+ max_sub_len: number;
17
+ n: number;
18
+ k: number;
19
+ tree_height: number;
20
+ num_audience_limit: number;
21
+ claims: string[];
22
+ forbidden_string: string;
23
+ }
24
+
25
+ export interface Secret {
26
+ sub: string;
27
+ iss: string;
28
+ aud: string;
29
+ }
30
+
31
+ export interface ProveRequest {
32
+ pk_path: string;
33
+ jwts: string[];
34
+ pk_ops: string[];
35
+ merkle_paths: string[][];
36
+ leaf_indices: number[];
37
+ root: string;
38
+ anchor: string[];
39
+ h_sign_user_op: string;
40
+ random: string;
41
+ aud_list: string[];
42
+ }
43
+
44
+ export interface ProveResult {
45
+ proofs: string[];
46
+ public_inputs: string[][];
47
+ }
48
+
49
+ // ──────────────────────────────────────────────────────────────────
50
+ // Supported API
51
+ // ──────────────────────────────────────────────────────────────────
52
+
53
+ /**
54
+ * Compute a Poseidon hash of one or more field-element strings (hex or decimal).
55
+ * Returns the result as a 0x-prefixed hex string.
56
+ */
57
+ export async function generateHash(messages: string[]): Promise<string> {
58
+ const result: string = await ZkapSdk.generateHash(
59
+ JSON.stringify({ messages })
60
+ );
61
+ return JSON.parse(result).result as string;
62
+ }
63
+
64
+ /**
65
+ * Generate a Poseidon threshold anchor from JWT credential secrets.
66
+ * Returns anchor polynomial evaluation points as hex strings.
67
+ */
68
+ export async function generateAnchor(
69
+ config: CircuitConfig,
70
+ secrets: Secret[]
71
+ ): Promise<{ evaluations: string[] }> {
72
+ const result: string = await ZkapSdk.generateAnchor(
73
+ JSON.stringify({ config, secrets })
74
+ );
75
+ return JSON.parse(result) as { evaluations: string[] };
76
+ }
77
+
78
+ /**
79
+ * Compute per-audience hashes and the combined audience-list hash.
80
+ */
81
+ export async function generateAudHash(
82
+ config: CircuitConfig,
83
+ aud_list: string[]
84
+ ): Promise<{ aud_hashes: string[]; h_aud_list: string }> {
85
+ const result: string = await ZkapSdk.generateAudHash(
86
+ JSON.stringify({ config, aud_list })
87
+ );
88
+ return JSON.parse(result) as { aud_hashes: string[]; h_aud_list: string };
89
+ }
90
+
91
+ /**
92
+ * Compute the Merkle leaf hash for an issuer and RSA public-key modulus (base64-encoded).
93
+ */
94
+ export async function generateLeafHash(
95
+ config: CircuitConfig,
96
+ iss: string,
97
+ pk_b64: string
98
+ ): Promise<string> {
99
+ const result: string = await ZkapSdk.generateLeafHash(
100
+ JSON.stringify({ config, iss, pk_b64 })
101
+ );
102
+ return JSON.parse(result).result as string;
103
+ }
104
+
105
+ /**
106
+ * Generate Groth16 proofs (on-device proving).
107
+ * Requires PK to be downloaded and cached via initProveArtifacts().
108
+ */
109
+ export async function prove(
110
+ config: CircuitConfig,
111
+ request: ProveRequest
112
+ ): Promise<ProveResult> {
113
+ const result: string = await ZkapSdk.prove(
114
+ JSON.stringify({ config, request })
115
+ );
116
+ return JSON.parse(result) as ProveResult;
117
+ }
118
+
119
+ // ──────────────────────────────────────────────────────────────────
120
+ // Unsupported API — explicit error messages
121
+ // ──────────────────────────────────────────────────────────────────
122
+
123
+ /**
124
+ * NOT supported on React Native.
125
+ * groth16Setup() requires a server environment with large CRS data.
126
+ * Use @baerae/zkap-zkp (Node.js) for setup.
127
+ */
128
+ export function groth16Setup(): never {
129
+ throw new Error(
130
+ '[zkap/sdk-react-native] groth16Setup() is not supported on mobile. ' +
131
+ 'Use @baerae/zkap-zkp (Node.js) for server-side trusted setup.'
132
+ );
133
+ }
134
+
135
+ /**
136
+ * NOT supported on React Native.
137
+ * verify() uses a verifying key hardcoded in a smart contract.
138
+ * Use @baerae/zkap-zkp (Node.js) for server-side verification.
139
+ */
140
+ export function verify(): never {
141
+ throw new Error(
142
+ '[zkap/sdk-react-native] verify() is not supported on mobile. ' +
143
+ 'Use @baerae/zkap-zkp (Node.js) for server-side verification.'
144
+ );
145
+ }
146
+
147
+ export { initProveArtifacts } from './artifact-manager';