@jupitermetalabs/face-zk-sdk 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.
Files changed (83) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +181 -0
  3. package/assets/README.md +22 -0
  4. package/assets/face-guidance/face-logic.js.txt +77 -0
  5. package/assets/face-guidance/index.html +173 -0
  6. package/assets/face-guidance/pose-guidance.js.txt +403 -0
  7. package/assets/liveness/antispoof.js.txt +143 -0
  8. package/assets/liveness/index.html +451 -0
  9. package/assets/liveness/liveness.js.txt +1003 -0
  10. package/assets/mediapipe/face_mesh.js.txt +131 -0
  11. package/assets/mediapipe/face_mesh_solution_packed_assets.data +0 -0
  12. package/assets/mediapipe/face_mesh_solution_simd_wasm_bin.wasm +0 -0
  13. package/assets/mediapipe/face_mesh_solution_wasm_bin.wasm +0 -0
  14. package/assets/onnx/ort-wasm-simd.wasm +0 -0
  15. package/assets/onnx/ort-wasm.wasm +0 -0
  16. package/assets/onnx/ort.min.js.txt +7 -0
  17. package/assets/wasm/zk_face_wasm_bg.wasm +0 -0
  18. package/assets/zk-worker.html +472 -0
  19. package/cli/copy-ort-assets.js +65 -0
  20. package/cli/setup.js +266 -0
  21. package/dist/FaceZkSdk.d.ts +69 -0
  22. package/dist/FaceZkSdk.js +132 -0
  23. package/dist/assets/onnx/ort-min.d.ts +1 -0
  24. package/dist/assets/onnx/ort-min.js +8 -0
  25. package/dist/config/defaults.d.ts +49 -0
  26. package/dist/config/defaults.js +55 -0
  27. package/dist/config/types.d.ts +123 -0
  28. package/dist/config/types.js +16 -0
  29. package/dist/core/enrollment-core.d.ts +68 -0
  30. package/dist/core/enrollment-core.js +202 -0
  31. package/dist/core/matching.d.ts +69 -0
  32. package/dist/core/matching.js +96 -0
  33. package/dist/core/types.d.ts +365 -0
  34. package/dist/core/types.js +34 -0
  35. package/dist/core/verification-core.d.ts +120 -0
  36. package/dist/core/verification-core.js +434 -0
  37. package/dist/core/zk-core.d.ts +69 -0
  38. package/dist/core/zk-core.js +240 -0
  39. package/dist/index.d.ts +29 -0
  40. package/dist/index.js +39 -0
  41. package/dist/react-native/adapters/faceEmbeddingProvider.d.ts +38 -0
  42. package/dist/react-native/adapters/faceEmbeddingProvider.js +41 -0
  43. package/dist/react-native/adapters/imageDataProvider.d.ts +53 -0
  44. package/dist/react-native/adapters/imageDataProvider.js +97 -0
  45. package/dist/react-native/adapters/livenessProvider.d.ts +133 -0
  46. package/dist/react-native/adapters/livenessProvider.js +144 -0
  47. package/dist/react-native/adapters/zkProofEngine-webview.d.ts +73 -0
  48. package/dist/react-native/adapters/zkProofEngine-webview.js +129 -0
  49. package/dist/react-native/components/FacePoseGuidanceWebView.d.ts +30 -0
  50. package/dist/react-native/components/FacePoseGuidanceWebView.js +474 -0
  51. package/dist/react-native/components/LivenessWebView.d.ts +39 -0
  52. package/dist/react-native/components/LivenessWebView.js +348 -0
  53. package/dist/react-native/components/OnnxRuntimeWebView.d.ts +54 -0
  54. package/dist/react-native/components/OnnxRuntimeWebView.js +394 -0
  55. package/dist/react-native/components/ZkProofWebView.d.ts +59 -0
  56. package/dist/react-native/components/ZkProofWebView.js +259 -0
  57. package/dist/react-native/dependencies.d.ts +144 -0
  58. package/dist/react-native/dependencies.js +123 -0
  59. package/dist/react-native/hooks/useOnnxLoader.d.ts +38 -0
  60. package/dist/react-native/hooks/useOnnxLoader.js +81 -0
  61. package/dist/react-native/hooks/useWasmLoader.d.ts +30 -0
  62. package/dist/react-native/hooks/useWasmLoader.js +122 -0
  63. package/dist/react-native/index.d.ts +59 -0
  64. package/dist/react-native/index.js +96 -0
  65. package/dist/react-native/services/FaceRecognition.d.ts +70 -0
  66. package/dist/react-native/services/FaceRecognition.js +517 -0
  67. package/dist/react-native/ui/FaceZkVerificationFlow.d.ts +97 -0
  68. package/dist/react-native/ui/FaceZkVerificationFlow.js +433 -0
  69. package/dist/react-native/ui/ReferenceEnrollmentFlow.d.ts +72 -0
  70. package/dist/react-native/ui/ReferenceEnrollmentFlow.js +321 -0
  71. package/dist/react-native/utils/faceAlignment.d.ts +37 -0
  72. package/dist/react-native/utils/faceAlignment.js +182 -0
  73. package/dist/react-native/utils/modelInitialisationChecks.d.ts +36 -0
  74. package/dist/react-native/utils/modelInitialisationChecks.js +92 -0
  75. package/dist/react-native/utils/resolveModelUri.d.ts +55 -0
  76. package/dist/react-native/utils/resolveModelUri.js +172 -0
  77. package/dist/react-native/utils/resolveUiConfig.d.ts +41 -0
  78. package/dist/react-native/utils/resolveUiConfig.js +76 -0
  79. package/dist/storage/defaultStorageAdapter.d.ts +44 -0
  80. package/dist/storage/defaultStorageAdapter.js +299 -0
  81. package/dist/tsconfig.tsbuildinfo +1 -0
  82. package/face-zk.config.example.js +88 -0
  83. package/package.json +76 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Copyright 2026 JupiterMeta Labs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import type { ModelSource } from "../../config/types";
17
+ /** Directory where URL-downloaded models are stored persistently. */
18
+ export declare const MODEL_STORE_DIR: string;
19
+ /**
20
+ * Resolve a ModelSource to a local file URI.
21
+ *
22
+ * @param source The model source to resolve.
23
+ * @param onProgress Optional callback receiving download fraction (0–1).
24
+ * Only fires for `url` sources during active download;
25
+ * silent on cache hits.
26
+ * @param allowedDomains Optional hostname allowlist. When provided and non-empty,
27
+ * any `url` source whose hostname is not in this list is
28
+ * rejected before any network request is made.
29
+ * Pass `FaceZkConfig.allowedDomains` here in production.
30
+ * @throws if the source has none of: module, url, localUri
31
+ * @throws if the download fails (url path)
32
+ */
33
+ export declare function resolveModelUri(source: ModelSource, onProgress?: (fraction: number) => void, allowedDomains?: string[]): Promise<string>;
34
+ /**
35
+ * Derive the local store path for a given URL.
36
+ *
37
+ * Guards against path traversal in two passes:
38
+ * 1. URL-decode the last path segment so encoded separators like `%2F` or
39
+ * `%2E%2E` are normalised before any checks run.
40
+ * 2. Re-extract the basename from the decoded string (a decoded `%2F`
41
+ * becomes `/`, so a second `.split("/")` is needed).
42
+ * 3. Reject the reserved names `.` and `..` outright.
43
+ * 4. Allowlist `[A-Za-z0-9._-]` — the only characters that can appear in a
44
+ * legitimate model filename. Anything outside this set throws so the
45
+ * caller can surface the problem rather than silently storing a bad file.
46
+ *
47
+ * @throws if the derived filename is empty, a traversal component, or contains
48
+ * characters outside the safe allowlist.
49
+ */
50
+ export declare function deriveStorePath(url: string): string;
51
+ /**
52
+ * Clear all URL-downloaded model files from the persistent store.
53
+ * Useful for forcing a re-download after a model version update.
54
+ */
55
+ export declare function clearModelCache(): Promise<void>;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Copyright 2026 JupiterMeta Labs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * Face+ZK SDK – Model URI Resolution
18
+ *
19
+ * Resolves a `ModelSource` to a local file URI that can be passed to
20
+ * FileSystem.readAsStringAsync() or similar APIs.
21
+ *
22
+ * Resolution order:
23
+ * 1. localUri – returned as-is (already resolved)
24
+ * 2. module – resolved via expo-asset (Metro-bundled)
25
+ * 3. url – downloaded to documentDirectory on first use, path returned
26
+ *
27
+ * Models are stored in documentDirectory (not cacheDirectory) so they
28
+ * survive cache clears and never need re-downloading.
29
+ */
30
+ import { Asset } from "expo-asset";
31
+ import * as FileSystem from "expo-file-system/legacy";
32
+ /** Directory where URL-downloaded models are stored persistently. */
33
+ export const MODEL_STORE_DIR = `${FileSystem.documentDirectory}face-zk-models/`;
34
+ /**
35
+ * Resolve a ModelSource to a local file URI.
36
+ *
37
+ * @param source The model source to resolve.
38
+ * @param onProgress Optional callback receiving download fraction (0–1).
39
+ * Only fires for `url` sources during active download;
40
+ * silent on cache hits.
41
+ * @param allowedDomains Optional hostname allowlist. When provided and non-empty,
42
+ * any `url` source whose hostname is not in this list is
43
+ * rejected before any network request is made.
44
+ * Pass `FaceZkConfig.allowedDomains` here in production.
45
+ * @throws if the source has none of: module, url, localUri
46
+ * @throws if the download fails (url path)
47
+ */
48
+ export async function resolveModelUri(source, onProgress, allowedDomains) {
49
+ // ── 1. Already a local URI ──────────────────────────────────────────────
50
+ if (source.localUri) {
51
+ return source.localUri;
52
+ }
53
+ // ── 2. Metro-bundled module ─────────────────────────────────────────────
54
+ if (source.module != null) {
55
+ const asset = Asset.fromModule(source.module);
56
+ await asset.downloadAsync();
57
+ const uri = asset.localUri ?? asset.uri;
58
+ if (!uri) {
59
+ throw new Error("[FaceZkSdk] Failed to resolve bundled asset URI.");
60
+ }
61
+ return uri;
62
+ }
63
+ // ── 3. Remote URL – download and store persistently ─────────────────────
64
+ if (source.url) {
65
+ return downloadAndStore(source.url, onProgress, allowedDomains);
66
+ }
67
+ throw new Error("[FaceZkSdk] ModelSource must have at least one of: module, url, localUri.");
68
+ }
69
+ /**
70
+ * Derive the local store path for a given URL.
71
+ *
72
+ * Guards against path traversal in two passes:
73
+ * 1. URL-decode the last path segment so encoded separators like `%2F` or
74
+ * `%2E%2E` are normalised before any checks run.
75
+ * 2. Re-extract the basename from the decoded string (a decoded `%2F`
76
+ * becomes `/`, so a second `.split("/")` is needed).
77
+ * 3. Reject the reserved names `.` and `..` outright.
78
+ * 4. Allowlist `[A-Za-z0-9._-]` — the only characters that can appear in a
79
+ * legitimate model filename. Anything outside this set throws so the
80
+ * caller can surface the problem rather than silently storing a bad file.
81
+ *
82
+ * @throws if the derived filename is empty, a traversal component, or contains
83
+ * characters outside the safe allowlist.
84
+ */
85
+ export function deriveStorePath(url) {
86
+ // Step 1 – take the last slash-delimited segment and drop any query string
87
+ const raw = url.split("/").pop()?.split("?")[0] ?? "";
88
+ // Step 2 – URL-decode so encoded traversal sequences are normalised
89
+ let decoded;
90
+ try {
91
+ decoded = decodeURIComponent(raw);
92
+ }
93
+ catch {
94
+ decoded = raw;
95
+ }
96
+ // Step 3 – re-extract basename in case decoding introduced new separators
97
+ const basename = decoded.replace(/\\/g, "/").split("/").pop() ?? "";
98
+ // Step 4 – reject empty names and dot-traversal components
99
+ if (!basename || basename === "." || basename === "..") {
100
+ throw new Error(`[FaceZkSdk] Unsafe or empty model filename derived from URL: "${url}"`);
101
+ }
102
+ // Step 5 – allowlist: only alphanumeric, dot, hyphen, underscore
103
+ if (!/^[A-Za-z0-9._-]+$/.test(basename)) {
104
+ throw new Error(`[FaceZkSdk] Model filename contains unsafe characters: "${basename}"`);
105
+ }
106
+ return `${MODEL_STORE_DIR}${basename}`;
107
+ }
108
+ /**
109
+ * Download a remote model URL to documentDirectory.
110
+ * Skips download if the file already exists (store hit).
111
+ *
112
+ * Security layers (in order):
113
+ * 1. HTTPS-only — rejects http://, file://, data: etc.
114
+ * 2. Domain allowlist — rejects hostnames not in allowedDomains (if provided).
115
+ * 3. Path containment — asserts the derived local path stays inside MODEL_STORE_DIR.
116
+ */
117
+ async function downloadAndStore(url, onProgress, allowedDomains) {
118
+ // ── Layer 1: HTTPS-only ────────────────────────────────────────────────
119
+ if (!url.startsWith("https://")) {
120
+ throw new Error(`[FaceZkSdk] Only HTTPS model URLs are permitted. Received: "${url.split("?")[0]}"`);
121
+ }
122
+ // ── Layer 2: Domain allowlist ──────────────────────────────────────────
123
+ if (allowedDomains && allowedDomains.length > 0) {
124
+ const hostnameMatch = url.match(/^https:\/\/([^/?#]+)/);
125
+ const hostname = hostnameMatch ? hostnameMatch[1] : null;
126
+ if (!hostname || !allowedDomains.includes(hostname)) {
127
+ throw new Error(`[FaceZkSdk] Model download blocked: hostname "${hostname ?? "unknown"}" is not in allowedDomains. ` +
128
+ `Allowed: [${allowedDomains.join(", ")}]`);
129
+ }
130
+ }
131
+ const localPath = deriveStorePath(url);
132
+ // ── Layer 3: Path containment ──────────────────────────────────────────
133
+ if (!localPath.startsWith(MODEL_STORE_DIR)) {
134
+ throw new Error(`[FaceZkSdk] Security: derived path escapes model store. This is a bug — please report it.`);
135
+ }
136
+ // Ensure store directory exists
137
+ const dirInfo = await FileSystem.getInfoAsync(MODEL_STORE_DIR);
138
+ if (!dirInfo.exists) {
139
+ await FileSystem.makeDirectoryAsync(MODEL_STORE_DIR, {
140
+ intermediates: true,
141
+ });
142
+ }
143
+ // Return stored file if already present
144
+ const fileInfo = await FileSystem.getInfoAsync(localPath);
145
+ if (fileInfo.exists) {
146
+ return localPath;
147
+ }
148
+ // Download with optional progress reporting
149
+ const downloadResumable = FileSystem.createDownloadResumable(url, localPath, {}, onProgress
150
+ ? (progress) => {
151
+ if (progress.totalBytesExpectedToWrite > 0) {
152
+ onProgress(progress.totalBytesWritten / progress.totalBytesExpectedToWrite);
153
+ }
154
+ }
155
+ : undefined);
156
+ const result = await downloadResumable.downloadAsync();
157
+ if (!result || result.status < 200 || result.status >= 300) {
158
+ await FileSystem.deleteAsync(localPath, { idempotent: true });
159
+ throw new Error(`[FaceZkSdk] Failed to download model from ${url} (HTTP ${result?.status ?? "unknown"}).`);
160
+ }
161
+ return localPath;
162
+ }
163
+ /**
164
+ * Clear all URL-downloaded model files from the persistent store.
165
+ * Useful for forcing a re-download after a model version update.
166
+ */
167
+ export async function clearModelCache() {
168
+ const info = await FileSystem.getInfoAsync(MODEL_STORE_DIR);
169
+ if (info.exists) {
170
+ await FileSystem.deleteAsync(MODEL_STORE_DIR, { idempotent: true });
171
+ }
172
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Copyright 2026 JupiterMeta Labs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * Face+ZK SDK – UI Config Resolver
18
+ *
19
+ * Merges user-supplied UiConfig with SDK defaults so flow components
20
+ * always have a complete, resolved theme and string set.
21
+ */
22
+ import type { UiConfig, FaceZkTheme, FaceZkStrings } from "../../core/types";
23
+ export declare const DEFAULT_THEME: FaceZkTheme;
24
+ export declare const DEFAULT_STRINGS: Required<FaceZkStrings>;
25
+ export interface ResolvedTheme extends FaceZkTheme {
26
+ colors: Required<FaceZkTheme["colors"]>;
27
+ borderRadius: number;
28
+ }
29
+ export interface ResolvedUiConfig {
30
+ theme: ResolvedTheme;
31
+ strings: Required<FaceZkStrings>;
32
+ renderLoading?: UiConfig["renderLoading"];
33
+ renderSuccess?: UiConfig["renderSuccess"];
34
+ renderError?: UiConfig["renderError"];
35
+ renderOverlay?: UiConfig["renderOverlay"];
36
+ }
37
+ export declare function resolveUiConfig(uiConfig?: UiConfig): ResolvedUiConfig;
38
+ /**
39
+ * Replace `{score}` placeholder in a string template.
40
+ */
41
+ export declare function interpolate(template: string, vars: Record<string, string | number>): string;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Copyright 2026 JupiterMeta Labs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ // ============================================================================
17
+ // Defaults
18
+ // ============================================================================
19
+ export const DEFAULT_THEME = {
20
+ colors: {
21
+ primary: "#4CAF50",
22
+ background: "#000000",
23
+ surface: "rgba(255,255,255,0.1)",
24
+ text: "#ffffff",
25
+ textMuted: "#aaaaaa",
26
+ error: "#F44336",
27
+ },
28
+ borderRadius: 8,
29
+ };
30
+ export const DEFAULT_STRINGS = {
31
+ loadingInitializing: "Initializing...",
32
+ loadingModels: "Loading face recognition models...",
33
+ loadingCapturing: "Capturing image...",
34
+ loadingProcessing: "Processing reference image...",
35
+ loadingEmbedding: "Processing face...",
36
+ loadingMatching: "Matching face...",
37
+ loadingZkProof: "Generating cryptographic proof...",
38
+ verificationSuccessTitle: "Verified!",
39
+ verificationSuccessSubtitle: "Match: {score}%",
40
+ enrollmentSuccessTitle: "Reference Enrolled",
41
+ enrollmentSuccessSubtitle: "Your reference has been successfully enrolled.",
42
+ verificationErrorTitle: "Verification Failed",
43
+ enrollmentErrorTitle: "Enrollment Failed",
44
+ cancelButton: "Cancel",
45
+ retryButton: "Try Again",
46
+ };
47
+ // ============================================================================
48
+ // Resolver
49
+ // ============================================================================
50
+ export function resolveUiConfig(uiConfig = {}) {
51
+ const userColors = uiConfig.theme?.colors ?? {};
52
+ const userTheme = uiConfig.theme ?? {};
53
+ return {
54
+ theme: {
55
+ colors: {
56
+ ...DEFAULT_THEME.colors,
57
+ ...userColors,
58
+ },
59
+ borderRadius: userTheme.borderRadius ?? DEFAULT_THEME.borderRadius,
60
+ },
61
+ strings: {
62
+ ...DEFAULT_STRINGS,
63
+ ...(uiConfig.strings ?? {}),
64
+ },
65
+ renderLoading: uiConfig.renderLoading,
66
+ renderSuccess: uiConfig.renderSuccess,
67
+ renderError: uiConfig.renderError,
68
+ renderOverlay: uiConfig.renderOverlay,
69
+ };
70
+ }
71
+ /**
72
+ * Replace `{score}` placeholder in a string template.
73
+ */
74
+ export function interpolate(template, vars) {
75
+ return template.replace(/\{(\w+)\}/g, (_, key) => key in vars ? String(vars[key]) : `{${key}}`);
76
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Copyright 2026 JupiterMeta Labs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import type { StorageAdapter, ReferenceId } from "../core/types";
17
+ /**
18
+ * Create a default storage adapter using AsyncStorage and FileSystem.
19
+ *
20
+ * This implementation:
21
+ * - Stores reference templates in AsyncStorage (small, fast access)
22
+ * - Stores ZK proofs in FileSystem (large, persistent)
23
+ * - Maintains indices for listing all stored items
24
+ *
25
+ * @returns StorageAdapter implementation
26
+ */
27
+ export declare function createDefaultStorageAdapter(): StorageAdapter;
28
+ /**
29
+ * Get all reference IDs from the index
30
+ */
31
+ export declare function getAllReferenceIds(): Promise<ReferenceId[]>;
32
+ /**
33
+ * Get all proof IDs from the index
34
+ */
35
+ export declare function getAllProofIds(): Promise<string[]>;
36
+ /**
37
+ * Clear all stored references and proofs (for testing/debugging)
38
+ */
39
+ export declare function clearAllStorage(): Promise<void>;
40
+ /**
41
+ * Singleton instance for convenience.
42
+ * Use this if you don't need custom configuration.
43
+ */
44
+ export declare const defaultStorageAdapter: StorageAdapter;
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Copyright 2026 JupiterMeta Labs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * Default Storage Adapter for React Native
18
+ *
19
+ * Implements the StorageAdapter interface using AsyncStorage for metadata
20
+ * and FileSystem for larger data (embeddings, proofs).
21
+ *
22
+ * Storage strategy:
23
+ * - Reference templates: AsyncStorage (embeddings are ~2KB, well under 2MB limit)
24
+ * - ZK proofs: FileSystem (proofs can be large)
25
+ */
26
+ import AsyncStorage from "@react-native-async-storage/async-storage";
27
+ import * as FileSystem from "expo-file-system/legacy";
28
+ /**
29
+ * Storage keys
30
+ */
31
+ const STORAGE_KEYS = {
32
+ REFERENCE_PREFIX: "@face-zk-sdk/reference/",
33
+ PROOF_PREFIX: "@face-zk-sdk/proof/",
34
+ REFERENCE_INDEX: "@face-zk-sdk/reference-index",
35
+ PROOF_INDEX: "@face-zk-sdk/proof-index",
36
+ };
37
+ /**
38
+ * File paths for FileSystem storage
39
+ */
40
+ const getFilePaths = () => {
41
+ const baseDir = `${FileSystem.documentDirectory}face-zk-sdk/`;
42
+ return {
43
+ baseDir,
44
+ proofsDir: `${baseDir}proofs/`,
45
+ };
46
+ };
47
+ /**
48
+ * Ensure storage directories exist
49
+ */
50
+ async function ensureDirectoriesExist() {
51
+ const { baseDir, proofsDir } = getFilePaths();
52
+ try {
53
+ // Check if base directory exists
54
+ const baseDirInfo = await FileSystem.getInfoAsync(baseDir);
55
+ if (!baseDirInfo.exists) {
56
+ await FileSystem.makeDirectoryAsync(baseDir, { intermediates: true });
57
+ }
58
+ // Check if proofs directory exists
59
+ const proofsDirInfo = await FileSystem.getInfoAsync(proofsDir);
60
+ if (!proofsDirInfo.exists) {
61
+ await FileSystem.makeDirectoryAsync(proofsDir, { intermediates: true });
62
+ }
63
+ }
64
+ catch (error) {
65
+ console.error("[DefaultStorageAdapter] Error creating directories:", error);
66
+ throw error;
67
+ }
68
+ }
69
+ /**
70
+ * Create a default storage adapter using AsyncStorage and FileSystem.
71
+ *
72
+ * This implementation:
73
+ * - Stores reference templates in AsyncStorage (small, fast access)
74
+ * - Stores ZK proofs in FileSystem (large, persistent)
75
+ * - Maintains indices for listing all stored items
76
+ *
77
+ * @returns StorageAdapter implementation
78
+ */
79
+ export function createDefaultStorageAdapter() {
80
+ return {
81
+ // ========================================================================
82
+ // Reference Template Storage
83
+ // ========================================================================
84
+ async saveReference(template) {
85
+ const key = `${STORAGE_KEYS.REFERENCE_PREFIX}${template.referenceId}`;
86
+ try {
87
+ // Serialize template to JSON
88
+ const json = JSON.stringify(template);
89
+ // Store in AsyncStorage
90
+ await AsyncStorage.setItem(key, json);
91
+ // Update index
92
+ await updateReferenceIndex(template.referenceId, "add");
93
+ console.log(`[DefaultStorageAdapter] Saved reference: ${template.referenceId}`);
94
+ return template.referenceId;
95
+ }
96
+ catch (error) {
97
+ console.error("[DefaultStorageAdapter] Error saving reference:", error);
98
+ throw new Error(`Failed to save reference: ${error instanceof Error ? error.message : "Unknown error"}`);
99
+ }
100
+ },
101
+ async loadReference(referenceId) {
102
+ const key = `${STORAGE_KEYS.REFERENCE_PREFIX}${referenceId}`;
103
+ try {
104
+ const json = await AsyncStorage.getItem(key);
105
+ if (!json) {
106
+ console.log(`[DefaultStorageAdapter] Reference not found: ${referenceId}`);
107
+ return null;
108
+ }
109
+ const template = JSON.parse(json);
110
+ console.log(`[DefaultStorageAdapter] Loaded reference: ${referenceId}`);
111
+ return template;
112
+ }
113
+ catch (error) {
114
+ console.error("[DefaultStorageAdapter] Error loading reference:", error);
115
+ return null;
116
+ }
117
+ },
118
+ async deleteReference(referenceId) {
119
+ const key = `${STORAGE_KEYS.REFERENCE_PREFIX}${referenceId}`;
120
+ try {
121
+ await AsyncStorage.removeItem(key);
122
+ // Update index
123
+ await updateReferenceIndex(referenceId, "remove");
124
+ console.log(`[DefaultStorageAdapter] Deleted reference: ${referenceId}`);
125
+ }
126
+ catch (error) {
127
+ console.error("[DefaultStorageAdapter] Error deleting reference:", error);
128
+ throw new Error(`Failed to delete reference: ${error instanceof Error ? error.message : "Unknown error"}`);
129
+ }
130
+ },
131
+ // ========================================================================
132
+ // ZK Proof Storage (FileSystem)
133
+ // ========================================================================
134
+ async saveProof(record) {
135
+ const proofId = `proof_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
136
+ const { proofsDir } = getFilePaths();
137
+ const filePath = `${proofsDir}${proofId}.json`;
138
+ try {
139
+ // Ensure directories exist
140
+ await ensureDirectoriesExist();
141
+ // Serialize proof record to JSON
142
+ const json = JSON.stringify(record);
143
+ // Write to file
144
+ await FileSystem.writeAsStringAsync(filePath, json, {
145
+ encoding: FileSystem.EncodingType.UTF8,
146
+ });
147
+ // Update index
148
+ await updateProofIndex(proofId, "add");
149
+ console.log(`[DefaultStorageAdapter] Saved proof: ${proofId}`);
150
+ return proofId;
151
+ }
152
+ catch (error) {
153
+ console.error("[DefaultStorageAdapter] Error saving proof:", error);
154
+ throw new Error(`Failed to save proof: ${error instanceof Error ? error.message : "Unknown error"}`);
155
+ }
156
+ },
157
+ async loadProof(proofId) {
158
+ const { proofsDir } = getFilePaths();
159
+ const filePath = `${proofsDir}${proofId}.json`;
160
+ try {
161
+ // Check if file exists
162
+ const fileInfo = await FileSystem.getInfoAsync(filePath);
163
+ if (!fileInfo.exists) {
164
+ console.log(`[DefaultStorageAdapter] Proof not found: ${proofId}`);
165
+ return null;
166
+ }
167
+ // Read file
168
+ const json = await FileSystem.readAsStringAsync(filePath, {
169
+ encoding: FileSystem.EncodingType.UTF8,
170
+ });
171
+ const record = JSON.parse(json);
172
+ console.log(`[DefaultStorageAdapter] Loaded proof: ${proofId}`);
173
+ return record;
174
+ }
175
+ catch (error) {
176
+ console.error("[DefaultStorageAdapter] Error loading proof:", error);
177
+ return null;
178
+ }
179
+ },
180
+ };
181
+ }
182
+ // ============================================================================
183
+ // Helper Functions for Index Management
184
+ // ============================================================================
185
+ /**
186
+ * Update the reference index (list of all reference IDs)
187
+ */
188
+ async function updateReferenceIndex(referenceId, action) {
189
+ try {
190
+ const indexJson = await AsyncStorage.getItem(STORAGE_KEYS.REFERENCE_INDEX);
191
+ const index = indexJson ? JSON.parse(indexJson) : [];
192
+ if (action === "add") {
193
+ if (!index.includes(referenceId)) {
194
+ index.push(referenceId);
195
+ }
196
+ }
197
+ else {
198
+ const idx = index.indexOf(referenceId);
199
+ if (idx !== -1) {
200
+ index.splice(idx, 1);
201
+ }
202
+ }
203
+ await AsyncStorage.setItem(STORAGE_KEYS.REFERENCE_INDEX, JSON.stringify(index));
204
+ }
205
+ catch (error) {
206
+ console.error("[DefaultStorageAdapter] Error updating reference index:", error);
207
+ }
208
+ }
209
+ /**
210
+ * Update the proof index (list of all proof IDs)
211
+ */
212
+ async function updateProofIndex(proofId, action) {
213
+ try {
214
+ const indexJson = await AsyncStorage.getItem(STORAGE_KEYS.PROOF_INDEX);
215
+ const index = indexJson ? JSON.parse(indexJson) : [];
216
+ if (action === "add") {
217
+ if (!index.includes(proofId)) {
218
+ index.push(proofId);
219
+ }
220
+ }
221
+ else {
222
+ const idx = index.indexOf(proofId);
223
+ if (idx !== -1) {
224
+ index.splice(idx, 1);
225
+ }
226
+ }
227
+ await AsyncStorage.setItem(STORAGE_KEYS.PROOF_INDEX, JSON.stringify(index));
228
+ }
229
+ catch (error) {
230
+ console.error("[DefaultStorageAdapter] Error updating proof index:", error);
231
+ }
232
+ }
233
+ /**
234
+ * Get all reference IDs from the index
235
+ */
236
+ export async function getAllReferenceIds() {
237
+ try {
238
+ const indexJson = await AsyncStorage.getItem(STORAGE_KEYS.REFERENCE_INDEX);
239
+ return indexJson ? JSON.parse(indexJson) : [];
240
+ }
241
+ catch (error) {
242
+ console.error("[DefaultStorageAdapter] Error getting reference index:", error);
243
+ return [];
244
+ }
245
+ }
246
+ /**
247
+ * Get all proof IDs from the index
248
+ */
249
+ export async function getAllProofIds() {
250
+ try {
251
+ const indexJson = await AsyncStorage.getItem(STORAGE_KEYS.PROOF_INDEX);
252
+ return indexJson ? JSON.parse(indexJson) : [];
253
+ }
254
+ catch (error) {
255
+ console.error("[DefaultStorageAdapter] Error getting proof index:", error);
256
+ return [];
257
+ }
258
+ }
259
+ /**
260
+ * Clear all stored references and proofs (for testing/debugging)
261
+ */
262
+ export async function clearAllStorage() {
263
+ try {
264
+ // Get all reference IDs
265
+ const referenceIds = await getAllReferenceIds();
266
+ // Delete all references
267
+ for (const refId of referenceIds) {
268
+ const key = `${STORAGE_KEYS.REFERENCE_PREFIX}${refId}`;
269
+ await AsyncStorage.removeItem(key);
270
+ }
271
+ // Clear reference index
272
+ await AsyncStorage.removeItem(STORAGE_KEYS.REFERENCE_INDEX);
273
+ // Get all proof IDs
274
+ const proofIds = await getAllProofIds();
275
+ // Delete all proof files
276
+ const { proofsDir } = getFilePaths();
277
+ for (const proofId of proofIds) {
278
+ const filePath = `${proofsDir}${proofId}.json`;
279
+ try {
280
+ await FileSystem.deleteAsync(filePath, { idempotent: true });
281
+ }
282
+ catch (error) {
283
+ console.warn(`[DefaultStorageAdapter] Error deleting proof file: ${proofId}`, error);
284
+ }
285
+ }
286
+ // Clear proof index
287
+ await AsyncStorage.removeItem(STORAGE_KEYS.PROOF_INDEX);
288
+ console.log("[DefaultStorageAdapter] All storage cleared");
289
+ }
290
+ catch (error) {
291
+ console.error("[DefaultStorageAdapter] Error clearing storage:", error);
292
+ throw error;
293
+ }
294
+ }
295
+ /**
296
+ * Singleton instance for convenience.
297
+ * Use this if you don't need custom configuration.
298
+ */
299
+ export const defaultStorageAdapter = createDefaultStorageAdapter();